“Should I use SQS or SNS here?” is one of those questions that sounds like a product comparison but is actually a design question. Both services make your system asynchronous. Both decouple producers from consumers. Both show up in the same architecture diagrams. But they implement two fundamentally different messaging models — and picking the wrong one gives you either lost events or accidental duplicate processing.
This post breaks down the two models (message broker/queue vs pub/sub), when to reach for each, and finishes with a system design where you use both together — which is what production systems actually do.
Async has two shapes
When someone says “make it async,” they usually mean one of two things without realizing they’re different:
- Work distribution — “someone, process this job, exactly once.” The message is a task. You care that it gets done, once, by any available worker.
- Event broadcast — “this thing happened; everyone who cares should react.” The message is a fact. You care that every interested party finds out.
SQS is built for the first. SNS is built for the second. Everything else — delivery model, retention, retries, fan-out — follows from that distinction.
What a message broker (queue) actually gives you
A queue is a durable buffer with exactly-one-consumer semantics. Producers put messages in; a pool of workers pulls them out. Each message is processed by exactly one worker.

The properties that matter:
- Pull-based delivery. Workers poll the queue at whatever pace they can handle. A slow consumer doesn’t drop messages — the queue just gets deeper. This is why queues are the standard answer for load leveling: your API can accept a burst of 10,000 requests while your workers chew through them at 200/sec.
- Retention. SQS holds messages for up to 14 days (default 4). If your consumer fleet is down for an hour, nothing is lost — messages wait.
- Visibility timeout. When a worker receives a message, it becomes invisible to other workers for a window. If the worker crashes before deleting it, the message reappears and someone else picks it up. That’s your at-least-once retry mechanism.
- Dead-letter queues. After
maxReceiveCountfailed attempts, a poison message gets shunted to a DLQ instead of blocking the pipeline forever. You inspect, fix, and redrive. - Ordering, if you need it. Standard queues are best-effort ordering with at-least-once delivery. FIFO queues give you strict ordering within a message group plus deduplication — at the cost of throughput (300 TPS per message group without batching, 3,000 with).
The mental model: a queue is a to-do list shared by a team. Each item gets crossed off once.
# Producer
sqs.send_message(
QueueUrl=QUEUE_URL,
MessageBody=json.dumps({"job": "resize_image", "key": "uploads/hero.jpg"}),
)
# Worker loop
while True:
resp = sqs.receive_message(
QueueUrl=QUEUE_URL,
MaxNumberOfMessages=10,
WaitTimeSeconds=20, # long polling — fewer empty receives, lower cost
)
for msg in resp.get("Messages", []):
process(json.loads(msg["Body"]))
sqs.delete_message(QueueUrl=QUEUE_URL, ReceiptHandle=msg["ReceiptHandle"])
Note the shape of the contract: the worker explicitly deletes the message after processing. Until that delete, the queue considers the job unfinished.
What pub/sub (a topic) actually gives you
A topic is a broadcast channel with copy-per-subscriber semantics. A publisher sends one message; SNS delivers an independent copy to every subscription — SQS queues, Lambda functions, HTTPS endpoints, email, SMS, mobile push.

The properties that matter:
- Push-based delivery. SNS actively delivers to endpoints. There’s no polling and no queue depth to monitor on the topic itself.
- No retention. This is the big one people miss. If nobody is subscribed when you publish, the message is gone. SNS retries delivery to a failing endpoint per its retry policy, but a topic is not a place where messages live.
- Publisher ignorance. The order service publishing
order-placedhas no idea whether three systems or thirty are listening. You add consumers without touching the publisher — this is the decoupling that actually matters in a growing codebase. - Filter policies. Subscribers can filter on message attributes, so the analytics queue only receives
event_type = "purchase"while the fraud queue receives everything above a certain amount. Routing logic lives in infrastructure, not in your application code.
The mental model: a topic is an announcement over a PA system. Everyone present hears it; anyone absent missed it.
When to reach for which
Reach for SQS when the message is a job:
- Background work — image resizing, PDF generation, sending emails, webhook delivery
- The producer is faster than the consumer and you need a shock absorber
- Each task must be processed once, by exactly one worker
- You need retries, backoff via visibility timeout, and a DLQ for poison messages
- Ordering matters within an entity (per-user, per-order) — FIFO with message groups
Reach for SNS when the message is an event:
- One thing happened and multiple systems must react independently
- You expect to add consumers later without redeploying the publisher
- Alerting — fan out a CloudWatch alarm to email, SMS, and a Slack webhook simultaneously
- Cross-service event propagation in a microservices setup
- Different subscribers need different slices of the stream — attribute-based filtering
The cheat sheet:
| SQS (queue) | SNS (topic) | |
|---|---|---|
| Consumers per message | exactly one | every subscriber |
| Delivery | pull (poll) | push |
| Retention | up to 14 days | none |
| Retry story | visibility timeout + redrive to DLQ | per-protocol delivery retry policy |
| Replay | yes, until deleted/expired | no |
| Ordering | FIFO queues available | FIFO topics (must pair with FIFO SQS) |
| Think of it as | work distribution | event broadcast |
The system design: use both
Here’s the part interviews and real systems care about. The question “SQS or SNS?” usually has a third answer: SNS → SQS fan-out.
The problem with subscribing services directly to a topic (via HTTPS or Lambda): if a subscriber is down or throttled, you’re leaning entirely on SNS’s delivery retry policy, and you have no backlog you control, no replay, no back-pressure. The problem with a bare queue: only one consumer gets each message, so you can’t broadcast.
The fix is to compose them. Publish to a topic; subscribe a queue per consumer to that topic. The topic gives you fan-out; each queue gives its owner durability, retries, replay, and independent scaling.

Walk through the failure modes, because that’s where this design earns its keep:
- Payment service is slow during a sale. Its queue absorbs the backlog. Inventory and notifications are unaffected — they’re reading from their own queues.
- Notification service is down for a deploy. Messages accumulate in
notify-qand get processed on recovery. Nothing lost, no coordination needed. - A payment message keeps failing (say, a malformed payload). After
maxReceiveCountattempts it lands in the payments DLQ. The pipeline keeps moving; an alarm on DLQ depth pages someone. - Product asks for an analytics consumer next quarter. Add a queue, subscribe it to the topic, optionally with a filter policy. The order service doesn’t change. It doesn’t even redeploy.
Two implementation details that bite people:
- Raw message delivery. By default, SNS wraps your payload in its own JSON envelope. Enable
RawMessageDeliveryon the SQS subscription unless you want every consumer unwrappingMessageout of the envelope. - Queue policy. The SQS queue needs a resource policy allowing
sns.amazonaws.comtoSendMessage, scoped to the topic ARN via aConditiononaws:SourceArn. Forgetting this is the #1 “why is my fan-out silently doing nothing” bug — the subscription exists, delivery just fails.
{
"Effect": "Allow",
"Principal": { "Service": "sns.amazonaws.com" },
"Action": "sqs:SendMessage",
"Resource": "arn:aws:sqs:ap-south-1:123456789012:payments-q",
"Condition": {
"ArnEquals": { "aws:SourceArn": "arn:aws:sns:ap-south-1:123456789012:order-placed" }
}
}
TL;DR
- The message is a job for one worker → SQS.
- The message is an event for many systems → SNS.
- The message is an event that must survive consumer failures → SNS fanning out to one SQS queue per consumer.
Pick by message semantics — is this a task or a fact? — and the service choice falls out on its own.
This is part of my AWS DVA-C02 build log — hands-on labs turned into articles. If you’re studying the same exam, the fan-out pattern above (topic → queues → DLQs, filter policies, raw message delivery) maps directly to several exam scenarios.