AWS SNS

By Vishvesh Patel · DevOps EngineerUpdated September 16, 2026

Publish-subscribe messaging on AWS: the SNS-to-SQS fan-out pattern, raw message delivery, filter policies and why email subscriptions bite.

Amazon SNS

SNS is publish-subscribe. A publisher sends one message to a topic, and SNS delivers a copy to every subscriber: a queue, a Lambda function, an HTTPS endpoint, an email address. The publisher never knows who is listening, which is exactly what makes it useful for letting several services react to the same event.

The critical difference from SQS: SNS pushes and does not store. A subscriber that is unavailable when the message is published misses it, aside from limited retries. That single property drives most real SNS design, and it is why the fan-out pattern below exists.

Topics and subscriptions

aws sns create-topic --name order-events
{ "TopicArn": "arn:aws:sns:eu-west-1:123456789012:order-events" }
aws sns subscribe --topic-arn "$TOPIC" --protocol sqs \
  --notification-endpoint "arn:aws:sqs:eu-west-1:123456789012:orders-worker"

aws sns publish --topic-arn "$TOPIC" \
  --message '{"orderId":"A-4821","status":"placed"}' \
  --message-attributes '{"eventType":{"DataType":"String","StringValue":"placed"}}'
aws sns list-subscriptions-by-topic --topic-arn "$TOPIC" \
  --query 'Subscriptions[].[Protocol,Endpoint,SubscriptionArn]' --output table

A subscription showing PendingConfirmation instead of an ARN has not been confirmed and receives nothing. For email and HTTPS endpoints, SNS sends a confirmation request the owner must accept. An email subscription silently delivering nothing is almost always this, and the confirmation mail routinely lands in spam. SQS and Lambda subscriptions confirm automatically.

The fan-out pattern

This is the arrangement worth internalising, because it fixes SNS's lack of durability:

                    ┌──▶ SQS queue ──▶ billing worker
publisher ──▶ SNS ──┼──▶ SQS queue ──▶ search indexer
                    └──▶ SQS queue ──▶ analytics worker

Each consumer gets its own queue, so each gets its own durable copy, its own retry behaviour and its own dead-letter queue. A consumer can be down for an hour and catch up; one consumer failing does not affect the others; and adding a fourth is a new queue and a new subscription rather than a change to the publisher.

Subscribing Lambda functions directly to SNS is simpler and gives up all of that. Use direct subscription for genuinely fire-and-forget notifications, and a queue in between whenever the work matters.

The queue needs a policy allowing SNS to write to it:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "Service": "sns.amazonaws.com" },
    "Action": "sqs:SendMessage",
    "Resource": "arn:aws:sqs:eu-west-1:123456789012:orders-worker",
    "Condition": { "ArnEquals": { "aws:SourceArn": "arn:aws:sns:eu-west-1:123456789012:order-events" } }
  }]
}

Missing that policy is the most common fan-out failure. The subscription is created successfully, publishing succeeds, and messages never arrive, because SNS is being denied and nothing in the publish call reveals it.

Raw message delivery

By default SNS wraps your payload in an envelope of SNS metadata:

{
  "Type": "Notification",
  "MessageId": "8f3c1e2a-4b7d-11ef-9c2a-0242ac120002",
  "TopicArn": "arn:aws:sns:eu-west-1:123456789012:order-events",
  "Message": "{\"orderId\":\"A-4821\"}",
  "Timestamp": "2026-09-16T09:14:22.481Z"
}

Your actual message is a JSON string inside the Message field, so consumers must parse twice. Worse, message attributes move into the envelope, which means SQS-side filtering on them stops working.

aws sns set-subscription-attributes \
  --subscription-arn "$SUB_ARN" \
  --attribute-name RawMessageDelivery --attribute-value true

With raw delivery the subscriber receives the payload exactly as published. Enable it on SQS and HTTPS subscriptions unless you specifically need the envelope's metadata. The double-encoded JSON is a frequent source of "the message arrived but my parser broke".

Filter policies

Rather than publishing to several topics, publish everything to one and let each subscription choose what it wants:

aws sns set-subscription-attributes \
  --subscription-arn "$SUB_ARN" \
  --attribute-name FilterPolicy \
  --attribute-value '{"eventType":["placed","cancelled"],"amount":[{"numeric":[">",100]}]}'

Filtering happens in SNS, so unmatched messages are never delivered and never billed to the subscriber. By default the policy is evaluated against message attributes, not the body. To filter on body contents, switch the scope:

aws sns set-subscription-attributes --subscription-arn "$SUB_ARN" \
  --attribute-name FilterPolicyScope --attribute-value MessageBody

A filter policy that matches nothing is indistinguishable from a broken subscription, so check NumberOfNotificationsFilteredOut in CloudWatch when messages go missing.

FIFO topics

aws sns create-topic --name order-events.fifo \
  --attributes FifoTopic=true,ContentBasedDeduplication=true

Like FIFO queues, the name must end in .fifo, and ordering is guaranteed per message group rather than per topic. A FIFO topic can only deliver to FIFO SQS queues, not Lambda, not HTTPS, not email. Publishing requires a MessageGroupId.

Delivery retries

SNS retry behaviour depends entirely on the protocol, which is worth knowing before you rely on it:

ProtocolRetries
SQS, LambdaExtensive, over hours
HTTPS/HTTPConfigurable; default gives up relatively quickly
Email, SMSBest effort, no guarantee

For HTTPS endpoints, set a delivery policy and a dead-letter queue on the subscription, or a brief outage at your endpoint silently loses messages:

aws sns set-subscription-attributes --subscription-arn "$SUB_ARN" \
  --attribute-name RedrivePolicy \
  --attribute-value '{"deadLetterTargetArn":"arn:aws:sqs:eu-west-1:123456789012:sns-dlq"}'

Common errors

AuthorizationError: User ... is not authorized to perform: SNS:Publish on resource ...

The publisher's IAM policy, or the topic's own access policy, does not permit publishing. Both are checked.

InvalidParameter: Invalid parameter: Message Structure - No default entry in JSON message body

You passed --message-structure json with a payload lacking a default key. Every protocol-specific message object needs default as a fallback.

Subscription is pending confirmation

The endpoint owner has not accepted. Re-request with aws sns confirm-subscription, and check the spam folder for email endpoints.

Quick reference

TaskCommand
Create a topicaws sns create-topic --name <name>
Subscribe a queueaws sns subscribe --protocol sqs --notification-endpoint <arn>
Publishaws sns publish --topic-arn "$T" --message '…'
List subscriptionsaws sns list-subscriptions-by-topic --topic-arn "$T"
Enable raw deliveryset-subscription-attributes --attribute-name RawMessageDelivery
Set a filterset-subscription-attributes --attribute-name FilterPolicy
Attach a DLQset-subscription-attributes --attribute-name RedrivePolicy
Unsubscribeaws sns unsubscribe --subscription-arn <arn>

Key takeaways

  • SNS pushes and does not store. An absent subscriber misses the message.
  • Fan out through SQS when delivery matters. Each consumer gets a durable copy and its own DLQ.
  • The queue needs a policy allowing sns.amazonaws.com, or messages vanish with no error.
  • Turn on raw message delivery unless you need the SNS envelope; otherwise consumers parse twice.
  • Filter policies read message attributes by default, not the body. Change FilterPolicyScope for that.
  • FIFO topics deliver only to FIFO queues, and need a MessageGroupId.
  • HTTPS subscriptions need a delivery policy and a DLQ; their retry window is short.
  • PendingConfirmation means no delivery at all. Check it first when email subscriptions go quiet.

Check your understanding

Exercise

A fan-out that delivers nothing

An SNS topic is subscribed to an SQS queue. The subscription exists with a real ARN, publish calls return a MessageId, and the queue stays empty. What is the most likely cause?

Exercise

Choosing SNS, SQS, or both

Three services must each react to every "order placed" event. Each may be redeployed independently and must not lose events while restarting. What design fits?


Frequently Asked Questions

What is the difference between SNS and SQS?

SNS is push-based publish-subscribe: one message goes to every subscriber immediately and is not retained for a subscriber that is absent. SQS is pull-based queuing: a message waits until a consumer reads and deletes it, and only one consumer gets it. So SNS is for notifying several parties about an event, and SQS is for handing work to a pool of workers. Combining them, SNS fanning out to several SQS queues, gives you both broadcast and durability, and is the usual production pattern.

What is the SNS to SQS fan-out pattern?

Publishing to a single SNS topic that has several SQS queues subscribed, one per consuming service. Each consumer gets its own durable copy, so it can be offline during a deploy and catch up afterwards; each has independent retries and its own dead-letter queue; and adding a consumer means creating a queue and a subscription without touching the publisher. It is the standard way to let multiple services react to one event without coupling them together.

Why is my SNS to SQS subscription not delivering messages?

Nine times out of ten the queue's access policy does not allow sns.amazonaws.com to call sqs:SendMessage on it. Publishing and delivering are authorised separately, so the publish succeeds and returns a MessageId while delivery is silently denied. Add a queue policy granting the SNS service principal, ideally with an aws:SourceArn condition naming the topic, and check the topic's NumberOfNotificationsFailed metric to confirm that was the problem.

What is raw message delivery in SNS?

By default SNS wraps your payload in a JSON envelope containing Type, MessageId, TopicArn and a Message field holding your payload as an escaped string, so consumers have to parse twice, and message attributes move into the envelope where SQS-side filtering cannot see them. Setting RawMessageDelivery to true on an SQS or HTTPS subscription delivers the payload exactly as published. Enable it unless you specifically need the envelope metadata.

How do SNS filter policies work?

A filter policy on a subscription describes which messages that subscriber wants, and SNS evaluates it before delivering, so unmatched messages are never sent and never billed. This lets one topic serve many consumers with different interests instead of maintaining a topic per event type. By default the policy is matched against message attributes, not the body; set FilterPolicyScope to MessageBody to filter on content. Check NumberOfNotificationsFilteredOut when messages seem to disappear.

Why hasn't my email subscription received anything?

Almost certainly because it was never confirmed. SNS sends a confirmation request to email and HTTPS endpoints that the owner must accept, and until then list-subscriptions-by-topic shows PendingConfirmation in place of a subscription ARN and nothing is delivered. The confirmation email frequently lands in spam. SQS and Lambda subscriptions confirm automatically, which is why this only bites on the protocols where a human is involved.

Continue Learning

Explore Related Topics

When it breaks

Related Resources