Amazon SNS Messaging Model Explained: Topics, Delivery & DLQs
The Amazon SNS messaging model is a fan-out system: you publish a message once to a topic and SNS delivers a copy to every matching subscriber in parallel. Understanding the details — the envelope structure, how filters apply, how retries and dead-letter queues work — is what separates systems that hold up in production from ones that silently lose events.
SNS as a broadcast hub
Think of it like a PA system
You speak into the microphone once and every speaker in the building plays it at the same time. No listener has to ask for it. SNS works the same way: you publish a message once, and every subscriber gets their own copy, pushed to them immediately.
This fan-out model is the core reason to reach for SNS. A single “order placed” event can trigger an inventory update, an email receipt, a fraud check, and a warehouse notification all at once, with no coupling between the publisher and those consumers.
For a broader introduction to topics, subscriptions, and supported protocols, start with the Amazon SNS Overview.
How it works
The path of a message through SNS follows the same sequence every time:
- Publish: your code calls
sns:Publishwith a topic ARN and a message body. The API call returns as soon as SNS accepts the message. Your publisher does not wait for delivery to complete. - Filter: SNS looks up all active subscriptions on the topic and evaluates any filter policies to decide which subscriptions should receive this message.
- Deliver: SNS delivers a copy to each matching subscription in parallel, using the protocol configured on that subscription (SQS, Lambda, HTTP/HTTPS, email, SMS, mobile push).
- Retry or DLQ: if delivery fails, SNS retries according to the protocol’s retry policy. If all retries are exhausted and a DLQ is configured, the message is sent there. Without a DLQ it is dropped.
Publisher
│
▼
SNS Topic ──[filter]──▶ SQS Queue (subscription 1)
│
├──[filter]──▶ Lambda function (subscription 2)
│
└──[filter]──▶ HTTP endpoint (subscription 3)This architecture is the foundation of event-driven systems in AWS. The publisher is fully decoupled from every downstream consumer.
What subscribers actually receive
SNS does not deliver your raw message body. It wraps it in a JSON envelope. Here is what an SQS queue or Lambda function actually receives:
{
"Type": "Notification",
"MessageId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"TopicArn": "arn:aws:sns:us-east-1:123456789012:order-events",
"Subject": "New Order",
"Message": "{\"orderId\": \"abc-123\", \"amount\": 49.99}",
"Timestamp": "2026-03-18T10:30:00.000Z",
"SignatureVersion": "1",
"Signature": "EXAMPLE...",
"SigningCertURL": "https://...",
"UnsubscribeURL": "https://...",
"MessageAttributes": {
"region": { "Type": "String", "Value": "EU" },
"orderType": { "Type": "String", "Value": "express" }
}
}The Message field holds exactly what you passed at publish time. If you published a JSON object, it arrives here as a string. Your consumer must call JSON.parse (or equivalent) to get back the original structure.
When consuming from an SQS queue subscribed to SNS, there are two levels of parsing. The SQS message body is the SNS envelope JSON above. You parse the SQS body to get the envelope, then parse the Message field inside it to get your original payload. Skipping the second parse is one of the most frequent beginner mistakes.
Message attributes and filter policies
Message attributes are metadata key-value pairs attached to a message at publish time, outside the message body. They support three data types: String, Number, and Binary.
aws sns publish \
--topic-arn arn:aws:sns:us-east-1:123456789012:order-events \
--message '{"orderId": "abc-123", "amount": 49.99}' \
--message-attributes '{
"region": {"DataType": "String", "StringValue": "EU"},
"orderType": {"DataType": "String", "StringValue": "express"}
}'Attribute-based filtering (default)
By default, SNS evaluates filter policies against message attributes. A subscription with the policy below only receives messages where region is EU and orderType is standard or express:
{
"region": ["EU"],
"orderType": ["standard", "express"]
}A message missing a required attribute, or with a non-matching value, is silently skipped for that subscription. Every other subscription is unaffected.
Message-body filtering
If your routing criteria live inside the message body rather than in separate attributes, you can change the filter scope. Set FilterPolicyScope to MessageBody on the subscription and SNS evaluates the filter policy against top-level JSON fields in the body instead.
Body filtering removes the need to duplicate routing data as attributes, which simplifies your publish calls. The trade-off: it only works on top-level JSON fields. Deeply nested values are not supported. If your schema nests the routing key several levels deep, stick with attributes.
Delivery, retries, and failure handling
Delivery behavior by subscriber type
- SQS: SNS writes the message to the queue. Once written, SQS owns it. Visibility timeouts, polling, and reprocessing are all managed on the SQS side independently.
- Lambda: SNS invokes the Lambda function using a synchronous invocation from its own perspective, meaning it waits for the function’s response to decide whether delivery succeeded. The original publisher is still decoupled — the
sns:PublishAPI call returned long before Lambda ran. See the AWS Lambda Overview for how Lambda executes internally. - HTTP/HTTPS: SNS posts to your endpoint. A 200-range response counts as success; anything else triggers a retry.
- Email / SMS / mobile push: SNS manages delivery directly. These protocols offer less control over retry behavior compared to SQS or Lambda.
Retry behavior
For HTTP/HTTPS subscriptions, SNS retries through several phases: immediate attempts, a pre-backoff phase, an exponential backoff phase, and a long post-backoff phase. The default policy is aggressive and can sustain retries spanning days. You can customize it per-subscription using a delivery policy JSON document.
For Lambda subscriptions, SNS retries a small number of times before giving up. If you need longer retry windows or batching, route through SQS first: SNS to SQS to Lambda. This pattern adds SQS visibility timeouts and DLQ support on top of SNS fan-out. See SNS Push vs SQS Pull for how to choose.
For a deeper look at diagnosing and recovering from delivery failures, see SNS message delivery failures in AWS.
Dead-letter queues
A DLQ is an SQS queue where SNS sends messages it could not deliver after all retries. You configure a DLQ per subscription, not per topic, so different subscriptions can handle failures differently.
# Create the DLQ
aws sqs create-queue --queue-name order-events-dlq
# Attach the DLQ to an existing SNS subscription
aws sns set-subscription-attributes \
--subscription-arn arn:aws:sns:us-east-1:123456789012:order-events:abc123 \
--attribute-name RedrivePolicy \
--attribute-value '{"deadLetterTargetArn": "arn:aws:sqs:us-east-1:123456789012:order-events-dlq"}'Messages that land in the DLQ carry metadata about why delivery failed. Your team can investigate and replay them once the underlying issue is resolved.
Set a CloudWatch alarm on the NumberOfMessagesSent metric of your DLQ. The moment that number goes above zero, something downstream is failing. Catching it immediately beats discovering it the next morning.
Lambda as an SNS subscriber
Lambda is the most common SNS target in event-driven workloads. Here is a Python handler that correctly unpacks an SNS event:
import json
def handler(event, context):
for record in event['Records']:
sns_message = record['Sns']
# Message arrives as a string, so parse it
payload = json.loads(sns_message['Message'])
attributes = sns_message.get('MessageAttributes', {})
region = attributes.get('region', {}).get('Value')
print(f"Order ID: {payload['orderId']}, Region: {region}")event[‘Records’] typically contains one entry. SNS invokes Lambda once per message, not in batches.
The Lambda function’s resource-based policy must grant lambda:InvokeFunction to the SNS service principal (sns.amazonaws.com), scoped to the source topic ARN. A common mistake is setting sns:Publish on the Lambda policy instead. That permission does not exist on Lambda and SNS will silently fail to invoke the function. Creating the subscription via the console or CLI adds the correct permission automatically. In Terraform or CloudFormation, add the aws_lambda_permission resource explicitly.
FIFO topics: ordering and deduplication
Standard SNS topics make no ordering guarantees and can occasionally deliver duplicates in rare failure scenarios. If strict message order matters, use a FIFO SNS topic.
FIFO topics guarantee delivery order per message group ID and deduplicate messages within a five-minute window. Throughput is significantly lower than standard topics, so FIFO is a deliberate trade-off, not a sensible default.
FIFO topics support SQS FIFO queues, Lambda, and Amazon Data Firehose as subscribers. Email, SMS, HTTP/HTTPS, and mobile push are not supported.
SNS FIFO deduplication prevents the same message being delivered twice to subscribers. It does not guarantee that your consumer processes it exactly once. If a Lambda function fails mid-execution and retries, your application logic still needs to handle that case idempotently.
When to use SNS
- Fan-out to multiple systems: one event needs to reach several independent consumers simultaneously.
- Fire-and-forget notifications: trigger downstream work without the publisher waiting for results.
- Decoupling microservices: services should react to domain events without direct API coupling to one another.
- Mobile push and SMS: SNS has native support for APNs, FCM, and global SMS delivery.
- Event-driven Lambda pipelines: triggering compute in response to domain events without polling.
When not to use SNS
- You only have one consumer: SQS alone is simpler and cheaper when there is no fan-out requirement.
- You need message retention or replay: SNS does not store messages. If a subscriber is offline, the message is gone. Use SQS (14-day retention) or Kinesis Data Streams for durable replay.
- Complex content-based routing: Amazon EventBridge offers richer pattern matching on event content, schema registries, and cross-account event buses that SNS cannot match.
- Job queues or work distribution: SQS lets competing consumers share load from a single queue. SNS pushes to all subscribers, which is the wrong model for distributing work.
SNS vs SQS
Both are AWS messaging services, but they solve different problems. For the full decision guide, see SNS vs SQS in AWS. The short version:
| Characteristic | SNS | SQS |
|---|---|---|
| Delivery model | Push to all subscribers | Pull by consumers |
| Fan-out | Built in: one publish reaches many | No — use SNS in front of SQS queues |
| Message retention | None — delivered or dropped | Up to 14 days |
| Consumer pace | Consumers cannot control timing | Consumers poll at their own rate |
| Buffering | No | Yes — handles backpressure naturally |
You do not have to choose one or the other. The SNS to SQS fan-out pattern is extremely common: SNS delivers to multiple SQS queues and each queue buffers messages independently for its own consumer. You get fan-out from SNS and durability, backpressure handling, and consumer-controlled polling from SQS.
Common mistakes
Not parsing the SNS envelope in Lambda. Your payload is inside
event[‘Records’][0][‘Sns’][‘Message’]as a string. Reading fields directly fromrecord[‘Sns’]without parsingMessagegives you nothing.Setting
sns:Publishon the Lambda resource policy. The correct permission islambda:InvokeFunction. SNS publishes to topics; it invokes Lambda functions. The two actions are completely different andsns:Publishon a Lambda resource policy will not work.Skipping DLQ configuration. Without a DLQ, messages that cannot be delivered vanish silently. Always attach a DLQ to subscriptions carrying important events.
Filtering on body fields without setting FilterPolicyScope. By default, SNS evaluates filter policies against message attributes, not body content. If your filter references body fields and you have not set
FilterPolicyScopetoMessageBody, the filter will not match anything as expected.Assuming FIFO topics support all subscriber types. FIFO topics only support SQS FIFO queues, Lambda, and Firehose. Adding an email or HTTP/HTTPS subscription to a FIFO topic will fail at creation time.
Treating FIFO deduplication as exactly-once processing. SNS deduplication prevents the same message being delivered twice at the SNS level. It does not stop your consumer from processing a message more than once if the consumer itself retries. Build idempotent consumers regardless.
Summary
- SNS is a fan-out hub: one publish reaches all matching subscribers in parallel
- Subscribers receive a JSON envelope — parse the
Messagefield to get your original payload - Filter policies default to message-attribute matching; set
FilterPolicyScope: MessageBodyto filter on body fields - SNS invokes Lambda synchronously from its own side; the publisher is still fully decoupled
- Lambda resource policies need
lambda:InvokeFunction, notsns:Publish - Configure a DLQ per subscription to capture messages that exhaust all retries
- FIFO topics add ordered, deduplicated delivery but support fewer subscriber types and lower throughput
- Use SNS for fan-out, SQS for buffering, EventBridge for complex content-based routing
Frequently asked questions
What does an SNS message look like when it arrives at a subscriber?
SNS wraps your payload in a JSON envelope before delivery. The envelope includes a MessageId, Timestamp, TopicArn, Subject (optional), Message (your original content as a string), and any MessageAttributes you set. If you published a JSON object, it arrives inside the Message field as a JSON string and your consumer must parse it again.
What happens if SNS cannot deliver a message?
SNS retries failed deliveries according to the retry policy for that protocol. HTTP/HTTPS endpoints get multiple retry phases with exponential backoff that can span days. Lambda subscriptions get a small number of retry attempts. If retries are exhausted and a DLQ is configured on the subscription, the message lands in the DLQ. Without a DLQ, it is silently dropped.
Can SNS filter on message body fields, or only on message attributes?
SNS supports both. The default mode evaluates filter policies against message attributes. If you set FilterPolicyScope to MessageBody on the subscription, SNS evaluates the filter policy against the top-level JSON fields in the message body instead. Deeply nested fields are not supported in body-based filtering.
What permission does SNS need to invoke a Lambda function?
The Lambda function's resource-based policy must grant lambda:InvokeFunction to the SNS service principal (sns.amazonaws.com), with a condition on the source topic ARN. When you create the subscription via the console or CLI, AWS adds this automatically. In Terraform or CloudFormation, you must add the permission resource explicitly.
Does FIFO guarantee exactly-once processing end to end?
FIFO topics deduplicate messages so the same message is not delivered twice to subscribers. However, exactly-once processing end to end also requires your consumer to be idempotent. If Lambda processing fails and retries, your application logic must handle that gracefully.
Does SNS deliver messages in the same order they were published?
Not on standard topics. SNS makes no ordering guarantees for standard topics and can occasionally deliver the same message more than once. If order matters, use a FIFO topic with message group IDs.
Can I add a filter policy to an existing subscription?
Yes. You can add or update a filter policy on an existing subscription using sns:SetSubscriptionAttributes. The change takes effect immediately for new messages.
What is the maximum SNS message size?
SNS messages are limited to 256 KB. For larger payloads, publish a reference such as an S3 object key in the SNS message and let subscribers retrieve the full payload from S3 directly.
Can one SNS topic have both standard and FIFO subscribers?
No. A topic is either standard or FIFO and you choose at creation time. A FIFO topic only accepts FIFO-compatible subscriber types: SQS FIFO queues, Lambda, and Firehose.
How do I know if SNS is dropping messages?
Monitor the NumberOfNotificationsFailed CloudWatch metric on the topic, and set alarms on the NumberOfMessagesSent metric of any DLQ attached to subscriptions. Those two signals together tell you whether delivery is failing and whether failed messages are being captured.