SNS and EventBridge: Building loosely coupled, event-driven systems
When you're running production systems that need to react to events across multiple services, you quickly realize that tight coupling doesn't scale. SNS and EventBridge are two AWS services that solve different messaging problems, and understanding when to use each one has shaped how I architect every event-driven system I build. Today we're diving into fan-out patterns, content-based routing, and how to replay events when things go wrong.
SNS — Simple pub/sub: one message to many subscribers
SNS (Simple Notification Service) is AWS's managed pub/sub system. The mental model is straightforward: you publish a message to a topic, and all subscribers receive it. It's the simplest fan-out mechanism available.
I've used SNS heavily in e-commerce systems. Imagine an order placement service. When an order is placed, you need to:
- Send a confirmation email
- Update inventory
- Trigger fulfillment
- Log analytics
Without SNS, your order service would call all four downstream services directly. One slow endpoint blocks everything. With SNS, you publish once and let subscribers handle the rest asynchronously.
Here's a practical example. You publish an order event to an SNS topic:
aws sns publish \
--topic-arn arn:aws:sns:us-east-1:123456789012:order-events \
--message '{"orderId":"12345","userId":"user-99","total":99.99}'Then you subscribe multiple services to this topic. SNS delivers the message to each subscriber independently. If one subscriber fails, the others still get it. The key advantage: your order service doesn't know or care who's listening.
In code, publishing from a NestJS service looks like this:
import { SNSClient, PublishCommand } from '@aws-sdk/client-sns';
const sns = new SNSClient({ region: 'us-east-1' });
export async function publishOrderEvent(order: Order) {
const command = new PublishCommand({
TopicArn: process.env.ORDER_TOPIC_ARN,
Message: JSON.stringify({
orderId: order.id,
userId: order.userId,
total: order.total,
timestamp: new Date().toISOString(),
}),
MessageAttributes: {
eventType: { DataType: 'String', StringValue: 'OrderPlaced' },
},
});
await sns.send(command);
}SNS works well when you have a small number of subscribers and you want simple fan-out. However, it lacks filtering. All subscribers receive all messages. If you need to route based on message content—like "only send this event to the European fulfillment service if the shipping country is in the EU"—you need something more sophisticated.
EventBridge — Rule-based routing with filtering on event content
EventBridge is SNS's more powerful cousin. It's an event router that lets you define rules to filter and transform events before they reach targets.
The difference matters. With SNS, filtering happens downstream. With EventBridge, you filter at the routing layer, which means only relevant events consume resources and cause invocations.
I built a travel booking platform where flight searches trigger different workflows based on route complexity:
- Domestic routes: simple inventory check
- International routes: visa requirement check, currency conversion
- High-value routes: manual approval queue
EventBridge rules made this elegant. You send one event, and rules route it to the right handlers:
{
"Name": "international-flight-search",
"EventPattern": {
"source": ["booking.service"],
"detail-type": ["Flight Search"],
"detail": {
"routeType": ["international"],
"estimatedCost": [{ "numeric": [">", 500] }]
}
},
"Targets": [
{
"Arn": "arn:aws:lambda:us-east-1:123456789012:function:visa-checker",
"RoleArn": "arn:aws:iam::123456789012:role/EventBridgeRole"
}
]
}This rule says: "If a flight search event comes in where routeType is 'international' AND estimatedCost is greater than 500, invoke the visa-checker Lambda." Other flight searches skip this rule entirely.
EventBridge also handles transformations. You can reshape the event before it reaches the target. In my CRM platform, I used this to normalize events from different internal services into a single schema for the data warehouse:
{
"Name": "normalize-user-events",
"EventPattern": {
"source": ["crm.service"],
"detail-type": ["User Action"]
},
"Targets": [
{
"Arn": "arn:aws:kinesis:us-east-1:123456789012:stream/analytics",
"RoleArn": "arn:aws:iam::123456789012:role/EventBridgeRole",
"InputTransformer": {
"InputPathsMap": {
"userId": "$.detail.user_id",
"action": "$.detail.action_type",
"timestamp": "$.detail.occurred_at"
},
"InputTemplate": "{\"user_id\": \"\", \"action\": \"\", \"ts\": \"\"}"
}
}
]
}This transformation ensures consistent schema across your data pipeline, reducing downstream parsing logic.
SaaS integrations — EventBridge receives events from many SaaS partners
One of EventBridge's underrated features is native SaaS integration through event sources. AWS maintains integrations with partners like Shopify, Datadog, and MongoDB. When configured, these partners can push events directly into your event bus.
In an e-commerce business I worked on, we integrated Shopify's events directly:
- Enable the Shopify event source in EventBridge
- Authorize Shopify to send events to your AWS account
- Set up rules on your event bus to handle incoming events
Shopify sends order.created, order.updated, product.created events directly to EventBridge. You define rules to route them wherever needed—analytics, inventory sync, financial reconciliation. No custom webhooks, no polling. AWS handles authentication and reliability.
This became crucial when we scaled to multiple channels. Shopify, WooCommerce, and our mobile app all fed events into the same event bus. Different rules processed each channel's events differently, but the infrastructure was unified.
In a trading platform context, I've seen this pattern used with data providers. Price feeds come via EventBridge event sources, rules filter for specific symbols or price thresholds, and Lambda functions react in milliseconds.
Archive and replay — Replay past events into your services
Here's where EventBridge truly shines for production reliability. Every event can be archived automatically. When you discover a bug in downstream processing, you can replay archived events without asking traders or customers to redo actions.
I ran into this in a ride-hailing system. A Lambda function that calculated driver ratings had a bug that underreported ratings for the first month of operation. Instead of manually fixing data, we:
- Fixed the bug in the rating function
- Queried the archive for all rating.calculated events from that month
- Replayed them to a test event bus
- Validated output
- Replayed to production
The EventBridge replay feature handles this:
aws events start-replay \
--name replay-march-ratings \
--event-source-arn arn:aws:events:us-east-1:123456789012:event-bus/default \
--role-arn arn:aws:iam::123456789012:role/EventBridgeReplayRole \
--event-start-time 2024-03-01T00:00:00Z \
--event-end-time 2024-03-31T23:59:59Z \
--event-pattern '{"source":["ride.service"],"detail-type":["Rating Calculated"]}'EventBridge replays matching events to all rules that were active during the original time period. It respects the original timestamps, so time-windowed calculations work correctly. This is invaluable for data consistency in financial systems, analytics platforms, and any mission-critical flow.
To enable this, you enable archiving on your event bus:
aws events put-event-bus \
--name default \
--event-source-account 123456789012 \
--retention-days 30Events are stored for the retention period. Query the archive, replay selectively. It's saved me more than once when production bugs surface.
Common mistakes
- Using SNS when you need EventBridge filtering: If you're publishing events that only some subscribers care about, EventBridge rules prevent unnecessary Lambda invocations and cost.
- Assuming EventBridge is a message queue: EventBridge is event-driven routing. It's not durable by default for failures at the target. Pair it with SQS or Lambda dead-letter queues if you need guaranteed delivery.
- Not enabling archiving early: Archive overhead is minimal. Enable it from day one. Disabling later means you lose history. Replaying past events has saved more production incidents than I can count.
- Complex event patterns without testing: EventBridge event patterns use JSON matching. Test them thoroughly before deploying. A misconfigured pattern might silently drop events.
- Forgetting IAM roles: EventBridge targets require explicit IAM permissions. Many integrations fail silently due to missing or overly restrictive role policies.
Key takeaways
- SNS is simple pub/sub for one-to-many delivery; use it when all subscribers need all messages.
- EventBridge adds rule-based routing and content filtering; use it when you need selective delivery based on event properties.
- EventBridge SaaS integrations eliminate custom webhooks; partner events flow directly into your event bus.
- Archive and replay is production insurance; enable archiving immediately and test replay workflows before you need them in an emergency.
- Both services excel when combined with SQS, Lambda, and other targets; think about failure modes and dead-letter queues from the start.






Comments
Loading comments…