This guide provides a step-by-step walkthrough for building a production-ready system to handle two-way SMS messaging within a Next.js application using AWS Simple Notification Service (SNS) and Node.js. You will learn how to send outbound SMS messages and receive and process inbound SMS replies via a webhook.
We'll cover everything from initial AWS setup and Next.js project configuration to handling incoming messages securely, storing conversation history, and deploying the application.
Project Goals:
- Send outbound SMS messages from a Next.js application via AWS SNS.
- Receive inbound SMS messages sent to a dedicated AWS phone number.
- Process inbound messages using a Next.js API route acting as an SNS webhook.
- Securely validate incoming requests from AWS SNS.
- Optionally store message history for context (using Prisma and a database).
- Deploy the solution.
Technologies Used:
- Next.js: React framework for frontend and API routes (using App Router).
- Node.js: JavaScript runtime environment.
- AWS SNS (Simple Notification Service): Fully managed messaging service for sending SMS and receiving inbound messages via topic subscriptions.
- AWS SDK for JavaScript (v3): To interact with AWS services (SNS).
- AWS Console (Pinpoint/Messaging Section): To acquire and configure a phone number capable of sending/receiving SMS and link it to an SNS topic.
- Prisma (Optional): ORM for database interaction to store message history.
- Vercel (or similar): Platform for deploying the Next.js application.
System Architecture:
+-----------------+ +----------------------+ +-----------------+
| User's Phone | <--> | AWS Phone Number | <--> | AWS SNS |
| (Sends/Receives)| | (Pinpoint/Messaging) | | (Topic & Publish)|
+-----------------+ +----------------------+ +--------+--------+
^ | (Subscription HTTP/S)
| (Outbound SMS via API) |
v v
+--------------------------+ +-----------------+ +-----------------------+
| Next.js Application | ---> | /api/sms/send | ---> | AWS SDK (SNS Publish) |
| (Frontend / API Routes) | | (POST Request) | +-----------------------+
| | +-----------------+
| | +--------------------------+
| | <--- | /api/sms/inbound| <--- | SNS Notification Payload |
| | | (POST Request) | +--------------------------+
| | +-------+---------+
+-------------+------------+ |
| (Optional DB Access) | (Stores Message)
v v
+-----------------------------------+
| Database (e.g._ PostgreSQL w/ Prisma) |
+-----------------------------------+
Prerequisites:
- An AWS Account with appropriate permissions (IAM user with programmatic access).
- Node.js (LTS version recommended) and npm/yarn installed.
- AWS CLI installed and configured (optional_ but recommended).
- A code editor (e.g._ VS Code).
- Basic understanding of Next.js_ React_ Node.js_ and REST APIs.
- Access to a database (e.g._ PostgreSQL_ MySQL_ SQLite) if implementing message storage.
> Important Note on Placeholders: Throughout this guide, you will encounter placeholder values like YOUR_AWS_PHONE_NUMBER
, YOUR_SNS_TOPIC_ARN
, YOUR_IAM_USER_ACCESS_KEY_ID
, your-aws-region
, etc. You must replace all such placeholders with your actual configuration details for the application to function correctly. Keep track of the values you create during the AWS setup steps.
1. Setting Up the AWS Environment
Before writing code, we need to configure the necessary AWS resources.
Step 1: Acquire an SMS-Capable Phone Number
AWS manages phone number acquisition for SMS often through the Pinpoint or general messaging sections of the console, even if you primarily use the SNS API for sending/receiving.
- Navigate to the AWS Management Console.
- Search for
Pinpoint
orMessaging & Targeting
>SMS and voice
. (Note: Console navigation might change slightly). - In the navigation pane, find
Phone numbers
or a similar option likeManage numbers
orOrigination identities
. - Request a new phone number suitable for your region and ensure it supports SMS and Two-Way SMS. This might be a 10DLC number (US), Short Code, or Long Code depending on your location and requirements. Follow the registration prompts if necessary (e.g., for 10DLC).
- Important: Note down the acquired phone number (in E.164 format, e.g.,
+12065550100
). You'll need this later. Let's call thisYOUR_AWS_PHONE_NUMBER
.
Step 2: Create an SNS Topic for Inbound Messages
This topic will receive notifications when someone sends an SMS to your AWS phone number.
- Navigate to the SNS (Simple Notification Service) console.
- Go to Topics in the left-hand navigation.
- Click Create topic.
- Choose Standard type.
- Give it a descriptive name (e.g.,
sms-inbound-topic
). - Keep other settings as default for now and click Create topic.
- Once created, copy the ARN (Amazon Resource Name) of the topic. You'll need this. Let's call it
YOUR_SNS_TOPIC_ARN
.
Step 3: Configure Two-Way SMS on the Phone Number
Link your AWS phone number to the SNS topic for inbound messages.
- Go back to the Pinpoint / SMS and voice section where you managed your phone number.
- Select the phone number you acquired.
- Find the Two-way SMS configuration section (it might be a tab or editable setting).
- Enable Two-way SMS.
- For the Destination type, select Amazon SNS topic.
- For SNS topic, select Existing Amazon SNS topic.
- Choose the
sms-inbound-topic
(using its ARN or name) you created in the previous step from the dropdown. - IAM Role: AWS needs permissions to publish messages from the phone number service to your SNS topic.
- Option 1 (Easiest): If available, choose an option like
Create a new service role
or allow AWS to create the necessary permissions automatically. - Option 2 (Manual): If you need to use an existing role or manage policies manually, ensure the role associated with the phone number service has the
sns:Publish
permission forYOUR_SNS_TOPIC_ARN
. Refer to the AWS Documentation for specific policy examples if needed. ChooseUse Amazon SNS topic policies
orChoose existing IAM role
accordingly.
- Option 1 (Easiest): If available, choose an option like
- Save the changes.
Step 4: Create an IAM User for Your Application
Your Next.js application needs AWS credentials to send SMS messages via the SNS API.
- Navigate to the IAM (Identity and Access Management) console.
- Go to Users and click Add users.
- Enter a username (e.g.,
nextjs-sms-app-user
). - Select Provide user access to the AWS console - optional if needed, but ensure you select Access key - Programmatic access.
- Click Next.
- Choose Attach policies directly.
- Search for and select the
AmazonSNSFullAccess
policy. Note: For production environments,AmazonSNSFullAccess
is too permissive. It's strongly recommended to create a custom IAM policy granting only the necessary permissions, primarilysns:Publish
. Refer to the AWS IAM documentation on creating policies. - Click Next through Tags.
- Review and click Create user.
- Crucial: On the final screen, copy the Access key ID and Secret access key. Store these securely. You will not be able to see the secret key again. Let's call these
YOUR_IAM_USER_ACCESS_KEY_ID
andYOUR_IAM_USER_SECRET_ACCESS_KEY
.
2. Setting Up the Next.js Project
Now, let's create the Next.js application and install dependencies.
Step 1: Create a New Next.js Project
Open your terminal and run:
npx create-next-app@latest nextjs-sms-app --typescript --eslint --tailwind --src-dir --app --import-alias ""@/*""
cd nextjs-sms-app
(Choose options according to your preferences; the above uses TypeScript, ESLint, Tailwind CSS, src/
directory, App Router, and the @/*
import alias).
Step 2: Install Dependencies
We need the AWS SDK v3 for SNS and a library to help validate incoming SNS messages. Optionally, install Prisma if you plan to store messages.
npm install @aws-sdk/client-sns sns-validator
# Optional: Install Prisma for database storage
npm install prisma @prisma/client --save-dev
Step 3: Configure Environment Variables
Create a file named .env.local
in the root of your project. Never commit this file to Git.
# .env.local
# AWS Credentials for sending SMS
AWS_ACCESS_KEY_ID=YOUR_IAM_USER_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY=YOUR_IAM_USER_SECRET_ACCESS_KEY
AWS_REGION=your-aws-region # e.g., us-east-1
# AWS Resources
SNS_TOPIC_ARN=YOUR_SNS_TOPIC_ARN
AWS_PHONE_NUMBER=YOUR_AWS_PHONE_NUMBER # Your provisioned number in E.164 format
# Optional: Database URL for Prisma
# Example for PostgreSQL: DATABASE_URL=""postgresql://user:password@host:port/database?schema=public""
DATABASE_URL=YOUR_DATABASE_CONNECTION_STRING
- Replace placeholders with the actual values you obtained/created earlier.
AWS_REGION
: Ensure this is the region where you created your SNS topic and acquired your phone number.
Step 4: Initialize Prisma (Optional)
If you're storing messages:
-
Initialize Prisma:
npx prisma init --datasource-provider postgresql # Or your chosen DB provider
This creates a
prisma
directory with aschema.prisma
file and updates.env
(though we use.env.local
). -
Define the Message schema in
prisma/schema.prisma
:// prisma/schema.prisma generator client { provider = ""prisma-client-js"" } datasource db { provider = ""postgresql"" // Or your chosen provider url = env(""DATABASE_URL"") } model Message { id String @id @default(cuid()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt direction String // ""inbound"" or ""outbound"" phoneNumber String // The external party's phone number (E.164) awsPhoneNumber String // Your AWS number involved in the exchange (E.164) body String? // Message content awsMessageId String? @unique // The ID returned by AWS SNS when sending status String? // Status from AWS (e.g., for outbound) @@index([phoneNumber, createdAt]) @@index([createdAt]) }
-
Apply the schema to your database:
npx prisma migrate dev --name init-message-schema
This creates the
Message
table in your database. -
Generate the Prisma Client:
npx prisma generate
3. Implementing Core Functionality: Sending and Receiving SMS
We'll create two API routes: one to send messages and another to receive inbound messages from SNS.
Step 1: Configure AWS SDK Client
Create a utility file to instantiate and export the SNS client.
// src/lib/aws.ts
import { SNSClient } from "@aws-sdk/client-sns";
const region = process.env.AWS_REGION;
const accessKeyId = process.env.AWS_ACCESS_KEY_ID;
const secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY;
if (!region || !accessKeyId || !secretAccessKey) {
// In production, prefer more robust handling or build-time checks
console.error("AWS configuration environment variables are missing!");
// Avoid throwing here in module scope if possible, handle in usage context
}
// Ensure credentials are only provided if they exist, letting the SDK
// potentially fall back to other credential providers if needed (e.g., EC2 instance roles)
const credentials = accessKeyId && secretAccessKey ? { accessKeyId, secretAccessKey } : undefined;
export const snsClient = new SNSClient({
region: region,
credentials, // Pass credentials only if they are explicitly set via env vars
});
export const AWS_PHONE_NUMBER = process.env.AWS_PHONE_NUMBER;
if (!AWS_PHONE_NUMBER) {
console.error("AWS_PHONE_NUMBER environment variable is not set!");
}
Step 2: Create API Route for Sending SMS (/api/sms/send
)
This endpoint will accept a destination phone number and message body, then use the AWS SDK to send the SMS via SNS.
// src/app/api/sms/send/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { PublishCommand } from '@aws-sdk/client-sns';
import { snsClient, AWS_PHONE_NUMBER } from '@/lib/aws';
// Optional: Import Prisma client if storing messages
// import { prisma } from '@/lib/prisma';
export async function POST(req: NextRequest) {
if (!AWS_PHONE_NUMBER) {
return NextResponse.json({ error: 'AWS phone number not configured' }, { status: 500 });
}
try {
const { to, message } = await req.json();
// Basic validation
if (!to || !message) {
return NextResponse.json({ error: 'Missing `to` or `message` field' }, { status: 400 });
}
// Add more robust phone number validation (e.g., E.164 format check)
if (!/^\+[1-9]\d{1,14}$/.test(to)) {
return NextResponse.json({ error: 'Invalid phone number format. Use E.164 (e.g., +12065550100)' }, { status: 400 });
}
const params = {
Message: message,
PhoneNumber: to,
// Optional: Specify MessageAttributes for SenderID, MaxPrice, etc.
// See: https://docs.aws.amazon.com/sns/latest/api/API_Publish.html
// MessageAttributes: {
// 'AWS.SNS.SMS.SenderID': {
// DataType: 'String',
// StringValue: 'MySenderID' // Must be pre-registered if required
// },
// 'AWS.SNS.SMS.SMSType': {
// DataType: 'String',
// StringValue: 'Transactional' // Or 'Promotional'
// }
// }
};
const command = new PublishCommand(params);
const data = await snsClient.send(command);
console.log(`Message sent to ${to}. MessageID: ${data.MessageId}`);
// Optional: Save outbound message to database
// try {
// await prisma.message.create({
// data: {
// direction: 'outbound',
// phoneNumber: to,
// awsPhoneNumber: AWS_PHONE_NUMBER,
// body: message,
// awsMessageId: data.MessageId,
// status: 'Sent', // Or map based on potential response details
// },
// });
// } catch (dbError) {
// console.error("Database write error (outbound):", dbError);
// // Decide how to handle DB errors - log, alert, but maybe don't fail the API call
// }
return NextResponse.json({ success: true, messageId: data.MessageId });
} catch (error: any) {
console.error("Error sending SMS:", error);
// Provide a more user-friendly error message if needed
const errorMessage = error.message || 'Failed to send SMS';
// Check for specific AWS error codes if necessary
return NextResponse.json({ error: 'Failed to send SMS', details: errorMessage }, { status: 500 });
}
}
Step 3: Create API Route for Receiving SMS (/api/sms/inbound
)
This endpoint acts as the webhook for SNS. It needs to handle SNS subscription confirmation and process incoming message notifications, crucially verifying the message signature.
// src/app/api/sms/inbound/route.ts
import { NextRequest, NextResponse } from 'next/server';
import MessageValidator from 'sns-validator';
import { promisify } from 'util';
// Optional: Import Prisma client if storing messages
// import { prisma } from '@/lib/prisma';
const validator = new MessageValidator();
const validateMessage = promisify(validator.validate).bind(validator);
// Utility to read stream to buffer/string
async function streamToString(stream: ReadableStream<Uint8Array>): Promise<string> {
const reader = stream.getReader();
const decoder = new TextDecoder();
let result = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
result += decoder.decode(value, { stream: true });
}
result += decoder.decode(); // Flush any remaining bytes
return result;
}
export async function POST(req: NextRequest) {
let messagePayload;
try {
// AWS SNS sends messages with content-type 'text/plain; charset=UTF-8'
// We need to parse the raw body as JSON.
const rawBody = await streamToString(req.body!); // Read the raw body stream
messagePayload = JSON.parse(rawBody); // Parse the raw body as JSON
} catch (e) {
console.error("Failed to parse incoming request body:", e);
return NextResponse.json({ error: 'Invalid request body' }, { status: 400 });
}
// 1. Validate the SNS Message Signature (CRITICAL SECURITY STEP)
try {
await validateMessage(messagePayload);
console.log("SNS Message validation successful.");
} catch (err) {
console.error("SNS Message validation failed:", err);
return NextResponse.json({ error: 'Invalid SNS message signature' }, { status: 403 });
}
// 2. Handle SNS Subscription Confirmation
if (messagePayload.Type === 'SubscriptionConfirmation') {
console.log('Received SNS Subscription Confirmation request');
try {
// Best practice for initial setup: Log the URL and confirm manually by visiting it.
// Automatically visiting the URL requires careful security implementation
// to prevent potential misuse or SSRF vulnerabilities.
console.warn(`Confirm SNS subscription by visiting: ${messagePayload.SubscribeURL}`);
return NextResponse.json({ success: true, message: 'Subscription confirmation request received. Confirm via SubscribeURL logged.' });
// Example of automatic confirmation (USE WITH CAUTION - ensure endpoint is secured):
// console.log('Attempting automatic subscription confirmation...');
// const response = await fetch(messagePayload.SubscribeURL);
// if (!response.ok) {
// throw new Error(`Subscription confirmation fetch failed: ${response.statusText}`);
// }
// console.log('Subscription confirmed automatically.');
// return NextResponse.json({ success: true, message: 'Subscription confirmed automatically.' });
} catch (confirmError) {
console.error('Error processing SNS subscription confirmation:', confirmError);
// Don't return 500 if manual confirmation is the primary method
// return NextResponse.json({ error: 'Failed to process subscription confirmation' }, { status: 500 });
return NextResponse.json({ success: true, message: 'Subscription confirmation request received, but automatic confirmation failed or is disabled. Confirm manually.' });
}
}
// 3. Handle SNS Notification (Actual Incoming SMS)
if (messagePayload.Type === 'Notification') {
try {
const snsMessage = JSON.parse(messagePayload.Message);
const incomingNumber = snsMessage.originationNumber; // Sender's number
const awsNumber = snsMessage.destinationNumber; // Your AWS number receiving the msg
const messageBody = snsMessage.messageBody;
const awsMessageId = snsMessage.messageId; // ID of the inbound message from AWS carrier perspective
console.log(`Received SMS from ${incomingNumber} to ${awsNumber}: "${messageBody}"`);
// --- Add your business logic here ---
// Examples:
// - Look up user based on incomingNumber
// - Trigger automated responses
// - Store the message
// - Forward to another system
// Handle STOP/HELP keywords (Example - Implement actual logic)
const lowerCaseBody = messageBody.toLowerCase().trim();
if (lowerCaseBody === 'stop') {
console.log(`Received STOP request from ${incomingNumber}. Implement opt-out logic.`);
// TODO: Add logic to flag user as unsubscribed in your database
} else if (lowerCaseBody === 'help') {
console.log(`Received HELP request from ${incomingNumber}. Implement help response logic.`);
// TODO: Add logic to send a pre-defined help message back (potentially via the /api/sms/send route)
}
// Optional: Save inbound message to database
// try {
// await prisma.message.create({
// data: {
// direction: 'inbound',
// phoneNumber: incomingNumber,
// awsPhoneNumber: awsNumber,
// body: messageBody,
// awsMessageId: awsMessageId, // Store the AWS inbound ID
// status: 'Received',
// },
// });
// console.log('Inbound message saved to database.');
// } catch (dbError) {
// console.error("Database write error (inbound):", dbError);
// // Log, alert, but acknowledge receipt to SNS
// }
// --- End Business Logic ---
// Acknowledge receipt to SNS
return NextResponse.json({ success: true, message: 'Message received' });
} catch (parseError) {
console.error('Failed to parse SNS message content:', parseError);
return NextResponse.json({ error: 'Invalid message format within notification' }, { status: 400 });
}
}
// Handle other message types if necessary (e.g., UnsubscribeConfirmation)
console.log(`Received unhandled SNS message type: ${messagePayload.Type}`);
return NextResponse.json({ success: true, message: `Unhandled message type: ${messagePayload.Type}` });
}
Explanation:
- Signature Validation:
sns-validator
checks if the request genuinely came from AWS SNS using cryptographic signatures. This prevents anyone from simply POSTing fake data to your endpoint. Do not skip this. - Subscription Confirmation: When you first subscribe an HTTP/S endpoint to an SNS topic, AWS sends a
SubscriptionConfirmation
message. Your endpoint must handle this. The recommended approach for setup is to log theSubscribeURL
and visit it manually. Automatic confirmation viafetch
is possible but requires careful security considerations. - Notification Handling: When an actual SMS arrives, SNS sends a
Notification
message. The code parses themessagePayload.Message
(which is a JSON string itself) to extract the sender (originationNumber
), your AWS number (destinationNumber
), and themessageBody
. - Database Interaction (Optional): The commented-out sections show where you would add Prisma calls to save
inbound
andoutbound
messages if you implemented the database schema.
4. Subscribing the API Endpoint to the SNS Topic
Your /api/sms/inbound
endpoint needs to be registered (""subscribed"") to the sms-inbound-topic
you created earlier.
Step 1: Deploy Your Application (Initial Deployment)
Before you can subscribe, your API endpoint needs a publicly accessible HTTPS URL. Deploy your Next.js application to a platform like Vercel.
- Push your code to a Git repository (GitHub, GitLab, Bitbucket).
- Sign up or log in to Vercel.
- Create a new project, import your Git repository.
- Crucially: Configure the Environment Variables (
AWS_ACCESS_KEY_ID
,AWS_SECRET_ACCESS_KEY
,AWS_REGION
,SNS_TOPIC_ARN
,AWS_PHONE_NUMBER
,DATABASE_URL
) in the Vercel project settings. - Let Vercel build and deploy your application. Note the production URL (e.g.,
https://your-app-name.vercel.app
).
Step 2: Add Subscription in AWS Console
- Navigate back to the SNS Console > Topics.
- Select your
sms-inbound-topic
. - Go to the Subscriptions tab and click Create subscription.
- Protocol: Select HTTPS.
- Endpoint: Enter the full URL of your deployed inbound API route (e.g.,
https://your-app-name.vercel.app/api/sms/inbound
). - Enable raw message delivery: Keep this unchecked. We need the full SNS JSON structure for validation and type checking.
- Click Create subscription.
Step 3: Confirm the Subscription
- Check the logs of your deployed application (e.g., Vercel's runtime logs).
- Look for the log message from your
/api/sms/inbound
route containing:Confirm SNS subscription by visiting: [URL]
- Copy the
SubscribeURL
logged. - Paste this URL into your browser and visit it. You should see an XML confirmation message from AWS.
- Go back to the SNS console > Subscriptions tab. The status should change from
Pending confirmation
toConfirmed
.
Your endpoint is now ready to receive messages from the SNS topic.
5. Testing the Implementation
Test 1: Sending an Outbound SMS
Use a tool like curl
or Postman to send a POST request to your deployed /api/sms/send
endpoint:
curl -X POST https://your-app-name.vercel.app/api/sms/send \
-H 'Content-Type: application/json' \
-d '{
"to": "+1xxxxxxxxxx", # Replace with a real phone number you can check
"message": "Hello from Next.js and AWS SNS! (Test 1)"
}'
- Replace
https://your-app-name.vercel.app
with your actual deployment URL. - Replace
+1xxxxxxxxxx
with a real phone number you can check. - Check the target phone; you should receive the SMS.
- Check your application logs for success messages or errors.
- (Optional) Check your database to see if the outbound message was recorded.
Test 2: Receiving an Inbound SMS
- Using the phone that received the SMS in Test 1 (or any phone), send an SMS reply to your
YOUR_AWS_PHONE_NUMBER
. - Monitor your application's logs (Vercel runtime logs). You should see logs indicating:
SNS Message validation successful.
Received SMS from [sender's number] to [your AWS number]: "[Your reply message]"
- (Optional) Logs indicating
STOP
/HELP
keyword detection if applicable. - (Optional)
Inbound message saved to database.
- (Optional) Check your database to verify the inbound message was stored correctly.
6. Security, Error Handling, and Considerations
- SNS Signature Validation: Already implemented via
sns-validator
. Never disable this; it's crucial for verifying that incoming webhook requests originate from AWS SNS. - IAM Permissions: Use the principle of least privilege. Grant your IAM user only the permissions absolutely necessary (
sns:Publish
is often sufficient for sending). Avoid using root credentials. Consider using IAM roles if deploying on EC2 or ECS. - Environment Variables: Keep AWS credentials and other secrets out of your codebase. Use
.env.local
locally and your hosting provider's secrets management (like Vercel Environment Variables) in production. - Input Validation: Sanitize and validate all inputs, especially the
to
phone number andmessage
content in the/api/sms/send
route. Check for length limits and potentially malicious content. Validate phone number formats rigorously (E.164). - Rate Limiting: Implement rate limiting on your
/api/sms/send
endpoint to prevent abuse and control costs. Vercel offers some protection, or use libraries likerate-limiter-flexible
or API Gateway features if applicable. - Error Handling: The provided code includes basic
try...catch
blocks. Enhance this with more specific error handling (e.g., catching specific AWS SDK errors likeThrottlingException
) and potentially use an external error tracking service (like Sentry). Ensure sensitive error details are not leaked to the client. - SNS Delivery Status Logging: For outbound messages, you can configure SNS to log delivery status to CloudWatch Logs for detailed tracking of successes and failures (e.g., carrier filtering, number invalid). See SNS documentation for ""SMS message delivery status"".
- Dead Letter Queues (DLQ): Configure a DLQ for your SNS subscription (the HTTPS endpoint). If your endpoint fails consistently (e.g., due to bugs, downtime, or invalid responses), SNS can send the failed messages to an SQS queue (the DLQ) for later inspection and reprocessing, preventing message loss. Configure this in the SNS Subscription settings in the AWS Console.
STOP
/HELP
Keywords: Carrier regulations (especially in the US for 10DLC/Short Codes) often mandate handlingSTOP
(to opt-out/unsubscribe) andHELP
(to provide contact info/instructions) keywords. AWS SNS has some built-in handling options (Self-managed opt-outs
) you can configure on the phone number settings. Alternatively, or in addition, implement logic in your/api/sms/inbound
route (as shown in the example snippet) to detect these keywords and take appropriate action (e.g., update a user's subscription status in your database, send a canned HELP response). Failure to handle these can lead to carrier filtering or suspension.- Message Correlation: SMS is inherently stateless. The optional database schema allows you to store inbound and outbound messages linked by the external party's
phoneNumber
. This is essential for creating conversational context. You might add aconversationId
or use timestamps and phone numbers to group related messages for displaying chat history or maintaining stateful interactions. - Cost: Be aware of AWS costs: per SMS segment sent/received (prices vary by country), phone number rental fees (monthly). Monitor your AWS billing dashboard.
- Character Limits & Encoding: Standard SMS messages have limits (160 GSM-7 characters, 70 UCS-2 for non-Latin characters like emojis). Longer messages are automatically split into multiple segments by carriers, and each segment is billed separately by AWS. Be mindful of message length to manage costs.
7. Troubleshooting and Caveats
Invalid SNS message signature
:- Ensure you are using
sns-validator
correctly on the raw request body before JSON parsing it elsewhere. - Check if raw message delivery is disabled (unchecked) on the SNS subscription settings in the AWS Console. Raw delivery bypasses the standard SNS JSON structure needed for validation.
- Verify the request body is being read and passed to the validator correctly.
- Ensure you are using
- Subscription Stuck in
Pending confirmation
:- Ensure your
/api/sms/inbound
endpoint is deployed and publicly accessible via HTTPS without requiring authentication that AWS cannot handle. - Check firewall rules if hosting outside standard PaaS platforms.
- Verify you are correctly visiting the full
SubscribeURL
logged by your application. Check for typos or browser issues. - Check application logs for errors during the confirmation request handling.
- Ensure your
- Messages Not Sending (
sns:Publish
errors, API errors):- Double-check IAM user credentials (
AWS_ACCESS_KEY_ID
,AWS_SECRET_ACCESS_KEY
) and region (AWS_REGION
) in your environment variables (both locally and in production). - Verify the IAM user has the necessary
sns:Publish
permission attached (either directly or via a group/role). - Ensure the
to
phone number is in the correct E.164 format (e.g.,+14155552671
). - Check if your AWS account is out of the SMS sandbox (new accounts often start in a sandbox with sending restrictions, e.g., only to verified numbers). Request a spending limit increase or production access via AWS Support if needed.
- Check SNS Delivery Status logs in CloudWatch (if configured) for specific error codes from carriers.
- Double-check IAM user credentials (
- Messages Not Being Received (Inbound):
- Verify the phone number's Two-Way SMS is enabled in the AWS Console (Pinpoint/Messaging section) and correctly pointing to your SNS Topic ARN.
- Check the SNS Subscription status in the AWS Console is
Confirmed
. If not, re-confirm. - Ensure your
/api/sms/inbound
endpoint is running, accessible, and not crashing. Check application logs on your hosting provider (e.g., Vercel). - Check the SNS Topic's
Monitoring
tab in the AWS console for delivery failures (NumberOfNotificationsFailed
) to your endpoint. Check the associated CloudWatch metrics/alarms. - Check the Dead Letter Queue (DLQ) if configured for your SNS subscription; failed messages might be there.