Event-Driven Enterprise Integration
Build a serverless pipeline connecting simulated Salesforce and SAP systems on AWS.
Introduction
30 Second Summary
When a customer places an order, their data lives in one system. The inventory needed to fulfill that order sits in a completely different system. Connecting these two is one of the most common problems in enterprise IT.
In this project, you will build a serverless event-driven pipeline on AWS that automatically validates orders against inventory, routes decisions through an event bus, and sends email notifications when orders are fulfilled or rejected.
What You'll Build
A serverless order fulfillment pipeline using AWS Lambda, Amazon EventBridge, and Amazon SNS that processes simulated Salesforce-to-SAP data flows on AWS.
By the end of this project, you'll have:
- An S3 data landing zone with simulated Salesforce order files and SAP inventory data.
- A Lambda function that validates orders against inventory and publishes custom events to EventBridge.
- A custom event bus with routing rules that directs fulfilled and rejected orders to SNS email notifications.
- Secret Mission: Add Amazon Bedrock AI to automatically classify order priority before processing.
Are there any prerequisites?
You need an AWS account (paid plan) to complete this project. No prior Python, Salesforce, or SAP experience is needed.
Before We Start
To build an event-driven pipeline on AWS, you need the AWS CLI installed on your machine. This tool lets you create and manage AWS resources directly from your terminal.
In this step, you will install the AWS CLI, configure it with your credentials, and set up the project folder structure in Cursor.
In this step, get ready to:
- Install and verify AWS CLI v2 on your Windows machine.
- Configure AWS CLI with your credentials and default region.
- Create the project folder structure in Cursor.
Install AWS CLI v2
The AWS CLI is a command-line tool that lets you interact with AWS services from your terminal. You need version 2 for this project.
- Open Command Prompt by pressing the Windows key, typing cmd, and pressing Enter.
- Check if AWS CLI is already installed by running this command:
aws --version
✔️ I see aws-cli/2.x.x
Your output starts with something like aws-cli/2.27.41 Python/3.11.6 Windows/10 exe/AMD64. The important part is that it starts with aws-cli/2. confirming you have version 2.
You are good to go. Skip ahead to the next substep.
ⓧ I see aws-cli/1.x.x
You have AWS CLI version 1 installed. Version 2 replaces version 1 on Windows when you install it. The v2 MSI installer handles the upgrade automatically.
- Download the AWS CLI v2 installer by opening this URL in your browser:
- https://awscli.amazonaws.com/AWSCLIV2.msi
- Run the downloaded AWSCLIV2.msi file and follow the installer prompts, accepting the defaults.
- Close and reopen Command Prompt after the install completes.
- Verify the upgrade by running:
aws --version
You should now see output starting with aws-cli/2. confirming version 2 is installed.
ⓧ 'aws' is not recognized
AWS CLI is not installed on your machine yet. Let's install it now.
- Download the AWS CLI v2 installer by opening this URL in your browser:
- https://awscli.amazonaws.com/AWSCLIV2.msi
- Run the downloaded AWSCLIV2.msi file and follow the installer prompts, accepting the defaults.
- Close and reopen Command Prompt after the install completes.
- Verify the installation by running:
aws --version
You should see output starting with aws-cli/2. followed by Python version and platform details.
Still seeing 'aws' is not recognized after installing?
Make sure you closed and reopened Command Prompt after the installation. The PATH update only takes effect in new terminal windows.
If it still does not work, try restarting your computer. Some Windows installations require a full restart for PATH changes to apply.
Help me troubleshoot my AWS CLI installation on Windows.
Configure your AWS credentials
The AWS CLI needs credentials to authenticate your requests. You will connect it to an IAM user with administrator permissions.
If you already have an IAM user with access keys and AdministratorAccess, skip to the aws configure step below. Otherwise, create one now.
- Sign in to the AWS Management Console at https://console.aws.amazon.com.
- Search for IAM in the top search bar and select IAM.
- Click Users in the left sidebar.
- Click Create user.
- Enter a username (e.g. admin-cli) and click Next.
- Select Attach policies directly.
- Search for AdministratorAccess and check the box next to it.
- Click Next, then Create user.
- Click on the username you just created to open the user details.
- Go to the Security credentials tab.
- Scroll down to Access keys and click Create access key.
- Select Command Line Interface (CLI) as the use case.
- Check the confirmation box at the bottom and click Next.
- Click Create access key.
- Copy both the Access key and Secret access key to a safe place. You will need them in the next step.
Keep your keys safe
This is the only time you can view the Secret access key. Copy both keys now and store them somewhere secure. Do not share them or commit them to source code.
Now configure the AWS CLI with your credentials.
- Run the configuration command in Command Prompt:
aws configure
What does aws configure ask for?
The command prompts you for four values, one at a time. Enter each value and press Enter:
- AWS Access Key ID: paste the Access Key you copied.
- AWS Secret Access Key: paste the Secret Access Key you copied.
- Default region name: enter us-east-1.
- Default output format: enter json.
- Verify your configuration works by running:
aws sts get-caller-identity
You should see JSON output showing your Account ID, User ARN, and User ID. This confirms the CLI is properly authenticated.
Seeing an authentication error?
Double-check that you pasted your Access Key ID and Secret Access Key correctly. Run aws configure again to re-enter them.
Make sure your IAM user has the AdministratorAccess policy attached. Without it, some commands may fail.
Help me fix my AWS CLI authentication error.
Create the project folder structure
Your project needs a specific folder structure to keep data files and Lambda code organized. You will create this inside Cursor.
- Open Cursor by pressing the Windows key, typing Cursor, and pressing Enter.
- Open the built-in terminal by pressing Ctrl+` (backtick key, usually above Tab).
- Move to your Desktop by running this command:
cd ~/Desktop
- Create the project folder and move into it by running these commands:
mkdir enterprise-pipeline
cd enterprise-pipeline
- Create the required subfolders by running these commands:
mkdir data\salesforce-orders
mkdir data\sap-inventory
mkdir lambda
What are these folders for?
The data/salesforce-orders folder holds simulated order data (what Salesforce would send in production). The data/sap-inventory folder holds inventory data (what SAP would provide).
The lambda folder holds the Python function code that processes orders and publishes events.
- Confirm the folders were created by running:
dir
You should see data and lambda listed in the output.
- Now open this folder in Cursor by clicking File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop, select the enterprise-pipeline folder, and click Select Folder.
In the Explorer sidebar on the left, you should see your data and lambda folders listed.
Don't see the folders in the Explorer sidebar?
Make sure you opened the enterprise-pipeline folder itself, not the Desktop or a parent folder. The folder tree in the sidebar should show data and lambda at the top level.
If the sidebar is hidden, press Ctrl+B to toggle it open.
Help me open my project folder in Cursor.
Your development environment is ready. Next up, you will create the S3 data landing zone with simulated Salesforce and SAP data.
Create the Data Landing Zone
Your development environment is set up and ready. Now it's time to build the foundation of your integration pipeline: the data landing zone where enterprise data arrives.
In a real enterprise system, Amazon AppFlow automatically syncs customer data from Salesforce and inventory data from SAP into S3 buckets. You'll create the same bucket structure and upload sample data that mirrors real enterprise formats.
In this step, get ready to:
- Create two S3 buckets to store Salesforce order data and SAP inventory data.
- Upload sample data files that simulate real enterprise data flows.
- Verify the data landing zone is ready for the pipeline.
Create the S3 buckets and upload inventory data
S3 bucket names must be globally unique across all of AWS. You'll use your AWS account ID as a suffix to guarantee uniqueness.
- Find your AWS account ID by running this command in your terminal:
aws sts get-caller-identity --query Account --output text
What does this command do?
This queries AWS for the identity associated with your configured credentials and returns just the 12-digit account ID. You'll use this number in your bucket names.
- Note down the 12-digit number that appears. You'll use it in the next commands.
- Create both S3 buckets by running these commands (replace the account ID placeholder with your actual number):
aws s3 mb s3://sf-orders-[[AWS_ACCOUNT_ID="your 12-digit AWS account ID"]]
aws s3 mb s3://sap-inventory-[[AWS_ACCOUNT_ID="your 12-digit AWS account ID"]]
What do these commands do?
The aws s3 mb command creates ("makes") a new S3 bucket. Since your CLI is configured with us-east-1 as the default region, both buckets are created there automatically.
The first bucket stores incoming Salesforce orders. The second stores SAP inventory data that the pipeline checks against.
- You should see two lines of output confirming each bucket was created, like make_bucket: s3://sf-orders-123456789012.
Seeing a BucketAlreadyExists error?
S3 bucket names are globally unique. If someone else already has a bucket with that name, add a short random suffix (like -v2 or -dev) to your bucket name. Just make sure to use your modified name consistently throughout the project.
Help me fix this BucketAlreadyExists error
Now you'll create the SAP inventory data file that the Lambda function will check orders against.
- In Cursor, open the data/sap-inventory folder inside your enterprise-pipeline project.
- Create a new file called inventory.json and paste in the following content:
{
"SAP-WIDGET-100": {
"name": "Industrial Widget A",
"stock": 200,
"warehouse": "US-EAST-1",
"unit_price": 45.99,
"last_sync": "2026-07-04T08:00:00Z"
},
"SAP-WIDGET-200": {
"name": "Premium Widget B",
"stock": 5,
"warehouse": "US-WEST-2",
"unit_price": 129.99,
"last_sync": "2026-07-04T08:00:00Z"
},
"SAP-MOTOR-300": {
"name": "Electric Motor C",
"stock": 0,
"warehouse": "EU-CENTRAL-1",
"unit_price": 899.00,
"last_sync": "2026-07-04T08:00:00Z"
}
}
What does this data represent?
This is simulated SAP ERP inventory data. Each product has a stock level that determines whether an incoming order can be fulfilled.
- SAP-WIDGET-100 has 200 units in stock (orders for this will succeed).
- SAP-WIDGET-200 has only 5 units (large orders will be rejected).
- SAP-MOTOR-300 has 0 units (all orders will be rejected).
- Save inventory.json.
- Upload the inventory file to S3 by running this command:
aws s3 cp data/sap-inventory/inventory.json s3://sap-inventory-[[AWS_ACCOUNT_ID="your 12-digit AWS account ID"]]/inventory.json
- You should see output like upload: data/sap-inventory/inventory.json to s3://sap-inventory-.../inventory.json.
Seeing a NoSuchBucket error?
Double check that your bucket name in the upload command matches exactly what you used in the aws s3 mb command earlier. The account ID must be identical.
Help me troubleshoot this NoSuchBucket error
Create the sample Salesforce order files
Each order represents a different business scenario that your pipeline will handle. These files simulate what Salesforce would send through AppFlow in production.
- In Cursor, open the data/salesforce-orders folder.
- Create a new file called order-001.json and paste in the following content:
{
"order_id": "SF-2026-001",
"customer": "Acme Corporation",
"product_id": "SAP-WIDGET-100",
"quantity": 50,
"order_date": "2026-07-04",
"source_system": "Salesforce",
"sales_rep": "Jane Smith"
}
What does this order test?
This order requests 50 units of SAP-WIDGET-100, which has 200 in stock. This is the "happy path" scenario where the order will be fulfilled successfully.
- Save order-001.json.
- Create a second file called order-002.json and paste in this content:
{
"order_id": "SF-2026-002",
"customer": "GlobalTech Industries",
"product_id": "SAP-WIDGET-200",
"quantity": 10,
"order_date": "2026-07-04",
"source_system": "Salesforce",
"sales_rep": "Bob Johnson"
}
What does this order test?
This order requests 10 units of SAP-WIDGET-200, but only 5 are in stock. This tests the "insufficient stock" rejection scenario.
- Save order-002.json.
- Create a third file called order-003.json and paste in this content:
{
"order_id": "SF-2026-003",
"customer": "StartupXYZ",
"product_id": "SAP-MOTOR-300",
"quantity": 2,
"order_date": "2026-07-04",
"source_system": "Salesforce",
"sales_rep": "Alice Chen"
}
What does this order test?
This order requests 2 units of SAP-MOTOR-300, which has 0 in stock. This tests the "completely out of stock" rejection scenario.
- Save order-003.json.
- Upload all three order files to the Salesforce orders bucket by running:
aws s3 cp data/salesforce-orders/ s3://sf-orders-[[AWS_ACCOUNT_ID="your 12-digit AWS account ID"]]/ --recursive
What does --recursive do?
The --recursive flag tells aws s3 cp to upload all files in the folder rather than a single file. You should see three upload confirmations, one for each order file.
- You should see three lines of output confirming each file was uploaded.
Only seeing one or two files uploaded?
Make sure all three files (order-001.json, order-002.json, order-003.json) are saved in the data/salesforce-orders/ folder. Unsaved files won't appear on disk for the CLI to upload.
Help me figure out why some files are missing
Verify the data landing zone
Before building the Lambda processor, confirm that all your data is in the right place.
- List the contents of both buckets by running these commands:
aws s3 ls s3://sf-orders-[[AWS_ACCOUNT_ID="your 12-digit AWS account ID"]]/
aws s3 ls s3://sap-inventory-[[AWS_ACCOUNT_ID="your 12-digit AWS account ID"]]/
- The first command should list three files: order-001.json, order-002.json, and order-003.json.
- The second command should list one file: inventory.json.
Not seeing the expected files?
If a bucket is empty, re-run the upload commands from earlier. Make sure your terminal is in the enterprise-pipeline folder so the relative paths resolve correctly. You can verify with cd and dir (Windows) to confirm the local files exist.
Help me figure out why my S3 bucket is empty
Why S3 as the landing zone?
In production, Amazon AppFlow would automatically deliver Salesforce data to the orders bucket and SAP data to the inventory bucket. By structuring your data landing zone this way now, the downstream architecture (Lambda, EventBridge, SNS) will work identically whether data arrives manually or through AppFlow.
The only change for production is replacing manual uploads with an AppFlow connection.
✔️ Awesome, I've got everything!
Great. Make sure all your JSON files are saved locally and confirmed in S3 before moving on.
ⓧ I'd like to double check the full code
Here is the complete content for each file. Compare yours against these:
{
"SAP-WIDGET-100": {
"name": "Industrial Widget A",
"stock": 200,
"warehouse": "US-EAST-1",
"unit_price": 45.99,
"last_sync": "2026-07-04T08:00:00Z"
},
"SAP-WIDGET-200": {
"name": "Premium Widget B",
"stock": 5,
"warehouse": "US-WEST-2",
"unit_price": 129.99,
"last_sync": "2026-07-04T08:00:00Z"
},
"SAP-MOTOR-300": {
"name": "Electric Motor C",
"stock": 0,
"warehouse": "EU-CENTRAL-1",
"unit_price": 899.00,
"last_sync": "2026-07-04T08:00:00Z"
}
}
{
"order_id": "SF-2026-001",
"customer": "Acme Corporation",
"product_id": "SAP-WIDGET-100",
"quantity": 50,
"order_date": "2026-07-04",
"source_system": "Salesforce",
"sales_rep": "Jane Smith"
}
{
"order_id": "SF-2026-002",
"customer": "GlobalTech Industries",
"product_id": "SAP-WIDGET-200",
"quantity": 10,
"order_date": "2026-07-04",
"source_system": "Salesforce",
"sales_rep": "Bob Johnson"
}
{
"order_id": "SF-2026-003",
"customer": "StartupXYZ",
"product_id": "SAP-MOTOR-300",
"quantity": 2,
"order_date": "2026-07-04",
"source_system": "Salesforce",
"sales_rep": "Alice Chen"
}
Your data landing zone is complete. Next up, you'll build the Lambda function that reads these orders, checks them against inventory, and publishes the results as events.
Build the Order Processor Lambda
Your data landing zone is ready with Salesforce orders and SAP inventory sitting in S3. Now you need something to actually process those orders.
In this step, you will write a Lambda function that reads an incoming order, checks it against inventory, and publishes the result as a custom event. This is the core integration logic that connects CRM data to ERP data.
In this step, get ready to:
- Write the order processing function in Python.
- Create an IAM role with the right permissions for the Lambda.
- Deploy the Lambda and connect it to your S3 bucket with a trigger.
Write the order processor function
The Lambda function needs to do three things: read the order file from S3, look up inventory in the SAP bucket, and decide whether the order can be fulfilled.
- Switch back to Cursor and open the lambda folder inside your enterprise-pipeline project.
- Create a new file called order_processor.py inside the lambda folder.
- Add the imports and client setup by pasting this code:
import json
import boto3
import os
# Create AWS service clients for S3 and EventBridge
s3_client = boto3.client('s3')
eventbridge_client = boto3.client('events')
What does this code do?
These lines import the libraries your function needs and create two service clients. The s3_client lets your function read files from S3 buckets. The eventbridge_client lets it publish events to EventBridge.
You do not need to install boto3 separately. It comes pre-installed in the Lambda Python runtime.
- Below the client setup, add the main handler function that reads and processes orders:
def lambda_handler(event, context):
# Extract the bucket name and file key from the S3 event
bucket = event['Records'][0]['s3']['bucket']['name']
key = event['Records'][0]['s3']['object']['key']
# Read the order file from S3
response = s3_client.get_object(Bucket=bucket, Key=key)
order = json.loads(response['Body'].read().decode('utf-8'))
# Read inventory data from the SAP bucket
inventory_bucket = os.environ['INVENTORY_BUCKET']
inv_response = s3_client.get_object(
Bucket=inventory_bucket, Key='inventory.json'
)
inventory = json.loads(inv_response['Body'].read().decode('utf-8'))
# Look up the product and compare stock to requested quantity
product_id = order['product_id']
quantity_requested = order['quantity']
available = inventory.get(product_id, {})
stock = available.get('stock', 0)
product_name = available.get('name', product_id)
# Determine if the order can be fulfilled
if stock >= quantity_requested:
status = 'FULFILLED'
message = (
f"Order {order['order_id']} for {quantity_requested}x "
f"{product_name} can be fulfilled. Stock available: {stock}"
)
else:
status = 'REJECTED'
message = (
f"Order {order['order_id']} for {quantity_requested}x "
f"{product_name} rejected. "
f"Requested: {quantity_requested}, Available: {stock}"
)
# Build the event detail payload
event_detail = {
'order_id': order['order_id'],
'customer': order['customer'],
'product_id': product_id,
'product_name': product_name,
'quantity_requested': quantity_requested,
'stock_available': stock,
'status': status,
'message': message,
'source_system': order.get('source_system', 'Unknown')
}
# Publish the event to EventBridge
eventbridge_client.put_events(
Entries=[
{
'Source': 'enterprise.order-processor',
'DetailType': f'Order {status}',
'Detail': json.dumps(event_detail),
'EventBusName': os.environ['EVENT_BUS_NAME']
}
]
)
return {
'statusCode': 200,
'body': json.dumps({
'order_id': order['order_id'],
'status': status,
'message': message
})
}
What does this code do?
The lambda_handler function is what AWS Lambda calls when the function is triggered. It does four things in sequence:
- Reads the uploaded order file from the Salesforce orders bucket using the event payload that S3 sends.
- Fetches the SAP inventory data from the second bucket using the INVENTORY_BUCKET environment variable.
- Compares the requested quantity against available stock and sets the status to FULFILLED or REJECTED.
- Publishes a custom event to EventBridge with the order details and decision. The DetailType field is what EventBridge rules will filter on later.
- Save order_processor.py.
✔️ Awesome, I've got everything!
Great. Double check you have saved your file before moving on.
ⓧ I'd like to double check the full code
import json
import boto3
import os
s3_client = boto3.client('s3')
eventbridge_client = boto3.client('events')
def lambda_handler(event, context):
bucket = event['Records'][0]['s3']['bucket']['name']
key = event['Records'][0]['s3']['object']['key']
response = s3_client.get_object(Bucket=bucket, Key=key)
order = json.loads(response['Body'].read().decode('utf-8'))
inventory_bucket = os.environ['INVENTORY_BUCKET']
inv_response = s3_client.get_object(
Bucket=inventory_bucket, Key='inventory.json'
)
inventory = json.loads(inv_response['Body'].read().decode('utf-8'))
product_id = order['product_id']
quantity_requested = order['quantity']
available = inventory.get(product_id, {})
stock = available.get('stock', 0)
product_name = available.get('name', product_id)
if stock >= quantity_requested:
status = 'FULFILLED'
message = (
f"Order {order['order_id']} for {quantity_requested}x "
f"{product_name} can be fulfilled. Stock available: {stock}"
)
else:
status = 'REJECTED'
message = (
f"Order {order['order_id']} for {quantity_requested}x "
f"{product_name} rejected. "
f"Requested: {quantity_requested}, Available: {stock}"
)
event_detail = {
'order_id': order['order_id'],
'customer': order['customer'],
'product_id': product_id,
'product_name': product_name,
'quantity_requested': quantity_requested,
'stock_available': stock,
'status': status,
'message': message,
'source_system': order.get('source_system', 'Unknown')
}
eventbridge_client.put_events(
Entries=[
{
'Source': 'enterprise.order-processor',
'DetailType': f'Order {status}',
'Detail': json.dumps(event_detail),
'EventBusName': os.environ['EVENT_BUS_NAME']
}
]
)
return {
'statusCode': 200,
'body': json.dumps({
'order_id': order['order_id'],
'status': status,
'message': message
})
}
Seeing a syntax error in Cursor?
Make sure the f-strings use regular double quotes and that parentheses are balanced in the message assignments. Each opening parenthesis must have a matching closing parenthesis.
Help me fix a syntax error in my order_processor.py Lambda function
Create the Lambda execution role and function
Before Lambda can run your code, it needs an IAM role that grants it permission to access S3, publish events to EventBridge, and write logs to CloudWatch.
- Open the AWS Console and navigate to the IAM service by typing IAM in the top search bar and selecting it.
- In the left sidebar, click **Roles**.
- Click **Create role**.
- Under **Trusted entity type**, select **AWS service**.
- Under **Use case**, select **Lambda**.
- Click **Next**.
- In the permissions search box, type AWSLambdaBasicExecutionRole and check the box next to it. This gives your function permission to write logs.
- Clear the search box, then type AmazonS3ReadOnlyAccess and check the box next to it. This gives your function permission to read from S3 buckets.
- Click **Next**.
- For **Role name**, enter order-processor-role.
- Click **Create role**.
What about EventBridge permissions?
You attached two managed policies so far, but the function also needs events:PutEvents permission to publish to EventBridge. You will add this as an inline policy next.
- After the role is created, click on order-processor-role in the roles list to open it.
- Click the **Permissions** tab, then click **Add permissions** and select **Create inline policy**.
- Click the **JSON** tab in the policy editor and replace the contents with this policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "events:PutEvents",
"Resource": "*"
}
]
}
- Click **Next**.
- For the policy name, enter EventBridgePutEvents.
- Click **Create policy**.
Why three separate permissions?
Your Lambda function talks to three AWS services. AWSLambdaBasicExecutionRole handles CloudWatch Logs. AmazonS3ReadOnlyAccess handles reading from S3. The inline policy handles publishing events to EventBridge.
This follows the principle of least privilege. Each permission is scoped to exactly what the function needs.
Now create the Lambda function itself.
- Navigate to the Lambda service by typing Lambda in the top search bar and selecting it.
- Click **Create function**.
- Select **Author from scratch**.
- For **Function name**, enter order-processor.
- For **Runtime**, select **Python 3.13**.
- Expand **Change default execution role**.
- Select **Use an existing role**.
- In the **Existing role** dropdown, select order-processor-role.
- Click **Create function**.
- In the code editor on the Lambda function page, select all the existing code and replace it with the contents of your order_processor.py file from Cursor.
- Click **Deploy** to save the code.
You should see a green banner confirming the function was updated successfully.
- Click the **Configuration** tab.
- In the left sidebar, click **Environment variables**.
- Click **Edit**.
- Click **Add environment variable**. For **Key**, enter INVENTORY_BUCKET. For **Value**, enter your SAP inventory bucket name (sap-inventory- followed by your account ID).
- Click **Add environment variable** again. For **Key**, enter EVENT_BUS_NAME. For **Value**, enter enterprise-integration-bus.
- Click **Save**.
Why use environment variables?
Environment variables keep your code portable. Instead of hardcoding bucket names and bus names into the Python file, you configure them externally.
If you ever need to point the function at a different bucket, you change the variable without redeploying code.
Don't see your role in the dropdown?
IAM roles can take a few seconds to propagate. If order-processor-role does not appear, wait 10 seconds and refresh the page. Also confirm you created the role in the same AWS account you are logged into.
Help me troubleshoot why my IAM role is not showing up when creating a Lambda function
Add the S3 event trigger
Your function exists, but nothing is calling it yet. You need to tell S3 to invoke this function whenever a new file lands in the Salesforce orders bucket.
- On your Lambda function page, scroll up to the **Function overview** pane at the top.
- Click **Add trigger**.
- In the trigger source dropdown, select **S3**.
- Under **Bucket**, select your Salesforce orders bucket (sf-orders- followed by your account ID).
- Under **Event types**, confirm that **All object create events** is selected.
- Under **Recursive invocation**, check the acknowledgement box. Your function reads from one bucket and writes to EventBridge, so there is no loop risk.
- Click **Add**.
You should see the S3 trigger appear in the Function overview diagram, connected to your Lambda function.
What just happened?
When you add a trigger through the Lambda console, AWS automatically creates a resource-based policy on your function that grants S3 permission to invoke it. It also configures the event notification on your S3 bucket. Both happen in one click.
Seeing an error about existing event notifications?
If your bucket previously had event notifications configured for a resource that no longer exists, S3 cannot create the new notification. Remove any stale event notifications from the bucket's **Properties** tab in the S3 console, then try adding the trigger again.
Help me fix an error when adding an S3 trigger to my Lambda function
Your order processor is deployed and wired up. Next, you will create the EventBridge bus and routing rules so events actually have somewhere to go.
Wire Up EventBridge
Your Lambda function is ready to process orders and publish events. But right now, those events have nowhere to go.
In this step, you will create the central routing layer of the architecture: a custom EventBridge event bus with rules that direct order events to the right place based on their fulfillment status.
In this step, get ready to:
- Create a custom EventBridge event bus called enterprise-integration-bus.
- Create two routing rules that filter events by fulfillment status.
- Test the pipeline end-to-end by uploading an order file and checking CloudWatch Logs.
Create a custom event bus
The default event bus in every AWS account only receives events from AWS services. Your Lambda publishes custom events, so it needs a dedicated custom event bus to receive them.
- Open the Amazon EventBridge console at https://console.aws.amazon.com/events/.
- In the left navigation pane, click Event buses.
- Click Create event bus.
- Enter enterprise-integration-bus as the name.
- Leave all other settings at their defaults (AWS owned key, no dead-letter queue, no archive).
- Click Create.
What is a custom event bus?
An event bus is a channel that receives events and routes them to rules. The default bus handles AWS service events. A custom bus handles your application's own events.
In production, this bus could fan out to dozens of consumers (analytics, audit logs, billing) without changing the Lambda that publishes events. That decoupling is the core value of event-driven architecture.
You should see enterprise-integration-bus listed under Custom event bus on the Event buses page.
Don't see your event bus?
Make sure you are in the us-east-1 region. Check the region selector in the top-right corner of the console.
help me find my custom event bus in EventBridge
Create routing rules for fulfilled and rejected orders
Rules tell EventBridge what to do when an event arrives on the bus. Each rule has an event pattern (a filter) and a target (a destination). You will create two rules: one that catches fulfilled orders and one that catches rejected orders.
Start with the fulfilled order rule:
- In the left navigation pane, click Rules.
- Click Create rule.
- For Name, enter order-fulfilled-rule.
- For Event bus, select enterprise-integration-bus from the dropdown.
- For Rule type, select Rule with an event pattern.
- Click Next.
Now define the event pattern that filters for fulfilled orders:
- Under Event source, select Other.
- Scroll down to the Event pattern section.
- Under Creation method, choose Custom pattern (JSON editor).
- Paste the following event pattern into the editor:
{
"detail-type": ["Order FULFILLED"]
}
What does this event pattern do?
EventBridge matches incoming events against this pattern. The detail-type field is set by your Lambda when it calls put_events. Only events with a DetailType of Order FULFILLED will match this rule.
Any field you omit from the pattern acts as a wildcard. Since you only specified detail-type, this rule matches fulfilled order events regardless of source or detail content.
- Click Next.
Now configure the target to send matched events to CloudWatch Logs for debugging:
- For Target types, select AWS service.
- For Select a target, choose CloudWatch log group.
- For the log group, enter /aws/events/order-fulfilled. EventBridge will create this log group automatically.
- Click Next.
- Skip the tags page by clicking Next again.
- Review the details, then click Create rule.
Now repeat the process for the rejected order rule:
- Click Create rule again.
- For Name, enter order-rejected-rule.
- For Event bus, select enterprise-integration-bus.
- For Rule type, select Rule with an event pattern.
- Click Next.
- Under Event source, select Other.
- Under Creation method, choose Custom pattern (JSON editor).
- Paste the following event pattern:
{
"detail-type": ["Order REJECTED"]
}
- Click Next.
- For Target types, select AWS service.
- For Select a target, choose CloudWatch log group.
- For the log group, enter /aws/events/order-rejected.
- Click Next.
- Skip the tags page by clicking Next again.
- Review and click Create rule.
Why two separate rules instead of one?
Each rule routes to a different target. In the next step, you will point them at different SNS messages. Keeping them separate means you can later add analytics for fulfilled orders without touching the rejected-order path.
This is the content-based routing pattern. The event bus inspects each event and routes it to only the consumers that care about it.
You should now see both order-fulfilled-rule and order-rejected-rule listed on the Rules page with enterprise-integration-bus as their event bus.
Rules not showing up?
Make sure you selected enterprise-integration-bus from the Event bus dropdown at the top of the Rules page. Rules are scoped to a specific bus and won't appear if you're viewing the default bus.
help me see my EventBridge rules on a custom event bus
Test the pipeline by uploading an order
Everything is wired up. Time to trigger the pipeline and verify events flow through the entire chain: S3 upload triggers Lambda, Lambda publishes to EventBridge, and EventBridge routes to CloudWatch Logs.
- Upload the order-001.json file to your Salesforce orders bucket by running this command in your terminal:
aws s3 cp data/salesforce-orders/order-001.json s3://[[SF_BUCKET="sf-orders-YOUR-ACCOUNT-ID"]]/order-001.json
Why re-upload a file that already exists?
S3 event notifications fire on object creation. Even though order-001.json was uploaded in Step 2, re-uploading it (overwriting) creates a new PutObject event. This triggers the Lambda fresh.
- Wait about 10-15 seconds for the Lambda to execute and publish the event.
- Open the CloudWatch console at https://console.aws.amazon.com/cloudwatch/.
- In the left navigation pane, click Log groups under Logs.
- Find and click the log group /aws/events/order-fulfilled.
- Click on the most recent log stream.
You should see a JSON event containing Order FULFILLED in the detail-type field, along with details like the order ID, customer name, product, and stock information.
What should I see?
The log event is a full EventBridge event envelope. Look for the detail field which contains your order processor's output: order_id, customer, status, and message.
Order 001 requests 50 widgets against 200 in stock. This means it should be FULFILLED and appear in the /aws/events/order-fulfilled log group.
No log group or log stream appearing?
- Check the Lambda's CloudWatch Logs at /aws/lambda/order-processor. If there are errors there, the Lambda failed before publishing to EventBridge.
- Verify the Lambda's EVENT_BUS_NAME environment variable is set to enterprise-integration-bus (not the ARN, just the name).
- Confirm the rule's event pattern matches exactly Order FULFILLED (capital F, with a space). The Lambda publishes f'Order {status}' where status is FULFILLED.
help me debug why my EventBridge rule isn't delivering events to CloudWatch Logs
Your event bus is live and routing. The pipeline now flows from S3 all the way through to CloudWatch Logs. Next up, you will replace CloudWatch Logs with email notifications so stakeholders get real-time order updates.
Add SNS Notifications and Test End-to-End
Your pipeline can process orders and route events through EventBridge. But right now, the only way to confirm it works is digging through CloudWatch Logs.
In a real enterprise integration, stakeholders need instant notifications when orders are fulfilled or rejected. In this step, you will connect Amazon SNS as the final consumer in the pipeline and run the complete end-to-end test with all three order scenarios.
In this step, get ready to:
- Create an SNS topic and subscribe your email address.
- Update EventBridge rules to send formatted email notifications via SNS.
- Test the full pipeline end-to-end with all three order scenarios.
Create the SNS topic and email subscription
Amazon SNS is a messaging service that pushes notifications to subscribers. You will create a topic that acts as a message channel, then subscribe your email address so you receive order status updates.
- Create the SNS topic by running this command in your terminal:
aws sns create-topic --name order-notifications
What does this command do?
This creates a new SNS topic called order-notifications in your AWS account. The command returns a TopicArn value that uniquely identifies this topic. You will need this ARN in the next command.
- Copy the TopicArn value from the output. It looks like arn:aws:sns:us-east-1:123456789012:order-notifications (with your own account ID).
- Subscribe your email address to the topic by running this command:
aws sns subscribe --topic-arn [[TOPIC_ARN="arn:aws:sns:us-east-1:YOUR-ACCOUNT-ID:order-notifications"]] --protocol email --notification-endpoint [[YOUR_EMAIL="your-email@example.com"]]
What does this command do?
The --protocol email flag tells SNS to deliver messages via SMTP email. The --notification-endpoint flag specifies the email address that receives notifications.
The output shows "SubscriptionArn": "pending confirmation" because email subscriptions require you to verify your address before notifications can be delivered.
- Check your email inbox for a message from AWS Notifications with the subject line containing your topic name.
- Click the Confirm subscription link in the email.
- You should see a confirmation page in your browser that says your subscription is confirmed.
Don't see the confirmation email?
- Check your spam or junk folder. AWS notification emails sometimes land there.
- Make sure you typed your email address correctly in the subscribe command. Run the subscribe command again with the correct address if needed.
- Wait 2-3 minutes. Email delivery can be slightly delayed.
Still stuck? Help me troubleshoot why I'm not receiving the SNS confirmation email.
Add SNS as a target on both EventBridge rules
Your EventBridge rules currently send events to CloudWatch Logs for debugging. Now you will add the SNS topic as a second target with an input transformer that formats the raw event data into a readable email message.
- Open the Amazon EventBridge console at https://console.aws.amazon.com/events/.
- In the left navigation pane, click Rules.
- Select the enterprise-integration-bus event bus from the event bus dropdown at the top.
- Click the order-fulfilled-rule rule name to open it.
- Click the Targets tab.
- Click Edit to modify the rule.
- Scroll down to the Targets section and click Add another target.
- In the target type dropdown, select AWS service.
- Select SNS topic as the specific service.
- In the Topic dropdown, select order-notifications.
Now you will configure the input transformer so the email contains a readable message instead of raw JSON.
- Expand Additional settings.
- In Configure target input, select Input transformer from the dropdown.
- Click Configure input transformer.
- In the Input path field, paste the following JSON that maps event fields to variables:
{"order_id": "$.detail.order_id", "customer": "$.detail.customer", "status": "$.detail.status", "message": "$.detail.message"}
- In the Template field, paste the following template:
"Order <order_id> for <customer> has been <status>. Details: <message>"
How does the input transformer work?
The input path extracts specific fields from the EventBridge event using JSONPath expressions. Each key (like order_id) becomes a variable you can reference in the template.
The template uses angle brackets to insert those variable values into a readable string. This is what gets delivered as the email body instead of the full raw event JSON.
- Click Confirm to save the input transformer configuration.
- Click Next through any remaining screens until you reach the review page.
- Click Update rule to save your changes.
Now repeat the same process for the rejected rule.
- Go back to Rules and click the order-rejected-rule rule name.
- Click the Targets tab, then click Edit.
- Click Add another target and select AWS service as the target type.
- Select SNS topic and choose order-notifications from the topic dropdown.
- Expand Additional settings and select Input transformer in Configure target input.
- Click Configure input transformer.
- Paste the same input path:
{"order_id": "$.detail.order_id", "customer": "$.detail.customer", "status": "$.detail.status", "message": "$.detail.message"}
- Paste the same template:
"Order <order_id> for <customer> has been <status>. Details: <message>"
- Click Confirm, then Next through to the review page, and click Update rule.
Don't see the SNS topic in the dropdown?
- Make sure you are in the us-east-1 region. The SNS topic must be in the same region as the EventBridge rule.
- Confirm the topic was created successfully by running aws sns list-topics in your terminal. You should see your order-notifications topic ARN in the list.
Still stuck? Help me figure out why my SNS topic isn't appearing in the EventBridge target dropdown.
Test the end-to-end pipeline with all three orders
The full pipeline is now wired: S3 upload triggers Lambda, Lambda publishes to EventBridge, EventBridge routes to SNS, and SNS emails you. Time to test all three order scenarios.
Why upload one at a time?
Each S3 upload triggers a separate Lambda invocation. Uploading one file at a time lets you confirm each order processes correctly before moving to the next. This makes troubleshooting easier if something fails.
- Upload the first order (fulfillable) by running this command:
aws s3 cp data/salesforce-orders/order-001.json s3://[[SF_BUCKET="sf-orders-YOUR-ACCOUNT-ID"]]/order-001.json
- Wait 30-60 seconds for the pipeline to process. Check your email inbox for a notification about order SF-2026-001 from Acme Corporation. It should show FULFILLED status because 50 widgets were requested and 200 are in stock.
- Upload the second order (will be rejected due to insufficient stock) by running:
aws s3 cp data/salesforce-orders/order-002.json s3://[[SF_BUCKET="sf-orders-YOUR-ACCOUNT-ID"]]/order-002.json
- Check your email for a notification about order SF-2026-002 from GlobalTech Industries. It should show REJECTED status because 10 units were requested but only 5 are available.
- Upload the third order (out of stock) by running:
aws s3 cp data/salesforce-orders/order-003.json s3://[[SF_BUCKET="sf-orders-YOUR-ACCOUNT-ID"]]/order-003.json
- Check your email for a notification about order SF-2026-003 from StartupXYZ. It should show REJECTED status because 2 Electric Motor C units were requested but 0 are in stock.
What should the emails contain?
Each email notification shows the order ID, customer name, fulfillment status, and a detailed message explaining the decision. Order 001 (Acme Corporation, 50 Industrial Widget A) is fulfilled. Orders 002 and 003 are rejected due to insufficient stock.
This is the exact pattern enterprise integration pipelines use. The downstream consumer (your inbox) receives structured, actionable notifications without knowing anything about the upstream systems.
Not receiving email notifications?
- Confirm your SNS subscription is confirmed (not pending). Run aws sns list-subscriptions and check that the SubscriptionArn is a full ARN, not PendingConfirmation.
- Check CloudWatch Logs for the Lambda function. If the Lambda errors, the event never reaches EventBridge or SNS.
- Verify your EventBridge rules have the SNS target by checking the Targets tab on each rule. You should see both CloudWatch Logs and the SNS topic listed.
- Check your spam folder. SNS notification emails sometimes get filtered.
Still stuck? Help me debug why my end-to-end pipeline isn't delivering email notifications.
Your enterprise integration pipeline is fully operational. You have a complete event-driven architecture that processes simulated Salesforce orders against SAP inventory, routes decisions through a central event bus, and delivers notifications to stakeholders. Next up, you will take this pipeline further with an AI-powered secret mission.
Secret mission
Add AI-Powered Order Priority Classification
Your pipeline processes orders, but every order gets the same treatment regardless of urgency. What if the system could automatically identify which orders deserve priority handling? In this secret mission, you will add Amazon Bedrock AI to classify incoming orders as HIGH, MEDIUM, or LOW priority using Claude 3 Haiku before the fulfillment check runs.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. None of these resources incur ongoing charges at this usage level because the pipeline only runs when you upload files.
Resources you used:
- 2 S3 buckets (sf-orders-{account-id} and sap-inventory-{account-id}).
- 1 Lambda function (order-processor).
- 1 EventBridge custom event bus (enterprise-integration-bus) with 2 rules.
- 1 SNS topic (order-notifications) with email subscription.
- 1 IAM role (order-processor-role).
- Local project folder (enterprise-pipeline).
Keep everything running
No action needed. Choose this if you are still actively building or plan to extend the pipeline further.
- The pipeline only runs when you upload files to the S3 bucket, so it will not generate charges while idle.
- Lambda, EventBridge, SNS, and S3 all remain within free tier at this usage level.
- Amazon Bedrock model access remains enabled but only costs money when you invoke the model.
Pause - I'll come back to this later
Shut down running processes to free up any background activity. Because this pipeline is event-driven, there is nothing actively running to pause.
- Remove the S3 event notification on the sf-orders-{account-id} bucket so that new uploads do not accidentally trigger the Lambda. You can re-add the trigger later.
- Keep all other resources in place. They do not cost anything while idle.
Delete - I don't want to use this again
Remove all project resources and start fresh. Run the following commands in your terminal one group at a time.
Empty and delete the S3 buckets by running these commands:
aws s3 rm s3://[[SF_BUCKET="sf-orders-your-account-id"]] --recursive
aws s3 rb s3://[[SF_BUCKET="sf-orders-your-account-id"]]
aws s3 rm s3://[[SAP_BUCKET="sap-inventory-your-account-id"]] --recursive
aws s3 rb s3://[[SAP_BUCKET="sap-inventory-your-account-id"]]
Delete the Lambda function by running:
aws lambda delete-function --function-name order-processor
Remove the EventBridge rule targets, then delete the rules and the event bus by running:
aws events remove-targets --rule order-fulfilled-rule --event-bus-name enterprise-integration-bus --ids target1
aws events remove-targets --rule order-rejected-rule --event-bus-name enterprise-integration-bus --ids target1
aws events delete-rule --name order-fulfilled-rule --event-bus-name enterprise-integration-bus
aws events delete-rule --name order-rejected-rule --event-bus-name enterprise-integration-bus
aws events delete-event-bus --name enterprise-integration-bus
Delete the SNS topic by running:
aws sns delete-topic --topic-arn [[TOPIC_ARN="arn:aws:sns:us-east-1:your-account-id:order-notifications"]]
Delete the IAM role using the AWS Console. The console automatically detaches managed policies and deletes inline policies for you.
- Open the IAM console at https://console.aws.amazon.com/iam/.
- Click Roles in the left navigation pane.
- Search for order-processor-role.
- Select the checkbox next to the role and click Delete at the top of the page.
- Type the role name to confirm, then click Delete.
Delete the local project folder by running:
cd %USERPROFILE%\Desktop
rmdir /s /q enterprise-pipeline
Nice Work!
Nice Work!
Nice work! You just built a production-grade, event-driven enterprise integration pipeline on AWS. This is the exact architecture pattern that connects CRM and ERP systems in real organizations.
You've learned how to:
- Build a serverless event-driven pipeline that processes simulated Salesforce orders against SAP inventory data using S3, Lambda, EventBridge, and SNS.
- Write a Lambda function in Python that validates orders against inventory and publishes custom events to a custom EventBridge event bus with content-based routing rules.
- Configure SNS email notifications as a downstream consumer and test the complete pipeline end-to-end with multiple business scenarios (fulfilled, rejected, and out-of-stock).
- Secret Mission: Integrated Amazon Bedrock AI to automatically classify order priority using Claude 3 Haiku before processing.
Ready to quiz yourself?