Skip to content

SNS fan-out

2 min read · main part only; folded sections are optional

After this you can answer: Why publish one event to a topic instead of sending it to each worker?

flowchart TB
R["outbox relay"] -->|"Publish once"| T["SNS topic<br/>minitickets-dev-booking-events"]
T -->|"copy"| J["SQS jobs queue<br/>(email)"]
T -->|"copy"| K["SQS tickets queue"]
J --> W["worker Lambda"]
K --> L["tickets Lambda"]
  1. Publish once. The outbox relay calls Publish on the topic with the message as JSON.
  2. SNS copies. The topic has two subscriptions (a subscription = “deliver copies to this endpoint”), both with protocol sqs: the jobs queue and the tickets queue.
  3. Raw delivery. rawMessageDelivery is on, so each queue receives exactly the JSON the api sent, not wrapped in an SNS envelope. The consumers parse one shape.
  4. The queue must allow SNS in. Each queue has a resource policy (a policy attached to the resource, saying who may use it): sns.amazonaws.com may sqs:SendMessage, only when aws:SourceArn is this topic and aws:SourceAccount is ours.
  5. Each consumer runs alone. A broken email path does not stop tickets, and the other way round.

Everything is in infra/src/jobs/booking-events.ts → createBookingEvents. Each subscription is created only after its queue policy exists (dependsOn), because SNS would otherwise fail to deliver. The topic ARN is handed to the api only when both subscriptions exist: SNS happily accepts a message with zero subscribers, and the relay would then mark an undelivered row as sent.

The topic is always on: it costs nothing while idle. The api task role may sns:Publish on this one topic only.

From → toWhat happensWhat allows it
Transactional outbox → Booking-events SNS topicthe relay publishes each pending row once to SNS
API ECS service → Booking-events SNS topiconly when computeEnabled = truethe outbox relay publishes each booking-confirmed event
  • iam api task role, policy send-jobs: sns:Publish on minitickets-dev-booking-events onlycreateIamRoles
  • route public route table: 0.0.0.0/0 → minitickets-dev-igw. The task has a public IP; there is no NAT gateway and no VPC endpoint.createNetwork
Booking-events SNS topic → Jobs SQS queueSNS delivers a copy to the email queue
  • resource-policy queue policy: sns.amazonaws.com may sqs:SendMessage on this queue, only when aws:SourceArn is the topic and aws:SourceAccount is ourscreateBookingEvents
  • trigger SNS subscription protocol sqs, rawMessageDelivery truecreateBookingEvents
Booking-events SNS topic → Tickets SQS queueSNS delivers a copy to the tickets queue
  • resource-policy queue policy: sns.amazonaws.com may sqs:SendMessage on this queue, only when aws:SourceArn is the topic and aws:SourceAccount is ourscreateBookingEvents
  • trigger SNS subscription protocol sqs, rawMessageDelivery truecreateBookingEvents
See the topic and its two queues on the map →

SNS → Topics → minitickets-dev-booking-events → Subscriptions: two, protocol SQS, status Confirmed.

SymptomCauseWhere to look
The relay logs “accepted by SNS”, but the queues stay emptyA queue policy does not allow sns.amazonaws.com from this topicSQS → queue → Access policy
Tickets arrive but emails do notOne subscription is missing or points at another queueSNS → topic → Subscriptions
The worker rejects every message as invalidRaw delivery is off: the message arrives inside the SNS envelopeSubscription → Raw message delivery
Why not let the Lambdas subscribe to SNS directly?

SNS would call them at once and retry on its own schedule. A queue in between holds messages while a consumer is down or slow, and gives each consumer batching, backoff and a DLQ.

How would you add a third consumer, for example analytics?

One more queue, its queue policy and one more subscription. The api and the other consumers do not change.

SNS or EventBridge?

EventBridge adds content-based routing rules, schemas and many targets. For one event type and two queues, SNS is simpler. It is a reasonable change if event types and routing rules grow.

Advanced: Details: ordering and duplicates

The topic and queues are standard (not FIFO). Delivery is at least once and in no fixed order. That is fine here: each message is about one booking, and both consumers are idempotent by booking id.

Answer these without scrolling up. If one feels hard, reread that section.

  1. How many times does the api publish one booking event?
  2. What does rawMessageDelivery change?
  3. Which two conditions does each queue policy check?
  4. Why does the api get the topic ARN only after both subscriptions exist?
  5. Why put a queue between SNS and each Lambda?

Redraw it from memory: outbox relay → SNS topic → (jobs queue → worker Lambda) and (tickets queue → tickets Lambda)

Try this: on the map, select the booking-events SNS topic, open Connections, and find the rule that lets SNS send to each queue

Read this book online: https://book.theversehub.in/