Ever heard the term "event-driven architecture" and felt like it was written in a different language? You're not alone.
In this post, I'll break it all down using plain English, real analogies, and working code you can run entirely on your laptop using AWS SAM, no AWS account charges required.
By the end, you'll understand how EventBridge, SNS, SQS, and Lambda work together, and you'll have built a fully connected event-driven pipeline locally.
Think of it Like a Shopping Mall
Before any code, let me paint a picture.
You own a shopping mall. A customer places an order. Several things need to happen:
- The warehouse needs to pack the item
- The accounts team needs to record the payment
- The delivery team needs to schedule a shipment
The old way? You'd call each team one by one. Slow, tightly coupled, and fragile. If one team is busy, everything stalls.
The new way? You post a message on a notice board. Every team that needs to act sees it automatically and responds in their own time.
That notice board is event-driven architecture. And AWS gives us four services to build it.
Prerequisites
Before you start, make sure you have the following installed on your Mac/Linux machine:
| Tool | Why You Need It |
|---|---|
| AWS CLI | To configure your AWS credentials |
| AWS SAM CLI | To build and run Lambda locally |
| Python 3.12 | The runtime for our Lambda functions |
| Docker | To simulate the Lambda execution environment |
Quick check — run these in your terminal to confirm everything is ready:
aws --version
sam --version
python3 --version
docker --version
No AWS deployment needed! We run everything locally using SAM and Docker, so no AWS account charges apply while following this guide.
Architecture

The diagram shows the live event flow. The phases build it in reverse starting from Lambda and working back to EventBridge — so each layer is testable before adding the next
The 4 Services, Explained Simply
1. EventBridge — The Smart Postman
EventBridge is your event bus, the central place where things get published when something happens in your app.
You define rules that tell it where to route events:
If the event comes from myapp.orders and the type is OrderPlaced, send it to SNS.
It reads the label on every package and delivers it to exactly the right destination.
// An EventBridge event looks like this
{
"source": "myapp.orders",
"detail-type": "OrderPlaced",
"detail": {
"orderId": "ORD-789",
"product": "Tablet",
"amount": 749.99,
"customer": "Emma Ulu"
}
}
2. SNS — The Group Chat
SNS (Simple Notification Service) is your megaphone. Once EventBridge sends an event to an SNS topic, SNS fans it out to all subscribers simultaneously.
You could have one subscriber or a hundred. They all get the message at the same time. This is the fan-out pattern.
One message in, many receivers out.
3. SQS — The Voicemail Box
SQS (Simple Queue Service) is a waiting line for messages. When SNS drops a message into an SQS queue, it sits there patiently until someone picks it up.
Why does this matter? Because if your Lambda function is busy or crashes, the message doesn't get lost. It just waits.
Like voicemail. If you're busy, the message waits. You won't miss it.
4. Lambda — The Worker
Lambda is your actual worker function, the code that does the real job. It picks up messages from SQS one by one and processes them: saving to a database, sending an email, charging a card, notifying another service. Anything you need.
The Full Flow
Customer places an order
↓
EventBridge checks its rules
↓
Rule matches → sends to SNS Topic
↓
SNS fans out → drops into SQS Queue
↓
Lambda picks up the message
↓
Order is processed ✅
Clean. Decoupled. Scalable.
What is AWS SAM?
SAM (Serverless Application Model) is the tool that lets you define and test all of this locally. Instead of clicking around in the AWS console, you write a template.yaml that describes your infrastructure as code.
SAM runs everything in Docker containers on your machine. No deployment needed while learning.
Think of it as a flight simulator. You learn to fly before touching the real plane.
Think of it as a flight simulator. You learn to fly before touching the real plane.
Building It — Phase by Phase
I built this project in 4 phases, each adding one layer to the architecture.
Phase 1 — Lambda Foundation
First, a simple Lambda function that can receive and log any event.
hello_world/app.py
import json
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
logger.info(f"Received event: {json.dumps(event)}")
return {
"statusCode": 200,
"body": json.dumps({"message": "Hello from Event-Driven App!"})
}
Test it locally:
sam build
sam local invoke HelloWorldFunction --event events/event.json --skip-pull-image
Phase 2 — Add SQS
Now we wire SQS to trigger Lambda whenever a message arrives in the queue.
template.yaml (key additions)
EventQueue:
Type: AWS::SQS::Queue
Properties:
QueueName: event-queue
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
Events:
SQSEvent:
Type: SQS
Properties:
Queue: !GetAtt EventQueue.Arn
BatchSize: 1
Updated app.py to handle SQS Records:
def lambda_handler(event, context):
for record in event.get("Records", []):
body = json.loads(record["body"])
logger.info(f"Processing message: {body}")
return {"statusCode": 200, "body": "SQS messages processed!"}
Test event (events/sqs_event.json):
{
"Records": [{
"body": "{\"orderId\": \"ORD-123\", \"product\": \"Laptop\", \"amount\": 999.99}",
"eventSource": "aws:sqs"
}]
}
sam local invoke HelloWorldFunction --event events/sqs_event.json --skip-pull-image
Output:
Processing message: {'orderId': 'ORD-123', 'product': 'Laptop', 'amount': 999.99}
✅ SQS → Lambda working!
Phase 3 — Add SNS Fan-out
Now SNS sits between your publisher and SQS, enabling fan-out to multiple queues if needed.
template.yaml additions:
EventTopic:
Type: AWS::SNS::Topic
EventTopicSubscription:
Type: AWS::SNS::Subscription
Properties:
TopicArn: !Ref EventTopic
Protocol: sqs
Endpoint: !GetAtt EventQueue.Arn
RawMessageDelivery: true
When SNS delivers to SQS, it wraps the message in an SNS envelope. Update app.py to unwrap it:
parsed_body = json.loads(record["body"])
if parsed_body.get("Type") == "Notification":
# Unwrap the SNS envelope
sns_message = json.loads(parsed_body["Message"])
logger.info(f"Subject: {parsed_body.get('Subject')}")
logger.info(f"Message: {sns_message}")
✅ SNS → SQS → Lambda working!
Phase 4 — Add EventBridge
The final piece. EventBridge becomes the entry point for all events, routing them to SNS based on rules.
template.yamladditions:
EventBus:
Type: AWS::Events::EventBus
Properties:
Name: event-driven-bus
OrderPlacedRule:
Type: AWS::Events::Rule
Properties:
EventBusName: !Ref EventBus
EventPattern:
source:
- "myapp.orders"
detail-type:
- "OrderPlaced"
Targets:
- Arn: !Ref EventTopic
Id: SNSTarget
Update app.py to extract the EventBridge payload from inside the SNS envelope:
if parsed_body.get("Type") == "Notification":
sns_message = json.loads(parsed_body["Message"])
source = sns_message.get("source")
detail_type = sns_message.get("detail-type")
detail = sns_message.get("detail", {})
logger.info(f"Source: {source}")
logger.info(f"Event Type: {detail_type}")
logger.info(f"Detail: {detail}")
if detail_type == "OrderPlaced":
process_order(detail)
def process_order(detail):
logger.info(f"Order ID: {detail.get('orderId')}")
logger.info(f"Product: {detail.get('product')}")
logger.info(f"Amount: ${detail.get('amount')}")
logger.info(f"Customer: {detail.get('customer')}")
Final test output:
EventBridge Source: myapp.orders
Event Type: OrderPlaced
Processing order: ORD-789
Product: Tablet
Amount: $749.99
Customer: Emma ulu
✅ Full pipeline working — 100% locally!
Why Not Just Call Lambda Directly?
| Direct Call | Event-Driven |
|---|---|
| Tight coupling | Loosely coupled |
| One failure breaks others | Services fail independently |
| Hard to add new features | Just add a new SNS subscriber |
| Hard to scale | Each service scales independently |
The event-driven approach lets your teams and services operate independently. A new feature? Just subscribe to the event. No need to touch existing code.
What I Learned (Phase Summary)
| Phase | Service Added | Pattern Learned |
|---|---|---|
| 1 | Lambda | Local SAM testing, template.yaml basics |
| 2 | SQS | Message queues, Records array |
| 3 | SNS | Fan-out, envelope unwrapping |
| 4 | EventBridge | Event buses, content-based routing |
What's Next?
Now that you've got the foundation, here's where to go next:
- Dead Letter Queue (DLQ) — never lose a failed message
- Multiple Lambda consumers — fan-out to different Lambdas for different jobs
- EventBridge Pipes — direct SQS→Lambda without SNS
- Schema Registry — validate your event shapes
- Deploy to AWS — sam deploy --guided when you're ready
Final Thoughts
Event-driven architecture sounds intimidating, but at its core, it's just a smarter way for services to talk to each other. You post an event, the right services react, and nothing is tightly coupled.
AWS SAM makes it approachable by letting you build and test everything locally before spending a single dollar on the cloud.
If you followed along and got it working, well done! You've just built a real-world architecture pattern used by companies at scale.
Drop a comment if you have questions or want me to go deeper on any phase. Happy building!
Built with: AWS SAM · EventBridge · SNS · SQS · Lambda · Python 3.12

Top comments (0)