Skip to main content

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

ServiceWhat it's for
SESSending formatted emails (transactional, marketing)
SNSSMS, multi-channel fan-out, push notifications
SQSReliable message queue for background jobs (one consumer)

Install

npm install @aws-sdk/client-sns dotenv

Configure the SNS Client

config/sns.js
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.

utils/sendSms.js
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

routes/auth.js
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 });
}
});
SMS sandbox

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

  1. Go to AWS Console → SNS → Topics → Create topic
  2. Choose Standard type
  3. Name it (e.g., order-events)
  4. Copy the Topic ARN

Add to .env:

.env
SNS_ORDER_TOPIC_ARN=arn:aws:sns:us-east-1:123456789:order-events

Publish to a Topic

utils/publishEvent.js
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

routes/orders.js
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).

  1. Go to SNS → Your topic → Create subscription
  2. Protocol: SQS
  3. Endpoint: your SQS queue ARN
  4. Click Create subscription

Also add an SQS Access Policy so SNS is allowed to write to your queue:

SQS Access Policy
{
"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 Transactional SMS type for OTPs — it has higher delivery priority than Promotional
  • 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)