Introduction to Amazon SNS
Amazon Simple Notification Service (SNS) is a fully managed pub/sub messaging service that enables decoupled communication between microservices, distributed systems, and serverless applications. It acts as a central hub where publishers send messages to topics, and subscribers receive those messages through various endpoints such as AWS Lambda, SQS queues, HTTP endpoints, email, and mobile push notifications.
While SNS is straightforward to get started with, running it effectively at scale requires careful attention to three critical dimensions: cost optimization, security hardening, and performance tuning. Neglecting any of these areas can lead to unexpected billing surprises, data exposure, or message delivery bottlenecks that undermine your application's reliability.
Why Best Practices Matter
SNS is a pay-per-use service, which means costs scale directly with your message volume and delivery patterns. A poorly designed topic architecture can multiply your costs several times over. On the security side, SNS topics often carry sensitive payloads, and misconfigured access policies have historically been a leading cause of data leaks in AWS environments. Performance matters because SNS sits on the critical path of many event-driven architectures — slow or unreliable delivery cascades into application-level failures.
By following established best practices, you can build messaging infrastructure that is predictable in cost, resistant to misuse, and capable of handling high throughput without degradation.
Getting Started with SNS
Before diving into best practices, let's establish a baseline understanding of how SNS works. You create a topic, configure subscriptions, and publish messages. Here is a minimal example using the AWS CLI:
# Create a standard topic
aws sns create-topic --name order-events
# Subscribe an SQS queue to the topic
aws sns subscribe \
--topic-arn arn:aws:sns:us-east-1:123456789012:order-events \
--protocol sqs \
--notification-endpoint arn:aws:sqs:us-east-1:123456789012:order-processing
# Publish a message
aws sns publish \
--topic-arn arn:aws:sns:us-east-1:123456789012:order-events \
--message '{"orderId":"12345","status":"created"}'
For programmatic access, the AWS SDK for Python (boto3) provides a clean interface:
import boto3
import json
sns = boto3.client('sns', region_name='us-east-1')
# Create a topic
response = sns.create_topic(Name='order-events')
topic_arn = response['TopicArn']
# Publish a message with attributes
sns.publish(
TopicArn=topic_arn,
Message=json.dumps({
'orderId': '12345',
'status': 'created',
'amount': 99.99
}),
MessageAttributes={
'eventType': {
'DataType': 'String',
'StringValue': 'ORDER_CREATED'
},
'priority': {
'DataType': 'Number',
'StringValue': '1'
}
}
)
Cost Optimization Best Practices
Choose FIFO Topics Only When Necessary
SNS offers two topic types: Standard and FIFO. Standard topics are significantly cheaper and offer higher throughput. FIFO topics guarantee ordering and exactly-once delivery but cost more per million requests and have lower throughput limits. Use FIFO topics only when your business logic genuinely requires ordering or deduplication.
- Standard topic: $0.50 per million requests (varies by region)
- FIFO topic: Higher per-request cost plus message group overhead
Use Message Filtering to Reduce Delivery Volume
One of the most effective cost reduction techniques is SNS message filtering. Without filters, every subscriber receives every message, even if only a subset is relevant. Filtering happens server-side at no additional cost, and it prevents unnecessary downstream processing in Lambda functions or SQS consumers that you also pay for.
import boto3
sns = boto3.client('sns', region_name='us-east-1')
# Subscribe with a filter policy
sns.subscribe(
TopicArn='arn:aws:sns:us-east-1:123456789012:order-events',
Protocol='sqs',
Endpoint='arn:aws:sqs:us-east-1:123456789012:high-value-orders',
Attributes={
'FilterPolicy': json.dumps({
'eventType': ['ORDER_CREATED'],
'amount': [{'numeric': ['>', 1000]}]
}),
'FilterPolicyScope': 'MessageAttributes'
}
)
With this filter policy, the subscriber only receives messages where the eventType attribute is ORDER_CREATED and the amount attribute exceeds 1000. All other messages are filtered out before delivery, saving both SNS delivery costs and downstream compute costs.
Batch Publish Operations
If your application publishes many messages in rapid succession, use the batch publish API to reduce the number of API calls. Each API call has a cost, so batching ten messages into a single call reduces your request charges by up to 90% for that workload.
import boto3
import json
sns = boto3.client('sns', region_name='us-east-1')
topic_arn = 'arn:aws:sns:us-east-1:123456789012:order-events'
# Batch publish up to 10 messages
response = sns.publish_batch(
TopicArn=topic_arn,
PublishBatchRequestEntries=[
{
'Id': 'msg-1',
'Message': json.dumps({'orderId': '1001', 'status': 'created'}),
'MessageAttributes': {
'eventType': {'DataType': 'String', 'StringValue': 'ORDER_CREATED'}
}
},
{
'Id': 'msg-2',
'Message': json.dumps({'orderId': '1002', 'status': 'shipped'}),
'MessageAttributes': {
'eventType': {'DataType': 'String', 'StringValue': 'ORDER_SHIPPED'}
}
}
]
)
print(f"Success: {response['Successful']}")
print(f"Failed: {response.get('Failed', [])}")
Monitor and Archive Unused Topics
Topics that no longer have active subscribers still incur costs if messages are published to them. Regularly audit your topic inventory and delete topics that are no longer in use. Use CloudWatch metrics such as NumberOfMessagesPublished and NumberOfNotificationsDelivered to identify dormant topics.
Security Best Practices
Apply Least Privilege Topic Policies
The most common SNS security mistake is using overly permissive topic access policies. Never use a wildcard principal. Instead, restrict access to specific IAM roles, accounts, or services. Here is an example of a well-scoped topic policy:
{
"Version": "2012-10-17",
"Id": "order-events-policy",
"Statement": [
{
"Sid": "AllowPublisherRole",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/OrderServiceRole"
},
"Action": "sns:Publish",
"Resource": "arn:aws:sns:us-east-1:123456789012:order-events",
"Condition": {
"StringEquals": {
"aws:SourceAccount": "123456789012"
}
}
},
{
"Sid": "AllowSubscriberRole",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/OrderProcessorRole"
},
"Action": [
"sns:Subscribe",
"sns:Receive"
],
"Resource": "arn:aws:sns:us-east-1:123456789012:order-events"
}
]
}
Enable Server-Side Encryption
Always enable server-side encryption (SSE) on your SNS topics using AWS KMS. This protects message payloads at rest and in transit between SNS and subscribers. Without encryption, anyone with access to the underlying storage layer could potentially read message contents.
import boto3
sns = boto3.client('sns', region_name='us-east-1')
# Create a topic with SSE-KMS encryption
response = sns.create_topic(
Name='sensitive-order-events',
Attributes={
'KmsMasterKeyId': 'arn:aws:kms:us-east-1:123456789012:alias/sns-key'
}
)
print(f"Encrypted topic ARN: {response['TopicArn']}")
When using SSE-KMS, ensure that subscribers have kms:Decrypt and kms:GenerateDataKey permissions for the KMS key. This is a common source of delivery failures that are easy to overlook during initial setup.
Restrict Subscription Protocols
By default, SNS allows subscriptions via email, HTTP, HTTPS, SMS, and several AWS service protocols. For production topics, restrict the allowed protocols using the topic policy to prevent data from being sent to unmonitored endpoints:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "RestrictSubscriptionProtocols",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/SubscriberRole"
},
"Action": "sns:Subscribe",
"Resource": "arn:aws:sns:us-east-1:123456789012:order-events",
"Condition": {
"StringEquals": {
"sns:Protocol": ["sqs", "lambda"]
}
}
}
]
}
Avoid Sending Sensitive Data in Message Bodies
Even with encryption, it is a best practice to avoid including sensitive data such as passwords, API keys, or personally identifiable information directly in SNS message bodies. Instead, pass references to secure storage locations and let consumers retrieve the sensitive data through authenticated channels.
Performance Best Practices
Understand Throughput Limits
Standard SNS topics support high throughput with soft limits that can be increased through quota requests. FIFO topics are limited to 300 messages per second per topic by default, or 300 messages per second per message group. Design your architecture with these limits in mind, and use message groups strategically to parallelize FIFO processing.
Use Fan-Out with SQS for Reliable Delivery
The fan-out pattern — publishing to an SNS topic that fans out to multiple SQS queues — is the recommended approach for reliable, decoupled delivery. SNS retries HTTP and Lambda endpoints a limited number of times, but SQS queues persist messages until consumers process them. This pattern also allows each consumer to process messages at its own pace.
import boto3
import json
sns = boto3.client('sns')
sqs = boto3.client('sqs')
# Create queues
inventory_queue = sqs.create_queue(QueueName='inventory-updates')['QueueUrl']
billing_queue = sqs.create_queue(QueueName='billing-events')['QueueUrl']
# Get queue ARNs
inventory_arn = sqs.get_queue_attributes(
QueueUrl=inventory_queue,
AttributeNames=['QueueArn']
)['Attributes']['QueueArn']
billing_arn = sqs.get_queue_attributes(
QueueUrl=billing_queue,
AttributeNames=['QueueArn']
)['Attributes']['QueueArn']
# Allow SNS to write to the queues
for queue_arn in [inventory_arn, billing_arn]:
sqs.set_queue_attributes(
QueueUrl=f"https://sqs.us-east-1.amazonaws.com/123456789012/{queue_arn.split(':')[-1]}",
Attributes={
'Policy': json.dumps({
'Version': '2012-10-17',
'Statement': [{
'Effect': 'Allow',
'Principal': {'Service': 'sns.amazonaws.com'},
'Action': 'sqs:SendMessage',
'Resource': queue_arn,
'Condition': {
'ArnEquals': {
'aws:SourceArn': 'arn:aws:sns:us-east-1:123456789012:order-events'
}
}
}]
})
}
)
# Subscribe both queues to the topic
for queue_arn in [inventory_arn, billing_arn]:
sns.subscribe(
TopicArn='arn:aws:sns:us-east-1:123456789012:order-events',
Protocol='sqs',
Endpoint=queue_arn
)
Optimize Message Size
SNS messages can be up to 256 KB in size, but larger messages take longer to serialize, transmit, and process. Keep messages as small as possible by including only essential data. For large payloads, use the SNS extended client library with Amazon S3 to offload the payload and send only a reference through SNS.
Set Appropriate Delivery Policies
For HTTP and HTTPS subscriptions, configure delivery retry policies that match your application's tolerance for latency and retries. The default retry policy may not be suitable for all use cases. You can customize the number of retries, the delay between retries, and the backoff function:
import boto3
import json
sns = boto3.client('sns', region_name='us-east-1')
subscription_arn = 'arn:aws:sns:us-east-1:123456789012:order-events:abc123-def456'
sns.set_subscription_attributes(
SubscriptionArn=subscription_arn,
AttributeName='DeliveryPolicy',
AttributeValue=json.dumps({
'healthyRetryPolicy': {
'minDelayTarget': 10,
'maxDelayTarget': 600,
'numRetries': 5,
'numMaxDelayRetries': 3,
'backoffFunction': 'exponential'
},
'throttlePolicy': {
'maxReceivesPerSecond': 10
}
})
)
Use Cross-Account Delivery Carefully
When delivering messages across AWS accounts, ensure that both the topic policy and the subscription policy grant the necessary permissions. Cross-account delivery adds latency and complexity, so only use it when the consumer genuinely lives in a different account. For same-account consumers, keep everything within a single account to minimize overhead.
Monitoring and Observability
No best practice discussion is complete without monitoring. SNS integrates with CloudWatch to provide metrics such as NumberOfMessagesPublished, NumberOfNotificationsDelivered, NumberOfNotificationsFailed, and PublishSize. Set up alarms for failed deliveries and publish anomalies:
import boto3
cloudwatch = boto3.client('cloudwatch', region_name='us-east-1')
cloudwatch.put_metric_alarm(
AlarmName='sns-delivery-failures',
AlarmDescription='Alert when SNS delivery failures exceed threshold',
MetricName='NumberOfNotificationsFailed',
Namespace='AWS/SNS',
Statistic='Sum',
Period=300,
EvaluationPeriods=1,
Threshold=10,
ComparisonOperator='GreaterThanThreshold',
Dimensions=[
{
'Name': 'TopicName',
'Value': 'order-events'
}
],
AlarmActions=[
'arn:aws:sns:us-east-1:123456789012:ops-alerts'
]
)
Additionally, enable SNS delivery status logging for HTTP, Lambda, and SQS subscriptions. Delivery logs are written to CloudWatch Logs and provide detailed information about each delivery attempt, including status codes, latency, and error messages. This is invaluable for debugging delivery issues in production.
Conclusion
Amazon SNS is a powerful and flexible messaging service, but getting the most out of it requires intentional design across cost, security, and performance dimensions. By choosing the right topic type for each workload, applying message filtering to reduce unnecessary deliveries, and batching publishes where possible, you can keep costs predictable even at high scale. On the security side, least-privilege topic policies, KMS encryption, and restricted subscription protocols form a defense-in-depth strategy that protects your message payloads. Finally, performance best practices such as the fan-out pattern with SQS, optimized message sizes, and tuned delivery retry policies ensure that your messaging infrastructure remains reliable under load. Combine these practices with robust CloudWatch monitoring and delivery logging, and you will have an SNS architecture that is both production-ready and cost-efficient.