Technical
Event-Driven Architecture With SQS and Lambda
Synchronous APIs work until they do not. A user signs up, the API calls the email service, the email service is slow, the user sees a spinning wheel, the user leaves. The fix is event-driven architecture: queue the work, respond immediately, process asynchronously. Here is how I build that on AWS.
The Pattern
The API receives the request, writes an event to SQS, and returns a 202 Accepted. A Lambda function processes events from the queue in the background. The user never waits for the slow step.
Client -> API Gateway -> Lambda (fast) -> SQS -> Lambda (slow work)The user sees a response in under 100ms regardless of how slow the background work is.
A Concrete Example
Newsletter signup. The fast path writes the subscriber to DynamoDB and returns. The slow path sends the welcome email, adds to the CRM, and notifies Slack. Here is the Lambda that handles the fast path:
import boto3
import json
sqs = boto3.client('sqs')
QUEUE_URL = 'https://sqs.us-east-1.amazonaws.com/.../welcome-queue'
def handler(event, context):
email = event['body']['email']
save_subscriber(email)
sqs.send_message(
QueueUrl=QUEUE_URL,
MessageBody=json.dumps({'email': email})
)
return {'statusCode': 202, 'body': 'Accepted'}The DynamoDB write is fast. The SQS send is fast. The Lambda returns in ~50ms.
The Background Worker
A second Lambda is configured with an SQS trigger. Every message in the queue invokes it:
def welcome_handler(event, context):
for record in event['Records']:
body = json.loads(record['body'])
send_welcome_email(body['email'])
sync_to_crm(body['email'])
notify_slack(body['email'])If any step fails, the message returns to the queue and retries automatically. SQS handles retry logic for free.
Why SQS Over Direct Async
You might wonder: why not just have the fast Lambda call the slow one asynchronously? Three reasons:
- Durability: SQS messages survive Lambda failures
- Decoupling: the slow Lambda can be deployed independently
- Rate limiting: SQS controls concurrent workers, protecting downstream services
A direct async call has none of these guarantees.
Dead Letter Queues
If a message fails too many times, SQS moves it to a Dead Letter Queue. I get a CloudWatch alarm and investigate. The main queue keeps flowing. One poison message does not crash the system.
Cost Math
SQS: first million requests per month free, then $0.40 per million. For a small SaaS, the SQS bill is zero.
Lambda: invocations from SQS count toward the Lambda free tier. No separate billing.
The whole pattern is effectively free until you hit real scale.
When to Keep It Synchronous
If the user needs the result to continue their flow (showing the result of a payment, for example), you cannot go async. For everything else (emails, analytics, third-party sync), async wins.
See the SQS documentation and the Lambda SQS trigger guide for the integration setup.
RELATED READING
The Consulting Shift I Am Making In Year Two
After a year of writing and building, my consulting practice is changing shape. Shorter engagements. Sharper outcomes.
ReadThe Frontend Shift: Shipping Less JavaScript In Year Two
A year ago I reached for Next.js for everything. This year I often reach for nothing.
ReadThe Serverless Lesson I Would Write On A Sticky Note
After a year of shipping serverless projects, one rule explains most of the wins and all of the losses.
Read