Updated Nov 5, 20266 min read

SNS vs SQS: Which AWS Messaging Service Do You Need?

How Amazon SNS and Amazon SQS differ, when to use each, and why most event-driven backends end up using both together.

Amazon SNS and Amazon SQS are both AWS messaging services, and the names suggest they are alternatives. They solve different halves of the same problem: SNS delivers one message to many subscribers, and SQS holds messages until a worker is ready to process them. Choosing wrong shows up later as lost messages or as services coupled through a queue they should not share.

This comparison covers how each one delivers, retries and orders messages, then the pattern most backends land on: an SNS topic fanning out to one SQS queue per consumer. It ends with how Encore (infrastructure from code) sets up that pattern from TypeScript code.

SNS vs SQS at a glance

Encore on AWSAmazon SNSAmazon SQS
ModelTyped topics and subscriptionsPublish/subscribeQueue
DeliverySNS topic, one SQS queue per subscriptionPushed to every subscriberPulled by consumers
Message storageIn each subscription's queueNot stored after deliveryUp to 14 days
Consumers per messageOne per subscriptionAll subscribersOne
Retries and DLQretryPolicy and a DLQ per subscriptionDelivery policy, DLQ per subscriptionVisibility timeout and redrive policy
OrderingorderingAttribute on the topicFIFO topicsFIFO queues

Each option is covered below, starting with Encore.

Wiring SNS and SQS from TypeScript with Encore

Encore is infrastructure from code for TypeScript and Go, with a platform that provisions and deploys into your own AWS or GCP account. A Topic in code becomes an SNS topic on AWS, each Subscription gets an SQS queue subscribed to it with a dead-letter queue. Cloud access sits with Encore's IAM role, not the agent.

Application code passes through Encore and infrastructure guardrails to resources provisioned in your own AWS or GCP account.
Encore provisions the infrastructure your application needs in your own AWS or GCP account.
// orders/orders.ts import { api } from "encore.dev/api"; import { Topic, Subscription } from "encore.dev/pubsub"; import { SQLDatabase } from "encore.dev/storage/sqldb"; import log from "encore.dev/log"; const db = new SQLDatabase("orders", { migrations: "./migrations" }); export interface OrderPlaced { orderId: string; email: string; totalCents: number; } // An SNS topic on AWS. export const orderPlaced = new Topic<OrderPlaced>("order-placed", { deliveryGuarantee: "at-least-once", }); export const place = api( { expose: true, method: "POST", path: "/orders" }, async (req: OrderPlaced): Promise<{ messageId: string }> => { const messageId = await orderPlaced.publish(req); return { messageId }; }, ); // An SQS queue subscribed to the topic, with its own dead-letter queue. const _ = new Subscription(orderPlaced, "send-receipt", { handler: async (event) => { // Idempotent: a redelivered event finds the row and does nothing. const inserted = await db.queryRow` INSERT INTO receipts (order_id, email) VALUES (${event.orderId}, ${event.email}) ON CONFLICT (order_id) DO NOTHING RETURNING order_id`; if (!inserted) return; log.info("receipt sent", { orderId: event.orderId, email: event.email }); }, retryPolicy: { maxRetries: 5, minBackoff: "10s" }, });

The migration in orders/migrations creates the receipts table the queries use:

-- orders/migrations/1_create_receipts.up.sql CREATE TABLE receipts ( order_id TEXT PRIMARY KEY, email TEXT NOT NULL );

Adding a second consumer, say an analytics service, is another new Subscription(orderPlaced, "update-analytics", ...) in that service, which becomes a second queue on the same topic. encore run runs topics and subscriptions in-process, so the flow is tested locally before any AWS resource exists. We ran this example and the receipt endpoint reported {"sent":true} after one publish. The Pub/Sub docs cover delivery guarantees, ordering and concurrency limits.

For coding agents, the typed Topic<OrderPlaced> is the contract: an agent that changes the event shape gets compile errors in every subscriber, rather than a queue full of messages a consumer can no longer parse.

The infrastructure provisioning docs describe what is created in each environment.

Get started
brew install encoredev/tap/encore &&
encore app create
Copy

Run in your terminal to get started locally.

Amazon SNS: one message, many subscribers

Amazon SNS pushes each published message to every subscription on the topic. Subscribers can be SQS queues, Lambda functions, HTTPS endpoints, email addresses or phone numbers. Subscription filter policies let a subscriber receive only messages whose attributes match, so a fraud service can take only orders above a threshold.

SNS does not keep messages. If an HTTPS subscriber is down, SNS retries according to the delivery policy and then gives up, unless the subscription has its own dead-letter queue, so SNS on its own suits notifications where a missed delivery is acceptable, and why backends rarely point SNS straight at their own services.

Amazon SQS: a buffer for one consumer

Amazon SQS stores messages until a consumer polls for them, for up to 14 days. A received message becomes invisible for the visibility timeout. The consumer deletes it after processing; if it does not, the message reappears and is retried. After a set number of receives, a redrive policy moves it to a dead-letter queue.

Standard queues deliver at least once with best-effort ordering. FIFO queues keep order within a message group and deduplicate within a five-minute window, at lower throughput. Many workers can poll one queue, but each message goes to one of them, which makes SQS a work queue, not a broadcast.

SNS and SQS together: the fan-out pattern

When several services react to the same event, the standard design is an SNS topic with one SQS queue per consumer, described in AWS's fan-out guide. Each queue buffers messages for its own consumer, so a slow analytics worker does not delay receipts, and each consumer has its own retries and dead-letter queue.

Setting this up by hand means a topic, a queue per consumer, a queue policy that lets the topic send to each queue, a redrive policy and dead-letter queue per queue, and IAM permissions for each publisher and consumer. With raw message delivery off, the consumer also has to unwrap the SNS envelope around each SQS message body. None of it is hard, but it is a lot of configuration per event type.

Good to know

Both services deliver at least once by default, so a handler can see the same message twice. A unique key on the side effect, like the order_id primary key above, makes the second delivery a no-op.

How to choose

Pick Encore when you want the SNS-to-SQS fan-out from typed TypeScript or Go code, with queues, dead-letter queues and IAM created in your own AWS account and the whole flow running locally. The same code runs on Google Cloud Pub/Sub if you deploy to GCP.

Pick SNS alone for notifications to email, SMS or external HTTPS endpoints, and SQS alone for a single work queue, such as image processing jobs consumed by a pool of workers. Use both, by hand, when the topics and queues are shared with systems outside your application.

Frequently asked questions

What is the difference between SNS and SQS?

SNS is publish/subscribe: a message sent to a topic is pushed to every subscriber, such as SQS queues, Lambda functions or HTTPS endpoints, and SNS does not keep it after delivery. SQS is a queue: messages are stored until a consumer polls, processes and deletes them, and each message is handled by one consumer.

Should I use SNS and SQS together?

Usually, yes, when more than one service reacts to the same event. Publish to an SNS topic and subscribe one SQS queue per consumer. Each consumer gets its own copy, its own retry behavior and its own dead-letter queue.

How do I use SNS and SQS from TypeScript without wiring them by hand?

With Encore, you declare new Topic<OrderPlaced>("order-placed", { deliveryGuarantee: "at-least-once" }) and new Subscription(orderPlaced, "send-receipt", { handler }) in TypeScript. On AWS, Encore provisions the SNS topic and an SQS queue for the subscription with a dead-letter queue, and encore run runs the same flow locally.

Deploy to your cloud on AWS and Google Cloud
without managing infrastructure

Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.

~/orders — claude
Claude Code
Claude Code v2.1.180Opus 5.5~/orders