Event-Driven Order Processing System

Route order events to multiple consumers using EventBridge content-based filtering.

Introduction

30 Second Summary

When you place an order online, multiple systems need to react at the same time. The warehouse starts packing, billing charges your card, and shipping generates a label. These systems don't call each other directly. They listen for events and respond independently.

In this project, you will build an event-driven order processing system on AWS using Amazon EventBridge. You'll publish order events and watch them get automatically routed to different consumers based on their content.

What You'll Build

A simulated e-commerce backend where EventBridge routes order events to Lambda functions, SQS queues, and SNS topics based on event content.

By the end of this project, you'll have:

  • A custom EventBridge event bus that receives order events and routes them to multiple consumers based on content-based rules.
  • Content-based routing rules that use numeric filtering to separate high-value orders from standard ones.
  • Both push (EventBridge to Lambda) and pull (SQS to Lambda) invocation patterns running side by side with a dead-letter queue for fault tolerance.
  • Secret Mission: Add an input transformation to reshape event payloads before they reach downstream consumers.

Are there any prerequisites?

You'll need an AWS Free Tier account with the AWS CLI installed and configured. Basic familiarity with Lambda and Python is helpful but not required.

Set Up Your EventBridge Event Bus and Lambda Functions

Every event-driven system needs two things: a channel for events to flow through and consumers that react to those events.

In this step, you will create a custom EventBridge event bus to receive order events, and two Lambda functions that will process those events in different ways.

In this step, get ready to:
  • Create a custom EventBridge event bus for order events.
  • Create a Lambda function that processes all incoming orders (push pattern).
  • Create a Lambda function that handles order fulfillment from a queue (pull pattern).
Create a custom EventBridge event bus

The default EventBridge event bus receives all AWS service events, which adds noise when you only care about your application's events. A custom event bus isolates your order events from everything else.

  • In the AWS Management Console, navigate to the EventBridge console by entering EventBridge in the top search bar and selecting Amazon EventBridge.
  • In the left sidebar, click Event buses.
  • Click Create event bus.
  • For Name, enter orders-event-bus.
  • Leave the encryption setting as the default (AWS owned key).
  • Click Create.

What is a custom event bus?

An event bus is a pipeline that receives events and routes them to targets based on rules. The default bus receives AWS service events (like EC2 state changes). A custom bus like orders-event-bus is dedicated to your application's events only.

This separation means your routing rules only need to match against your own events, not the hundreds of AWS service events flowing through the default bus.

You should see orders-event-bus listed under Custom event bus on the Event buses page.

Don't see your event bus?

Make sure you clicked Create after entering the name. If the name field shows a validation error, check that you entered orders-event-bus with no spaces or special characters.

help me find my custom event bus in the EventBridge console

Create the Order Processor Lambda function

This function will receive every order event directly from EventBridge. It represents the push invocation pattern where EventBridge calls your function immediately when an event matches a rule.

  • Open the Lambda console by entering Lambda in the top search bar and selecting Lambda.
  • Click Create function.
  • Select Author from scratch.
  • For Function name, enter order-processor.
  • For Runtime, select Python 3.13.
  • Leave all other settings at their defaults.
  • Click Create function.

Lambda creates a basic Hello World function. You will now replace it with the order processing logic.

  • In the Code tab, click on the lambda_function.py file in the code editor.
  • Select all the existing code and delete it.
  • Paste in the following order processing code:
import json

def lambda_handler(event, context):
    # Log the full event for debugging
    print("Received order event:")
    print(json.dumps(event, indent=2))

    # Extract order details from the event
    order_id = event.get("detail", {}).get("order_id", "unknown")
    amount = event.get("detail", {}).get("amount", 0)
    customer = event.get("detail", {}).get("customer_name", "unknown")

    print(f"Processing order {order_id} for {customer}, amount: ${amount}")

    # Return a success response with order details
    return {
        "statusCode": 200,
        "body": json.dumps({
            "message": f"Order {order_id} processed successfully",
            "order_id": order_id,
            "amount": amount
        })
    }

What does this code do?

  • lambda_handler(event, context) is the entry point that Lambda calls when it receives an event from EventBridge.
  • json.dumps(event, indent=2) logs the full incoming event so you can inspect it in CloudWatch.
  • event.get("detail", {}).get("order_id", "unknown") safely extracts the order ID from the nested event structure. EventBridge wraps your custom data inside a detail field.
  • The function returns a 200 status with the processed order information.
  • Click Deploy in the DEPLOY section above the code editor.

You should see a green banner confirming the function was updated successfully.

Seeing a deployment error?

Check that you deleted all the original Hello World code before pasting. Leftover code can cause syntax errors. Also confirm that the indentation uses spaces, not tabs.

help me fix a Lambda deployment error after pasting my order-processor code

Create the Order Fulfillment Lambda function

This second function represents the pull invocation pattern. Instead of being called directly by EventBridge, it will poll messages from an SQS queue. This decoupling adds resilience because messages persist in the queue even if the function is temporarily unavailable.

  • Click Functions in the left sidebar to return to the functions list.
  • Click Create function.
  • Select Author from scratch.
  • For Function name, enter order-fulfillment.
  • For Runtime, select Python 3.13.
  • Leave all other settings at their defaults.
  • Click Create function.
  • In the Code tab, click on lambda_function.py in the code editor.
  • Select all the existing code and delete it.
  • Paste in the following fulfillment code:
import json

def lambda_handler(event, context):
    # Log the full SQS event for debugging
    print("Received SQS messages:")
    print(json.dumps(event, indent=2))

    # Process each message from the SQS queue
    for record in event["Records"]:
        body = json.loads(record["body"])
        print(f"Fulfilling order from SQS message:")
        print(json.dumps(body, indent=2))

        # Extract order details from the message body
        order_id = body.get("detail", {}).get("order_id", "unknown")
        amount = body.get("detail", {}).get("amount", 0)

        print(f"Order {order_id} (${amount}) sent to fulfillment pipeline")

    return {
        "statusCode": 200,
        "body": json.dumps({"message": "Fulfillment processing complete"})
    }

What does this code do?

  • event["Records"] contains the batch of messages that Lambda pulled from the SQS queue. Lambda polls the queue automatically once you connect them.
  • json.loads(record["body"]) parses each SQS message body from a JSON string back into a dictionary.
  • The function extracts order_id and amount from the parsed body and simulates sending the order to a fulfillment pipeline.

Why two different patterns?

The order-processor function uses the push pattern. EventBridge invokes it directly and immediately when an event matches. This is great for real-time processing.

The order-fulfillment function uses the pull pattern. Events land in an SQS queue first, and Lambda polls the queue on a schedule. This adds a buffer. If the function fails, the message stays in the queue for retry instead of being lost.

  • Click Deploy in the DEPLOY section above the code editor.

You should see a green banner confirming the function was updated successfully.

  • Click Functions in the left sidebar to verify both functions appear in the list.

You should see both order-processor and order-fulfillment listed on the Functions page.

Only seeing one function?

Make sure you clicked Create function for the second function and did not accidentally edit the first one. Check the function name at the top of the page to confirm which function you are viewing.

help me find my missing Lambda function in the console

✔️ Awesome, I've got everything!

Great. Double check you clicked Deploy on both functions before moving on.

ⓧ I'd like to double check the full code

Here is the complete code for each Lambda function. Compare yours against these.

order-processor function:

import json

def lambda_handler(event, context):
    print("Received order event:")
    print(json.dumps(event, indent=2))

    order_id = event.get("detail", {}).get("order_id", "unknown")
    amount = event.get("detail", {}).get("amount", 0)
    customer = event.get("detail", {}).get("customer_name", "unknown")

    print(f"Processing order {order_id} for {customer}, amount: ${amount}")

    return {
        "statusCode": 200,
        "body": json.dumps({
            "message": f"Order {order_id} processed successfully",
            "order_id": order_id,
            "amount": amount
        })
    }

order-fulfillment function:

import json

def lambda_handler(event, context):
    print("Received SQS messages:")
    print(json.dumps(event, indent=2))

    for record in event["Records"]:
        body = json.loads(record["body"])
        print(f"Fulfilling order from SQS message:")
        print(json.dumps(body, indent=2))

        order_id = body.get("detail", {}).get("order_id", "unknown")
        amount = body.get("detail", {}).get("amount", 0)

        print(f"Order {order_id} (${amount}) sent to fulfillment pipeline")

    return {
        "statusCode": 200,
        "body": json.dumps({"message": "Fulfillment processing complete"})
    }

Your event bus and Lambda functions are ready. Next up, you will create the SQS queues and SNS topic that complete the messaging layer of your event-driven system.

Create the SQS Queues and SNS Topic

Your Lambda functions are ready to process orders, but they need messaging infrastructure to receive events from.

In this step, you'll create the queues and notification topic that sit between EventBridge and your Lambda consumers. You'll build a main processing queue, a dead-letter queue for catching failures, and an SNS topic for sending email alerts.

In this step, get ready to:
  • Create a dead-letter queue to capture failed messages.
  • Create a processing queue with the dead-letter queue attached.
  • Create an SNS topic with an email subscription for VIP notifications.
Create the dead-letter queue

A dead-letter queue (DLQ) is a holding area for messages that fail processing. You'll create this first because the main processing queue needs to reference it.

  • Open the SQS console at https://console.aws.amazon.com/sqs/.
  • Click Create queue.
  • For Type, leave Standard selected (this is the default).
  • For Name, enter orders-dlq.
  • Leave all other settings at their defaults.
  • Scroll to the bottom and click Create queue.

What is a dead-letter queue?

When a message fails processing too many times, SQS automatically moves it to the dead-letter queue instead of discarding it. This gives you a safe place to inspect failed messages and debug what went wrong.

Think of it like a "lost and found" for messages that couldn't be delivered successfully.

Create the main processing queue with the DLQ attached

Now you'll create the queue that actually receives high-value order events from EventBridge. This queue will forward failed messages to the DLQ you just created.

  • In the SQS console, click Create queue again.
  • For Type, leave Standard selected.
  • For Name, enter orders-processing-queue.
  • Scroll down to the Dead-letter queue section and toggle it to Enabled.
  • Under Dead-letter queue settings, select orders-dlq as the queue ARN.
  • Set Maximum receives to 3.
  • Click Create queue.

Why set Maximum receives to 3?

Maximum receives defines how many times a message can be delivered to a consumer before SQS moves it to the dead-letter queue. After 3 failed processing attempts, the message lands in orders-dlq where you can inspect it.

AWS recommends setting this to at least 5 for production Lambda-SQS integrations. We're using 3 here so you can see DLQ behavior faster during testing.

Why configure the DLQ on the queue, not on Lambda?

AWS recommends configuring a dead-letter queue on the SQS queue itself, not on the Lambda function. This ensures that messages that fail repeatedly are captured regardless of the failure type.

Don't see orders-dlq in the dropdown?

SQS is a distributed system, so new queues can take a moment to propagate. Wait a few seconds and refresh the page. If it still doesn't appear, confirm the DLQ was created successfully by checking the Queues list in the left navigation.

Why can't I find my dead-letter queue in the dropdown?

Create the SNS topic for VIP notifications

When a high-value order comes in, you want to be notified by email. SNS (Simple Notification Service) handles this fan-out notification pattern.

  • Open the Amazon SNS console by typing SNS in the AWS search bar at the top and selecting Simple Notification Service.
  • In the left navigation pane, choose Topics.
  • Click Create topic.
  • For Type, select Standard.
  • For Name, enter high-value-order-alerts.
  • Leave all other settings at their defaults and click Create topic.

You'll land on the topic's detail page. Now create an email subscription so you receive notifications.

  • On the high-value-order-alerts topic detail page, click Create subscription.
  • For Protocol, choose Email.
  • For Endpoint, enter your email address.
  • Click Create subscription.

Confirm your email subscription now

Check your email inbox for a message from AWS Notifications. Open it and click the Confirm subscription link.

You will not receive any email alerts from this topic until you confirm the subscription. Check your spam folder if you don't see the email within a minute.

After confirming, return to the SNS console. Your subscription status should show Confirmed.

Subscription still showing Pending confirmation?

Check your spam or junk folder for the confirmation email. The subject line includes the topic name. If you still can't find it, delete the subscription and create a new one with a different email address.

Help me troubleshoot my SNS email subscription

Your messaging infrastructure is set up. You have a processing queue that catches failures in a DLQ, and an SNS topic ready to email you about high-value orders. Next up, you'll create EventBridge rules that route events to these targets based on order content.

Create EventBridge Rules for Content-Based Routing

You have an event bus, two Lambda functions, an SQS queue, and an SNS topic. Now you need to tell EventBridge how to decide which events go where.

In this step, you will create three rules on your custom event bus. Each rule uses an event pattern to match incoming events and route them to specific targets. One rule catches all orders. The other two use numeric filtering to pick out only high-value orders above $100.

In this step, get ready to:
  • Create a rule that routes all order events to the processor Lambda.
  • Create a rule with numeric filtering that sends high-value orders to SQS.
  • Create a rule that notifies you via SNS when a high-value order arrives.
Create a rule to route all orders to the processor Lambda

The first rule will match every order event published to your custom bus and send it directly to the order-processor Lambda. This is the push invocation pattern where EventBridge invokes the Lambda function immediately.

  • Return to the EventBridge console at https://console.aws.amazon.com/events/.
  • In the navigation pane, choose Rules.
  • Choose Create rule.
  • On the rule builder selection page, choose Advanced Builder.
  • For Name, enter all-orders-to-processor.
  • For Event bus, select orders-event-bus from the dropdown.
  • For Rule type, choose Rule with an event pattern.
  • Choose Next.

Now you will define the event pattern that tells EventBridge which events this rule cares about.

  • For Event source, choose Other.
  • For Creation method, select Custom pattern (JSON editor).
  • In the Event pattern text area, paste the following JSON:
{
  "source": ["com.ecommerce.orders"],
  "detail-type": ["Order Placed"]
}

What does this pattern do?

This pattern matches any event where the source field equals com.ecommerce.orders and the detail-type field equals Order Placed.

You only specify the fields you want to match. EventBridge ignores all other fields in the event, so every order event that hits the bus will trigger this rule regardless of the amount, customer, or items.

  • Choose Next.
  • For Target types, choose AWS service.
  • For Select a target, choose Lambda function from the dropdown.
  • For Function, select order-processor.
  • Choose Next.
  • Choose Next again to skip tags.
  • Review the details, then choose Create rule.

What is content-based routing?

Content-based routing means EventBridge inspects the content of each event and delivers it only to the targets whose rules match. Producers never need to know which consumers exist. You can add new consumers later by adding new rules without changing the producer.

Don't see your custom event bus in the dropdown?

Make sure you are in the same AWS Region where you created orders-event-bus. Check the Region selector in the top-right corner of the console.

help me find my custom event bus in the EventBridge console

Create a rule to route high-value orders to SQS

The second rule uses a numeric comparison operator to filter events by amount. Only orders over $100 will reach the SQS queue, where the order-fulfillment Lambda will eventually pull them.

  • In the navigation pane, choose Rules.
  • Choose Create rule.
  • Choose Advanced Builder.
  • For Name, enter high-value-orders-to-sqs.
  • For Event bus, select orders-event-bus.
  • For Rule type, choose Rule with an event pattern.
  • Choose Next.
  • For Event source, choose Other.
  • For Creation method, select Custom pattern (JSON editor).
  • In the Event pattern text area, paste the following JSON:
{
  "source": ["com.ecommerce.orders"],
  "detail-type": ["Order Placed"],
  "detail": {
    "amount": [{ "numeric": [">", 100] }]
  }
}

What does the numeric filter do?

The numeric operator tells EventBridge to compare the detail.amount field as a number. The pattern [">", 100] means "match when the amount is greater than 100."

This is what makes EventBridge powerful for content-based routing. You can filter on exact values, numeric ranges, prefixes, and more without writing any code in your consumers.

  • Choose Next.
  • For Target types, choose AWS service.
  • For Select a target, choose SQS queue from the dropdown.
  • For Queue, select orders-processing-queue.
  • Choose Next.
  • Choose Next again to skip tags.
  • Review the details, then choose Create rule.

Why didn't I need to set up permissions?

When you add an SQS target via the console in the same account, EventBridge automatically attaches a resource-based policy granting itself permission to send messages to the queue. No manual policy setup is needed for same-account console workflows.

Create a rule to send high-value order notifications via SNS

The third rule demonstrates a fan-out pattern. The same high-value order event triggers both the SQS rule and this SNS rule in parallel. This sends you an email alert every time a big order comes in.

  • In the navigation pane, choose Rules.
  • Choose Create rule.
  • Choose Advanced Builder.
  • For Name, enter high-value-orders-to-sns.
  • For Event bus, select orders-event-bus.
  • For Rule type, choose Rule with an event pattern.
  • Choose Next.
  • For Event source, choose Other.
  • For Creation method, select Custom pattern (JSON editor).
  • In the Event pattern text area, paste the same numeric filter pattern you used for the SQS rule:
{
  "source": ["com.ecommerce.orders"],
  "detail-type": ["Order Placed"],
  "detail": {
    "amount": [{ "numeric": [">", 100] }]
  }
}
  • Choose Next.
  • For Target types, choose AWS service.
  • For Select a target, choose SNS topic from the dropdown.
  • For Topic, select high-value-order-alerts.
  • Choose Next.
  • Choose Next again to skip tags.
  • Review the details, then choose Create rule.

You now have three rules active on your custom event bus. Verify by checking the Rules page with orders-event-bus selected in the Event bus filter. You should see all three rules listed with a status of Enabled.

How does fan-out work here?

When a high-value order event arrives on the bus, EventBridge evaluates all three rules independently. The event matches all-orders-to-processor (because it is an order event), high-value-orders-to-sqs (because the amount exceeds 100), and high-value-orders-to-sns (same numeric filter).

All three targets receive the event in parallel. This is the fan-out pattern. You added two new consumers without changing the event producer at all.

Rule shows as Disabled?

EventBridge rules are enabled by default when created via the console. If a rule shows as disabled, select it, choose Enable, and confirm.

help me enable my EventBridge rule

Your routing layer is complete. Next up, you will connect the SQS queue to the fulfillment Lambda and publish real events to see the entire system in action.

Connect SQS to Lambda and Publish Test Events

Your event bus and routing rules are ready. Now it's time to wire up the last connection and put the entire system to the test.

Right now, the order-fulfillment Lambda has no way to read from the SQS queue. You need to grant it permission and create an event source mapping. Once that's done, you'll publish real events through the AWS CLI and watch them flow through the system.

In this step, get ready to:
  • Connect the SQS queue to the Order Fulfillment Lambda function.
  • Publish a standard order event and confirm it only reaches the all-orders consumer.
  • Publish a high-value order event and confirm it triggers all three rules.
Connect the SQS queue to the Order Fulfillment Lambda

The order-fulfillment function needs two things before it can process messages from SQS: IAM permissions to read the queue, and a trigger that tells Lambda to poll it.

  • Return to the Lambda console and click the order-fulfillment function.
  • Choose the Configuration tab, then choose Permissions.
  • Under Role name, click the link to your execution role. This opens the role in the IAM console.
  • Choose Add permissions, then choose Attach policies.
  • In the search field, enter AWSLambdaSQSQueueExecutionRole.
  • Select the checkbox next to the policy, then choose Add permissions.

What does this policy grant?

The AWSLambdaSQSQueueExecutionRole managed policy grants three permissions: sqs:ReceiveMessage, sqs:DeleteMessage, and sqs:GetQueueAttributes. These let Lambda poll the queue, process messages, and delete them after successful processing.

  • Go back to your order-fulfillment function in the Lambda console.
  • Under Function overview, choose Add trigger.
  • In the trigger configuration dropdown, select SQS.
  • For SQS queue, select orders-processing-queue.
  • Leave all other settings at their defaults (Enable trigger should already be selected).
  • Choose Add.

The SQS trigger should now appear in the Function overview diagram. It may take about a minute for the status to show as enabled.

Push vs pull in action

The order-processor function uses the push pattern. EventBridge invokes it directly whenever a matching event arrives.

The order-fulfillment function uses the pull pattern. Lambda polls the SQS queue and invokes the function when messages are available. This decouples the producer from the consumer and adds a buffer for traffic spikes.

Don't see the trigger as enabled?

If the trigger shows a failed state, confirm that the AWSLambdaSQSQueueExecutionRole policy is attached to the correct execution role. Open the Configuration > Permissions tab and verify the policy appears under the role.

Also make sure the Lambda function and the SQS queue are in the same AWS Region.

help me troubleshoot why my SQS trigger is failing on my order-fulfillment Lambda function

Publish a standard order event via the AWS CLI

Time to send your first event. You'll start with a standard order under $100 to confirm it only reaches the all-orders rule and does NOT trigger the high-value rules.

  • Open a terminal on your local machine.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create the standard order event file by running this command:
cat > order-event-standard.json << 'EOF'
[
  {
    "Source": "com.ecommerce.orders",
    "DetailType": "Order Placed",
    "Detail": "{ \"order_id\": \"ORD-1002\", \"customer_name\": \"John Doe\", \"amount\": 50, \"items\": [\"Basic Widget\"], \"shipping_address\": \"456 Oak Ave\" }",
    "EventBusName": "orders-event-bus"
  }
]
EOF

What does this event file contain?

This JSON follows the structure required by the PutEvents API. The Source, DetailType, and Detail fields are required for EventBridge to route the event. The EventBusName tells EventBridge to send this event to your custom bus instead of the default one.

Notice the amount is 50. This is below the $100 threshold in your numeric filter rules, so it should only match the all-orders rule.

  • Publish the event to your custom event bus by running this command:
aws events put-events --entries file://order-event-standard.json

You should see a response showing zero failed entries, which means EventBridge accepted the event.

  • Verify the order-processor Lambda received the event by checking CloudWatch Logs. In the Lambda console, open the order-processor function.
  • Choose the Monitor tab, then choose View CloudWatch logs.
  • Click on the most recent log stream.

You should see log output containing the order details for ORD-1002 with amount $50.

  • Now verify that the SQS queue did NOT receive the message. Return to the SQS console, open orders-processing-queue, and check that Messages available is still 0.

Why didn't SQS receive it?

Your high-value-orders-to-sqs rule uses a numeric filter that only matches when detail.amount is greater than 100. Since this order was $50, EventBridge evaluated the pattern and skipped that rule entirely. This is content-based routing in action.

Not seeing any CloudWatch logs?

Make sure you published to orders-event-bus (not the default bus). Check that your JSON file has "EventBusName": "orders-event-bus" included.

Also confirm your AWS CLI is configured for the same region where you created the event bus.

help me debug why my order-processor Lambda is not receiving events from EventBridge

Publish a high-value order event

Now for the real test. You'll send an order worth $250 and confirm it triggers all three rules: the all-orders Lambda, the SQS queue (which invokes the fulfillment Lambda), and the SNS email notification.

  • Create the high-value order event file by running this command:
cat > order-event.json << 'EOF'
[
  {
    "Source": "com.ecommerce.orders",
    "DetailType": "Order Placed",
    "Detail": "{ \"order_id\": \"ORD-1001\", \"customer_name\": \"Jane Smith\", \"amount\": 250, \"items\": [\"Widget Pro\", \"Gadget Max\"], \"shipping_address\": \"123 Main St\" }",
    "EventBusName": "orders-event-bus"
  }
]
EOF

What's different about this event?

The amount is now 250, which exceeds the $100 threshold. All three of your EventBridge rules should match this event: the all-orders rule (matches all events), the high-value-to-SQS rule (amount > 100), and the high-value-to-SNS rule (amount > 100).

  • Publish the high-value event by running this command:
aws events put-events --entries file://order-event.json
  • Check CloudWatch Logs for the order-processor function. You should see a new log entry for order ORD-1001 with amount $250.
  • Check CloudWatch Logs for the order-fulfillment function. You should see a log entry showing the SQS message was received and processed.
  • Check your email inbox. You should have received an SNS notification from the high-value-order-alerts topic containing the event details.

What just happened?

A single event published to your custom bus triggered three independent consumers simultaneously. EventBridge evaluated all three rules against the event, and because the amount exceeded $100, all three matched. This fan-out pattern is a core benefit of event-driven architecture.

The standard $50 order only triggered one rule. The high-value $250 order triggered all three. You did not change any code to achieve this. The routing logic lives entirely in the EventBridge rules.

Order Fulfillment logs not showing up?

The SQS-to-Lambda connection uses polling, so there may be a short delay (a few seconds) before the function is invoked. Wait 10-15 seconds and refresh CloudWatch Logs.

If logs still don't appear, confirm the SQS trigger is enabled on the order-fulfillment function and that the AWSLambdaSQSQueueExecutionRole policy is attached to the execution role.

help me figure out why my order-fulfillment Lambda is not processing messages from the orders-processing-queue

Your event-driven system is fully operational. Next up, you'll test what happens when things go wrong and see how the dead-letter queue catches failed messages.

Test the Dead-Letter Queue

Your event-driven system now routes orders to the right consumers based on their content. But what happens when a consumer fails to process a message?

In production, downstream services go down all the time. A database might be unreachable, an API might time out, or a service might crash mid-processing. Without a safety net, those failed messages disappear forever. In this step, you will intentionally break the order-fulfillment Lambda to see how your dead-letter queue captures failed messages for debugging and reprocessing.

In this step, get ready to:
  • Simulate a processing failure by breaking the fulfillment Lambda.
  • Inspect the dead-letter queue to find the failed message.
  • Fix the Lambda and understand the DLQ redrive lifecycle.
Simulate a processing failure

To test the dead-letter queue, you need a message that consistently fails. You will modify the order-fulfillment Lambda to throw an error every time it processes a message.

  • Return to the Lambda console and open the order-fulfillment function.
  • In the Code source editor, replace the entire function code with this broken version:
import json

def lambda_handler(event, context):
    print("Received SQS messages:")
    print(json.dumps(event, indent=2))

    for record in event["Records"]:
        body = json.loads(record["body"])
        print(f"Fulfilling order from SQS message:")
        print(json.dumps(body, indent=2))

        # Simulate a downstream failure
        raise Exception("Fulfillment service unavailable - simulated failure")

    return {
        "statusCode": 200,
        "body": json.dumps({"message": "Fulfillment processing complete"})
    }

What does this code do?

The raise Exception(...) line forces the Lambda function to crash every time it receives a message from SQS. This simulates a real-world scenario where a downstream service is unavailable.

When the Lambda fails, SQS does not delete the message. Instead, the message becomes visible again after the visibility timeout expires (30 seconds by default). Lambda will pick it up and try again.

  • Click Deploy to save the updated function.

Now publish a high-value order event to trigger the broken function.

  • Switch back to your terminal and publish the high-value order event by running:
aws events put-events --entries file://order-event.json

What happens next?

EventBridge routes the $250 order to orders-processing-queue via the high-value orders rule. Lambda picks up the message, tries to process it, and fails. The message returns to the queue after 30 seconds, and Lambda tries again.

After 3 failed attempts (the maximum receives you configured), SQS automatically moves the message to orders-dlq. This takes roughly 1-2 minutes to complete all retry cycles.

  • Wait approximately 2 minutes for the message to cycle through all 3 retry attempts.

Check CloudWatch Logs while you wait

Open CloudWatch Logs for the order-fulfillment function. You should see multiple log entries showing the function receiving the message and then failing with the simulated error. Each log entry corresponds to one retry attempt.

Inspect the dead-letter queue

After the retries exhaust, the message lands in your dead-letter queue. This is where you go to investigate what went wrong in production.

  • Open the SQS console at https://console.aws.amazon.com/sqs/.
  • Select the orders-dlq queue from the list.
  • Click Send and receive messages in the upper right.
  • Scroll down to the Receive messages section and click Poll for messages.

You should see at least one message appear in the Messages list below.

  • Click on the message ID to expand its details.
  • Examine the Body field. You should see the full EventBridge event envelope containing your original order data, including the order_id, customer_name, and amount fields.

Why does this matter in production?

The DLQ preserves the exact message that caused the failure. In a real system, an engineer would inspect these messages to understand what payload triggered the bug. The message body tells you exactly which order failed, so you can fix the issue and reprocess it.

Don't see any messages in the DLQ?

The retry cycle takes time. The default visibility timeout is 30 seconds, so 3 retries takes at least 90 seconds. Wait a full 2 minutes and poll again.

If the DLQ is still empty after several minutes, confirm that the order-fulfillment Lambda was deployed with the broken code. Check CloudWatch Logs for the function to see if errors are appearing.

Help me figure out why my SQS dead-letter queue is not receiving failed messages.

Fix and reprocess

In a real incident, you would fix the bug and then reprocess the failed messages. The fix-and-redrive cycle is the complete error handling pattern.

  • Return to the Lambda console and open the order-fulfillment function.
  • Replace the broken code with the original working version:
import json

def lambda_handler(event, context):
    print("Received SQS messages:")
    print(json.dumps(event, indent=2))

    for record in event["Records"]:
        body = json.loads(record["body"])
        print(f"Fulfilling order from SQS message:")
        print(json.dumps(body, indent=2))

        order_id = body.get("detail", {}).get("order_id", "unknown")
        amount = body.get("detail", {}).get("amount", 0)

        print(f"Order {order_id} (${amount}) sent to fulfillment pipeline")

    return {
        "statusCode": 200,
        "body": json.dumps({"message": "Fulfillment processing complete"})
    }

What does this code do?

This is the original working function that processes SQS messages normally. It extracts the order_id and amount from the message body and logs a success message. When Lambda returns successfully, SQS automatically deletes the message from the queue.

  • Click Deploy to save the reverted function.

What is DLQ redrive?

In production, after fixing the bug you would use SQS DLQ redrive to move messages from the dead-letter queue back to the original source queue for reprocessing. This replays the failed messages through the now-fixed consumer without manually republishing events.

The complete lifecycle is: message fails repeatedly, moves to DLQ, engineer inspects and fixes the bug, then redrives messages back to the source queue. This pattern ensures zero message loss even during outages.

Your function is back to its working state. The failed message remains in orders-dlq for inspection, and in a production scenario you would redrive it back to orders-processing-queue for successful reprocessing.

✔️ Awesome, I've got everything!

Great. Double check you have deployed the working version of order-fulfillment and confirmed the failed message in your DLQ.

ⓧ I'd like to double check the full code

Here is the final working version of your order-fulfillment Lambda function:

import json

def lambda_handler(event, context):
    print("Received SQS messages:")
    print(json.dumps(event, indent=2))

    for record in event["Records"]:
        body = json.loads(record["body"])
        print(f"Fulfilling order from SQS message:")
        print(json.dumps(body, indent=2))

        order_id = body.get("detail", {}).get("order_id", "unknown")
        amount = body.get("detail", {}).get("amount", 0)

        print(f"Order {order_id} (${amount}) sent to fulfillment pipeline")

    return {
        "statusCode": 200,
        "body": json.dumps({"message": "Fulfillment processing complete"})
    }

You have just proven that your event-driven system handles failures gracefully. Messages never disappear. They flow through retries, land in the DLQ for inspection, and can be redriven once the issue is resolved.

Secret mission

Add EventBridge Input Transformation

Right now, your order-fulfillment Lambda receives the entire EventBridge event envelope just to extract a few fields. In production, this creates unnecessary parsing logic in every consumer. Can you use EventBridge Input Transformers to reshape the payload before it even reaches the SQS queue?

Clean Up Your Resources

Clean Up Your Resources

All resources in this project fall within the AWS Always Free tier. There are no ongoing costs for keeping them in your account.

Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • Custom EventBridge event bus (orders-event-bus).
  • 3 EventBridge rules (all-orders-to-processor, high-value-orders-to-sqs, high-value-orders-to-sns).
  • 2 Lambda functions (order-processor, order-fulfillment).
  • 2 SQS queues (orders-processing-queue, orders-dlq).
  • 1 SNS topic with email subscription (high-value-order-alerts).
  • 2 local JSON files (order-event.json, order-event-standard.json).

Keep everything running

No action needed. Choose this if you want to continue experimenting with event-driven patterns or testing new rules.

  • All resources stay within the AWS Always Free tier at $0/month.
  • Lambda free tier includes 1,000,000 requests per month.
  • SQS free tier includes 1,000,000 requests per month.
  • SNS free tier includes 1,000,000 publishes per month.
  • Your local JSON files remain on your machine.

Pause - I'll come back to this later

Disable the EventBridge rules so no events get processed. Your resources stay in place but nothing runs until you re-enable them.

  • Open the EventBridge console at https://console.aws.amazon.com/events/.
  • In the navigation pane, choose Rules.
  • Under Event bus, select orders-event-bus.
  • Select the button next to each rule and choose Disable, then Disable to confirm.
  • Repeat for all three rules.

Delete - I don't want to use this again

Remove all project resources completely. Follow these steps in order.

Delete the EventBridge rules:

  • Open the EventBridge console at https://console.aws.amazon.com/events/.
  • In the navigation pane, choose Rules.
  • Under Event bus, select orders-event-bus.
  • Select the button next to all-orders-to-processor and choose Actions, Delete, Delete.
  • Repeat for high-value-orders-to-sqs and high-value-orders-to-sns.

Delete the custom event bus:

  • In the navigation pane, choose Event buses.
  • Select orders-event-bus.
  • Choose Delete and confirm the deletion.

Delete the Lambda functions:

  • Open the Functions page of the Lambda console.
  • Select order-processor.
  • Choose Actions, Delete.
  • Type confirm in the text input field and choose Delete.
  • Repeat for order-fulfillment.

Delete the SQS queues:

  • Open the SQS console at https://console.aws.amazon.com/sqs/.
  • Select orders-processing-queue.
  • Choose Delete and confirm by typing the queue name.
  • Repeat for orders-dlq.

Delete the SNS topic:

  • Open the SNS console and navigate to Topics.
  • Select high-value-order-alerts.
  • Choose Delete and type delete me to confirm.

Delete the local JSON files:

  • In your terminal, delete the event files by running:
rm order-event.json order-event-standard.json
  • Confirm the files are removed by running ls in the same directory.

Nice Work!

Nice Work!

You just built a production-grade event-driven order processing system on AWS. Your architecture routes events based on content, handles failures gracefully, and demonstrates the exact patterns used in real microservices at scale.

You've learned how to:

  • Build content-based routing rules on a custom EventBridge event bus using numeric comparison operators to filter high-value orders.
  • Implement both push (EventBridge direct to Lambda) and pull (SQS to Lambda) invocation patterns side by side to understand the tradeoffs of each.
  • Configure a dead-letter queue to capture failed messages and inspect them for debugging, giving your system fault tolerance.
  • Secret Mission: Added input transformations to reshape event payloads before delivery, reducing parsing logic in downstream consumers.

Ready to quiz yourself?