Notifications with AWS SNS
AWS Simple Notification Service (SNS) is a pub/sub messaging service. It lets you send a single message to multiple recipients or endpoints simultaneously — unlike SQS (one message, one consumer), SNS fans out to many.
Common use cases:
- Sending SMS messages (OTPs, alerts)
- Fan-out: one event triggers multiple actions (notify a user, update a cache, log to analytics)
- Push notifications via mobile endpoints
- Alerting on-call engineers when your system breaks
SNS vs SES vs SQS
| Service | What it's for |
|---|---|
| SES | Sending formatted emails (transactional, marketing) |
| SNS | SMS, multi-channel fan-out, push notifications |
| SQS | Reliable message queue for background jobs (one consumer) |
Install
- npm
- Yarn
- pnpm
- Bun
npm install @aws-sdk/client-sns dotenv
yarn add @aws-sdk/client-sns dotenv
pnpm add @aws-sdk/client-sns dotenv
bun add @aws-sdk/client-sns dotenv
Configure the SNS Client
import { SNSClient } from "@aws-sdk/client-sns";
const sns = new SNSClient({
region: process.env.AWS_REGION,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
},
});
export default sns;
Sending an SMS
SNS can send SMS to any phone number worldwide without setting up a carrier account. This is commonly used for OTPs and security alerts.
import { PublishCommand } from "@aws-sdk/client-sns";
import sns from "../config/sns.js";
export async function sendSms(phoneNumber, message) {
// Phone number must be in E.164 format: +15551234567
const command = new PublishCommand({
Message: message,
PhoneNumber: phoneNumber,
MessageAttributes: {
"AWS.SNS.SMS.SMSType": {
DataType: "String",
StringValue: "Transactional", // Higher delivery priority than "Promotional"
},
"AWS.SNS.SMS.SenderID": {
DataType: "String",
StringValue: "MyApp", // Optional: shown as sender on supported carriers
},
},
});
return sns.send(command);
}
SMS in an Express route
import { sendSms } from "../utils/sendSms.js";
router.post("/send-otp", async (req, res) => {
const { phone } = req.body;
const otp = Math.floor(100000 + Math.random() * 900000).toString();
try {
await sendSms(phone, `Your OTP is: ${otp}. It expires in 5 minutes.`);
// Store hashed OTP in your database with expiry...
res.json({ message: "OTP sent" });
} catch (err) {
res.status(500).json({ message: "Failed to send OTP", error: err.message });
}
});
By default, SNS SMS is in sandbox mode — you can only send to verified phone numbers. Go to AWS Console → SNS → Text messaging (SMS) → Sandbox destination phone numbers to add and verify numbers. Request production access when ready.
Topics: Fan-Out to Multiple Subscribers
An SNS Topic is a named channel. You publish one message to a topic, and SNS delivers it to all subscribers simultaneously. Subscribers can be:
- SQS queues (for background processing)
- Lambda functions (for event-driven logic)
- HTTP/HTTPS endpoints (webhooks to your services)
- Email addresses
- SMS phone numbers
Create a Topic
- Go to AWS Console → SNS → Topics → Create topic
- Choose Standard type
- Name it (e.g.,
order-events) - Copy the Topic ARN
Add to .env:
SNS_ORDER_TOPIC_ARN=arn:aws:sns:us-east-1:123456789:order-events
Publish to a Topic
import { PublishCommand } from "@aws-sdk/client-sns";
import sns from "../config/sns.js";
export async function publishEvent(topicArn, eventType, payload) {
const command = new PublishCommand({
TopicArn: topicArn,
Message: JSON.stringify({ eventType, payload, timestamp: new Date().toISOString() }),
Subject: eventType,
MessageAttributes: {
eventType: {
DataType: "String",
StringValue: eventType,
},
},
});
return sns.send(command);
}
Express route that publishes an event
import express from "express";
import { publishEvent } from "../utils/publishEvent.js";
const router = express.Router();
router.post("/", async (req, res) => {
const { userId, items, total } = req.body;
try {
// Save order to database...
const order = { id: "ord_123", userId, items, total, status: "created" };
// Fan out the event to all subscribers:
// - SQS queue processes the order (send confirmation email, update inventory)
// - Lambda updates analytics
// - Another SQS queue notifies the warehouse
await publishEvent(process.env.SNS_ORDER_TOPIC_ARN, "ORDER_CREATED", order);
res.status(201).json(order);
} catch (err) {
res.status(500).json({ message: "Failed to create order" });
}
});
export default router;
Subscribe an SQS Queue to an SNS Topic
A common pattern is SNS → SQS: SNS delivers the event to an SQS queue, and your worker processes it at its own pace. This gives you both fan-out (SNS) and reliability (SQS).
- Go to SNS → Your topic → Create subscription
- Protocol: SQS
- Endpoint: your SQS queue ARN
- Click Create subscription
Also add an SQS Access Policy so SNS is allowed to write to your queue:
{
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "sns.amazonaws.com" },
"Action": "sqs:SendMessage",
"Resource": "arn:aws:sqs:us-east-1:123456789:my-queue",
"Condition": {
"ArnEquals": {
"aws:SourceArn": "arn:aws:sns:us-east-1:123456789:order-events"
}
}
}
]
}
IAM Permissions for SNS
{
"Effect": "Allow",
"Action": [
"sns:Publish",
"sns:Subscribe",
"sns:ListTopics"
],
"Resource": "*"
}
For production, restrict Resource to specific topic ARNs.
Key Takeaways
- SNS is for fan-out (one message → many consumers); SQS is for reliable queuing (one message → one consumer)
- Use
TransactionalSMS type for OTPs — it has higher delivery priority thanPromotional - Phone numbers must be in E.164 format (
+15551234567) - The SNS → SQS pattern combines fan-out with reliable processing — a very common production architecture
- Request production SMS access before going live (SNS sandbox restricts to verified numbers)