AWS Builder Center

Build an Event-Driven Workflow with Amazon SQS and AWS Lambda

Learn how to decouple application components using Amazon SQS and AWS Lambda. In this hands-on tutorial, you'll build a message-processing workflow, test it from the AWS CLI, and explore how to handle failures and retries.

Why use an event-driven architecture?

Imagine you're building an application that processes customer orders. When a customer places an order, your application might need to send a confirmation email, update inventory, and generate a receipt.
You could perform all these operations in the original API request. However, if the email service is slow or the inventory system is unavailable, the entire request could be delayed or fail.
An event-driven architecture helps solve this problem by separating the act of accepting work from the act of processing it.
In this tutorial, we'll use two AWS services:
  • Amazon Simple Queue Service (SQS): Stores messages until they can be processed.
  • AWS Lambda: Runs code to process messages without requiring you to manage servers.
The result is a small, serverless workflow that can absorb bursts of incoming work and retry processing when something goes wrong.

1. Understand the architecture

Here's what we're building:
1
2
3
4
5
6
7
8
9
10
11
12
13
Application
|
| Send message
v
Amazon SQS Queue
|
| Lambda polls for messages
v
AWS Lambda Function
|
| Process order
v
Amazon CloudWatch Logs
The important detail is that your application doesn't need to call Lambda directly. It sends a message to SQS and can continue with other work.
Lambda polls the queue through an event source mapping and processes messages as they become available. Messages are delivered in batches, so one invocation can process multiple orders.

2. Create an SQS queue

For this example, we'll use the AWS CLI and a standard SQS queue.
First, make sure the AWS CLI is installed and configured with credentials that have the necessary permissions.
Create the queue:
1
2
aws sqs create-queue \
--queue-name order-processing-queue
Retrieve the queue URL:
1
2
3
4
5
6
QUEUE_URL=$(aws sqs get-queue-url \
--queue-name order-processing-queue \
--query 'QueueUrl' \
--output text)

echo "$QUEUE_URL"
Retrieve its ARN, which we'll need when connecting the queue to Lambda:
1
2
3
4
5
6
7
QUEUE_ARN=$(aws sqs get-queue-attributes \
--queue-url "$QUEUE_URL" \
--attribute-names QueueArn \
--query 'Attributes.QueueArn' \
--output text)

echo "$QUEUE_ARN"
Keep these values available in your terminal.

3. Create the Lambda function

We'll write a small Python function that receives order messages and logs their details.
Create a file named lambda_function.py:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import json
import logging

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
for record in event["Records"]:
order = json.loads(record["body"])

order_id = order["orderId"]
customer_id = order["customerId"]
total = order["total"]

logger.info(
"Processing order %s for customer %s. Total: %.2f",
order_id,
customer_id,
total,
)

return {"statusCode": 200}
This function expects each SQS message body to contain a JSON object with an order ID, customer ID, and total.
For example:
1
2
3
4
5
{
"orderId": "ORD-1001",
"customerId": "CUST-42",
"total": 89.99
}
For this demonstration, processing means logging the order. A production application might instead call a payment service, update a database, or publish another event.
Notice that the function iterates over event["Records"]. Lambda can deliver several messages in one invocation, so the event is not necessarily a single order.

4. Configure Lambda permissions

Lambda needs permission to read messages from SQS and write logs to CloudWatch.
In the AWS Console:
  1. Open IAM → Roles.
  2. Create a role trusted by the Lambda service.
  3. Attach the AWS-managed policy AWSLambdaSQSQueueExecutionRole.
  4. Save the role ARN for the next step.
This policy provides the standard SQS polling and CloudWatch Logs permissions required for this example. In production, prefer least-privilege permissions scoped to the resources the function actually uses.

5. Deploy the function

Package the Python file:
1
zip function.zip lambda_function.py
Set your role ARN to the IAM role you created:
1
ROLE_ARN="arn:aws:iam::YOUR_ACCOUNT_ID:role/lambda-sqs-role"
Replace the account ID with your actual AWS account ID.
Create the Lambda function:
1
2
3
4
5
6
7
aws lambda create-function \
--function-name ProcessOrders \
--runtime python3.12 \
--handler lambda_function.lambda_handler \
--role "$ROLE_ARN" \
--zip-file fileb://function.zip \
--timeout 30
If your account or region doesn't support that runtime, choose a currently supported Python runtime instead.

6. Connect SQS to Lambda

Now we need to tell Lambda to consume messages from our queue.
Create an event source mapping:
1
2
3
4
5
aws lambda create-event-source-mapping \
--function-name ProcessOrders \
--event-source-arn "$QUEUE_ARN" \
--batch-size 1 \
--enabled
We're using a batch size of one to make the initial test easy to understand. You can increase it later to process multiple messages per invocation.
What's happening behind the scenes?
Lambda manages polling for messages through the event source mapping. When messages arrive, Lambda invokes your function with an event containing the messages. When processing succeeds, Lambda removes the messages from the queue.
Keep the queue and Lambda function in the same AWS Region.

7. Send a test order

Let's simulate an application submitting an order.
Run:
1
2
3
4
5
6
7
aws sqs send-message \
--queue-url "$QUEUE_URL" \
--message-body '{
"orderId": "ORD-1001",
"customerId": "CUST-42",
"total": 89.99
}'
You should receive a response containing a message ID.
You don't need to invoke Lambda manually. The event source mapping will poll SQS and trigger the function automatically.
To verify that the message was processed, inspect the function's logs:
1
2
aws logs tail /aws/lambda/ProcessOrders \
--since 10m
You should see a log entry similar to:
1
Processing order ORD-1001 for customer CUST-42. Total: 89.99
If the log doesn't appear immediately, give the event source mapping a little time to poll the queue and invoke the function.

8. What happens when processing fails?

Distributed systems need to account for failures. A Lambda function might time out, a downstream API might become unavailable, or a database might reject a write.
With SQS and Lambda, a failed invocation normally causes the messages in that batch to become available again after the queue's visibility timeout. Lambda can retry processing them.
However, this introduces an important consideration: SQS-triggered Lambda processing is at least once, not exactly once. A message can be processed more than once.
For example, your function might successfully charge a payment but fail before completing its invocation. The message could be retried, potentially charging the customer twice if the payment operation isn't protected.
For production workflows:
  • Make processing idempotent so duplicate messages don't repeat irreversible actions.
  • Configure a dead-letter queue (DLQ) for messages that repeatedly fail.
  • Set the source queue's visibility timeout to at least six times the Lambda function timeout.
  • Use partial batch responses when processing multiple messages so a single failed record doesn't unnecessarily cause successful records to be retried.

9. When should you use this pattern?

SQS and Lambda work well together when an application needs to process work asynchronously, isolate failures, or handle variable traffic.
Common examples include:
  • Processing uploaded files.
  • Sending notifications and emails.
  • Handling order events.
  • Running background data transformations.
  • Integrating services without tightly coupling them.
This pattern isn't a replacement for every synchronous API call. If a client needs an immediate result, you may still need a synchronous endpoint. But when work can happen in the background, queues can make systems more resilient and easier to scale.

10. Clean up your AWS resources

When you're finished experimenting, remove the resources to avoid unnecessary charges.
First, disable and delete the event source mapping. Retrieve its UUID:
1
2
aws lambda list-event-source-mappings \
--function-name ProcessOrders
Then delete the mapping using its UUID:
1
2
aws lambda delete-event-source-mapping \
--uuid YOUR_EVENT_SOURCE_MAPPING_UUID
Delete the Lambda function:
1
2
aws lambda delete-function \
--function-name ProcessOrders
Delete the SQS queue:
1
2
aws sqs delete-queue \
--queue-url "$QUEUE_URL"
You can also delete the IAM role if you created it solely for this tutorial and no longer need it. Remove any CloudWatch log groups you created if you want to clean up the remaining logging resources.
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article