The situation
A retail company runs three event flows and the platform lead has asked the question every platform lead eventually asks: can we stop bespoke point-to-point integrations and adopt one shape for event fan-out?
- A payment-captured flow: a successful payment needs to be acknowledged by the ledger service, the receipt service, the loyalty service, and the email service. Four internal consumers, all owned by the same company, all within the same AWS accounts. The payment team publishes once; four services react.
- An order-placed flow: a placed order must trigger fulfilment, notification, analytics, fraud review, and a CRM update. Five consumers; some of them filter by order type (the fraud service only cares about orders over $500; the CRM only cares about first-time customers).
- A SaaS-event flow: Zendesk, Stripe, and Auth0 all emit events that the company wants to consume, ticket created, subscription updated, user signed up. Each emitter has its own schema; the consumer for each is a different Lambda; the routing has to sort events by source and detail type.
Everything today is hand-rolled with Lambda invoking Lambda, which works until it doesn’t. The question isn’t whether a messaging service helps, it does, it’s which one fits each shape, and how to reason about the differences without conflating them.
What actually matters
Before reaching for the console, it’s worth asking what we’re actually trading.
The core distinction in AWS’s fan-out story is push vs pull vs route. One primitive pushes a message to every subscribed endpoint. Another buffers messages until a consumer pulls them, with each message delivered to exactly one puller. A third routes events through a pattern-matching engine that selects targets per event. Three primitives, three delivery semantics.
The first thing to ask is: how many consumers, and do they all want every event? If every consumer wants every event, the pub-sub or routing models fit naturally. If one consumer should grab each event and the rest shouldn’t see it, that’s the buffered-queue model.
The second thing to ask is: do consumers need filtering, and how rich? Some primitives offer no filtering at all (the producer picks the destination, or the consumer filters client-side). Others offer flat attribute-based filtering. Others offer rich content-based filtering on the whole event payload, with numeric comparisons, prefix matching, and IP range matching, and route matching events to specific targets. The richer the filtering requirement, the stronger the case for the routing model.
The third is producers and targets. Some primitives have a short built-in target list confined to a handful of internal destinations. Others reach a far wider AWS-service catalogue plus external HTTPS endpoints. If the routing target is anything other than “internal queue or function,” the wider catalogue tends to be the cleaner fit.
The fourth is schema story. Some primitives ship a schema registry that discovers the shapes of events on the wire and can generate code bindings from them, with a consistent envelope used for routing. Others are bring-your-own-schema; the envelope is whatever the publisher writes.
The fifth is delivery guarantees and ordering. The pull-queue primitive offers a choice between best-effort ordering with possible duplicates and strict ordering within a message group with a short dedup window. The pub-sub primitive offers similar variants. The routing primitive is at-least-once with no ordering guarantee on the bus itself.
And finally, a softer one: cost and throughput shapes. The three pricing models meter different things, per published event plus per cross-account invocation, per publish plus per delivery, per request. For high-volume fan-out to many consumers, the cost curves look different, worth checking the pricing calculator for specific volumes before committing.
What we’ll filter on
Distilling that exploration into filters we can score each service against:
- Fan-out model, push to many, pull by one, or route by pattern?
- Filtering capability, attribute-based, content-based, or none?
- Target variety, internal consumers only, or the AWS-wide target catalogue?
- Schema handling, envelope, registry, or bring-your-own?
- Ordering / dedup, standard, FIFO, or neither?
The service landscape
-
SQS (Simple Queue Service). A buffered queue. A producer puts a message; one consumer gets it; when the message is processed, the consumer deletes it; if not deleted before the visibility timeout expires, another consumer gets the same message. The foundational at-least-once queue. Standard queues are unordered and have occasional duplicates; FIFO queues preserve order within a message group and deduplicate within a five-minute window. Fan-out isn’t native: to fan out, the producer writes to multiple queues, or SNS sits in front.
-
SNS (Simple Notification Service). A pub-sub topic. A producer publishes one message; every subscription gets a copy. Subscriptions can be SQS queues, Lambda functions, HTTP/HTTPS endpoints, email, SMS, mobile push, or Firehose streams. Message filtering on attributes (flat key-value) means a subscription can filter which messages it actually receives. FIFO topics exist and only support SQS FIFO subscribers. The classic fan-out-to-all-subscribers service.
-
EventBridge. An event bus. A producer publishes an event with
source,detail-type, and adetailpayload. A rule matches events against a pattern (content-based, can inspect any field ofdetail) and sends matching events to one or more targets. The target catalogue is the AWS service list: Lambda, Step Functions, API destinations (external HTTPS), SQS, SNS, Kinesis, ECS, Redshift Data API, SageMaker, and so on. Three bus types: the default bus (AWS service events), custom buses (application events), and partner buses (SaaS events from the integrations catalogue). -
Kinesis Data Streams. A different primitive. An ordered, sharded log. Producers write records; consumers read records by shard in order, at their own pace, with checkpoints. Not really a fan-out service in the same sense, it’s for high-throughput stream processing where order within a key matters. Appears on this page only to be set aside.
-
MSK (Managed Streaming for Kafka). Apache Kafka as a service. Similar shape to Kinesis, richer ecosystem. Set aside for the same reason, it’s a stream-processing primitive, not a fan-out router.
Side by side
| Service | Fan-out model | Filtering | Target variety | Schema | Ordering / dedup |
|---|---|---|---|---|---|
| SQS | Queue (1-to-1) | None (consumer-side) | One consumer per message | BYO | Std or FIFO |
| SNS | Topic (1-to-many) | Attribute-based (flat) | Built-in list: SQS, Lambda, HTTP, email, SMS, push | BYO | Std or FIFO |
| EventBridge | Bus (pattern routing) | Content-based (deep) | Large AWS target catalogue + API destinations | Envelope + registry | Best-effort |
| Kinesis | Sharded log | — (consumers filter) | Consumer applications | BYO | Per-shard ordered |
| MSK (Kafka) | Sharded log | — | Consumer applications | BYO (or schema registry) | Per-partition ordered |
Reading the table by flow rather than by service:
- Payment-captured, four internal consumers, each wants every event, no routing beyond “send to all four.” SNS is the direct hit: one topic, four subscriptions (three SQS queues and one Lambda, or four Lambdas), one publish-per-event cost.
- Order-placed, five consumers, each with its own filter condition. EventBridge is the direct hit: one bus, five rules with content-based patterns, five targets. Each consumer receives only the events it’s interested in, without the producer or the other consumers knowing.
- SaaS-events, multiple emitters with distinct schemas, routing by source and detail type. EventBridge partner buses for the SaaS side; rules route by
source(aws.partner/zendesk.com/...) anddetail-type(TicketCreated) into the right Lambda. The schema registry auto-discovers shapes so consumer code can be generated.
Mapping fan-out shape to service
The picks in depth
Payment-captured → SNS topic fanning into SQS queues. The topic is one resource; each consumer owns its own subscription. Three of the four consumers get their own SQS queue subscribed to the topic, so their processing is decoupled from the moment of publish, if the loyalty service is slow or down, messages pile up in its queue rather than blocking the others. The fourth (email) subscribes a Lambda directly because the workload is light and the team wants the simplest path. Each SQS queue has a dead-letter queue behind it with a 5-message maxReceiveCount, so persistent failures don’t quietly retry forever.
SNS FIFO exists if the team ever needs ordering, but for payment-captured the order of receipt-issue vs ledger-write is orthogonal (they’re idempotent, they can arrive in any order), so standard is cheaper and faster.
Order-placed → EventBridge custom bus with five rules. The producer publishes once with PutEvents to the orders bus. Rules inside the bus inspect each event’s content and route matching events to targets:
{
"source": ["com.acme.orders"],
"detail-type": ["OrderPlaced"],
"detail": { "amount": [{ "numeric": [">", 500] }] }
}
The fraud rule matches only orders over $500; the CRM rule matches "firstOrder": [true]; the other three match every event. Targets include a Step Functions state machine (fulfilment), a Lambda (notification), a Firehose stream to S3 (analytics), another Lambda (fraud), and an API destination for the third-party CRM (HTTPS webhook). The target list is where EventBridge pulls ahead of SNS. SNS cannot deliver directly to Step Functions or an external HTTPS endpoint; EventBridge can.
Dead-letter handling: each target has its own DLQ configuration (DeadLetterConfig in the target). A failed delivery to the fraud Lambda doesn’t affect the fulfilment Step Function; each consumer’s failure is isolated.
SaaS events → EventBridge partner buses. Each SaaS integration creates a partner event source in the AWS account; the company associates it with a partner bus named after the vendor. Rules on that bus filter by source and detail-type:
{
"source": ["aws.partner/zendesk.com/acme/Tickets"],
"detail-type": ["Ticket Created"]
}
The rule targets the ticket-handler Lambda. A different rule on the same bus matches detail-type: ["Ticket Updated"] and targets a different Lambda. The schema registry auto-discovers the shape; the Lambda’s bindings are generated from the registry. When Zendesk changes the shape, the registry picks up the new version; the team sees the change in a code review before it lands in production.
A worked publishing example
Producer publishing an order event:
await eventbridge.putEvents({ Entries: [{
Source: 'com.acme.orders',
DetailType: 'OrderPlaced',
Detail: JSON.stringify({
orderId: 'ord_abc123',
customerId: 'cus_xyz',
amount: 1250,
firstOrder: false,
items: [...]
}),
EventBusName: 'orders',
}] }).promise();
Routes taken (one publish, four targets invoked):
- Fulfilment rule matches → Step Functions execution starts.
- Notification rule matches → Lambda invocation.
- Analytics rule matches → Firehose record.
- Fraud rule:
amount: 1250 > 500matches → Lambda invocation. - CRM rule:
firstOrder: falsedoes not match → no invocation.
One PutEvents call, four targets fired based on content, one target skipped by the rule’s pattern. The producer has no idea there are four consumers; the consumers have no idea there’s a producer. That’s the point.
What’s worth remembering
- SNS is pub-sub to a fixed target catalogue. One topic, many subscriptions, filter on attributes. The default for internal fan-out when the targets are SQS queues or Lambda functions.
- EventBridge is a pattern-matching router. One bus, many rules, content-based patterns, a much wider target list. The default when routing depends on event content or targets include Step Functions, API destinations, or non-Lambda non-SQS services.
- SQS is a queue, not a fan-out service. Fan-out with SQS means SNS-in-front or multiple producers writing to multiple queues. SQS’s strength is the decoupling and the dead-letter queue behind each consumer.
- EventBridge partner buses are the SaaS story. Zendesk, Stripe, Auth0, Shopify, PagerDuty, partner source attaches to a partner bus; rules sort by source and detail-type.
- Filtering shapes the service choice. SNS supports flat attribute filtering; EventBridge supports deep content-based filtering with numeric and prefix matching. If the filter needs to inspect a field inside the payload, EventBridge is right.
- Ordering / dedup needs FIFO. Standard SQS and SNS are at-least-once, unordered; FIFO variants give strict ordering within a group and a five-minute dedup window. EventBridge has no ordering guarantee.
- DLQs belong on every target. SNS subscriptions have a redrive policy; EventBridge targets have a
DeadLetterConfig; SQS queues haveRedrivePolicy. Without DLQs, failures vanish. - Targets matter more than you’d expect. SNS can deliver to SQS, Lambda, HTTP, email, SMS, push, Firehose. EventBridge can deliver to that list plus Step Functions, API destinations, ECS, Kinesis, SageMaker, and dozens more. The target list is frequently the deciding factor.
SNS for push to many, SQS for buffered pull by one, EventBridge for content-based routing to the AWS-wide target catalogue. Three fan-out shapes, three services, three clean fits. The work isn’t picking a favourite, it’s pairing each flow with the service whose model matches its fan-out shape.