Frequently Asked Questions
Implement a system using CloudWatch Logs and Lambda. SNS logs delivery attempts to CloudWatch, which triggers a Lambda function. This function parses the logs, queries a DynamoDB table using a Global Secondary Index (GSI) to find the message's internal ID, and updates its status.
The GSI allows efficient lookup of the internal message ID using the SNS message ID. Since SNS logs don't contain our internal ID, the GSI bridges this gap by indexing snsMessageId
and projecting internalMessageId
, enabling quick retrieval of the corresponding record in DynamoDB.
AWS SNS doesn't natively offer HTTP callbacks for SMS delivery status. This guide's architecture provides a workaround using CloudWatch Logs and Lambda to indirectly capture and process status updates.
Always prefer IAM roles for production deployments. For local development, you can use access keys stored in .env.local
. However, in production environments like Vercel, EC2, or ECS, IAM roles provide a more secure and manageable way to grant permissions to your application without exposing credentials.
Create a Next.js API route that uses the AWS SDK for JavaScript v3 to publish messages to an SNS topic. This API route should handle input validation, generate a unique internal message ID, store the initial message status in DynamoDB, and publish the SMS message via SNS.
The system uses Next.js for the frontend and API routes, AWS SNS for sending SMS, AWS CloudWatch Logs for capturing delivery status, AWS Lambda for processing logs, AWS DynamoDB for storing message status, AWS IAM for permissions, and the AWS SDK for JavaScript v3 for interacting with AWS services.
Yes, you can customize the sender ID by using MessageAttributes
in the PublishCommandInput
when sending the SMS message. Set 'AWS.SNS.SMS.SenderID'
to your desired alphanumeric sender ID (up to 11 characters).
Lambda acts as the processing engine triggered by CloudWatch Logs. It parses SNS delivery status logs, queries the DynamoDB GSI using the SNS message ID, retrieves the internal message ID, and updates the message status in DynamoDB.
Create a DynamoDB table with internalMessageId
as the primary key. Add a Global Secondary Index (GSI) with snsMessageId
as the partition key and project the internalMessageId
attribute. This allows efficient lookup of the internal ID based on the SNS message ID logged by CloudWatch.
Use the command npx create-next-app@latest sns-status-tracker --typescript
in your terminal. This creates a new Next.js project with TypeScript. Then, navigate into the project directory using cd sns-status-tracker
.
Run npm install @aws-sdk/client-sns @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
to install the necessary AWS SDK clients for interacting with SNS and DynamoDB.
For local development, create a .env.local
file in your project root. Store AWS credentials there. For production, use IAM roles or platform-specific secret management systems, keeping credentials out of your codebase.
You'll need Node.js and npm/yarn, an AWS account, AWS CLI configured, and a basic understanding of Next.js, React, and AWS concepts. Also, ensure you have appropriate AWS permissions to create the required resources.
SNS logs a JSON object containing details like message ID (messageId
), delivery status (status
), provider response (providerResponse
), and timestamp. The Lambda function parses this data to update the message status in DynamoDB.
This guide provides a step-by-step walkthrough for building a system within a Next.js application to send SMS messages via Amazon Simple Notification Service (SNS) and track their delivery status using AWS CloudWatch Logs and AWS Lambda.
Project Overview and Goals
We will build a Next.js application that enables users to send an SMS message to a specified phone number. After sending, the application will track the delivery status (e.g.,
SUCCESS
,FAILURE
) of that message.aws configure
).System Architecture Diagram
1. Setting up the Project
Let's initialize the Next.js project and install necessary dependencies.
Create Next.js App: Open your terminal and run:
Choose the defaults when prompted.
Install AWS SDK v3: We need the AWS SDK clients for SNS and DynamoDB.
@aws-sdk/client-sns
: For sending messages via SNS.@aws-sdk/client-dynamodb
: Base client for DynamoDB.@aws-sdk/lib-dynamodb
: Utility library simplifying DynamoDB interactions.Environment Variables: Create a file named
.env.local
in the project root. This file stores sensitive credentials and configuration. Never commit this file to version control. Replace the placeholder values with your actual configuration.AmazonSNSFullAccess
,AmazonDynamoDBFullAccess
,AWSLambda_FullAccess
,CloudWatchLogsFullAccess
- restrict these tightly in production). Download the access key and secret key. For production, always prefer IAM roles assigned to your compute environment (e.g., Vercel environment variables, EC2 instance profile, ECS task role).SNS_TOPIC_ARN
,DYNAMODB_TABLE_NAME
,LAMBDA_FUNCTION_NAME
: We will create these resources in later steps and update these values. TheSNS_TOPIC_ARN
is often not directly used when sending SMS viaPhoneNumber
, but might be relevant for other configurations.Project Structure: Your
src/
directory (if using/src
) will eventually contain:pages/
: Frontend pages and API routes.components/
: Reusable React components.lib/
: Utility functions (like AWS client setup).lambda/
: Directory for Lambda function code (created later).AWS Client Configuration (Optional Utility): Create
src/lib/awsClients.ts
to centralize SDK client initialization.DynamoDBDocumentClient
for easier interaction with DynamoDB using plain JavaScript objects.AWS_ACCESS_KEY_ID
,AWS_SECRET_ACCESS_KEY
,AWS_SESSION_TOKEN
) in production by not passing thecredentials
object unless specifically configured for development in.env.local
. The SDK automatically detects IAM roles in environments like Lambda, EC2, ECS, or Vercel/Amplify if configured correctly.2. Implementing Core Functionality (Sending SMS)
Let's create the frontend form and the API route to send the SMS.
Frontend Component: Create
src/components/SmsForm.tsx
:Add Component to Page: Modify
src/pages/index.tsx
:3. Building the API Layer (Send SMS)
Create the Next.js API route that handles sending the message via SNS and creating the initial status record in DynamoDB.
src/pages/api/send-sms.ts
:randomUUID
(internalMessageId
) to track the message within our system before getting thesnsMessageId
.status: 'PENDING'
usingPutCommand
.snsMessageId
usingUpdateCommand
upon successful SNS publication.PublishCommand
to send the SMS directly to the phone number.try...catch
blocks and attempts to update the DynamoDB status toFAILED_TO_SEND
usingUpdateCommand
if errors occur during SNS publishing or database updates.4. Integrating with AWS Services (SNS, DynamoDB, IAM)
Now, let's create the necessary AWS resources. We'll primarily use the AWS Management Console for clarity, but provide equivalent AWS CLI commands where feasible. Remember to replace placeholders like
YOUR_REGION
andYOUR_ACCOUNT_ID
with your actual values.Create DynamoDB Table and GSI:
SnsMessageStatus
(or your chosen name from.env.local
).internalMessageId
, Type:String
.Active
, select it.snsMessageId
, Type:String
.SnsMessageIdIndex
(this name will be used in the Lambda code).internalMessageId
to the projected attributes. This makes the lookup efficient..env.local
: EnsureDYNAMODB_TABLE_NAME
matches the exact name you used (SnsMessageStatus
).Configure SNS SMS Delivery Status Logging: This tells SNS to log delivery attempts to CloudWatch Logs.
Console:
100
(to log all successes for debugging; reduce in production if cost is a concern).SNSSuccessFeedback
). AWS automatically creates a role (SNSSuccessFeedback
) with a policy allowinglogs:CreateLogStream
andlogs:PutLogEvents
to CloudWatch Logs.SNSFailureFeedback
) similarly.What this does: SNS will now attempt to write log entries for SMS delivery attempts to a CloudWatch Log Group, typically named
sns/YOUR_REGION/YOUR_ACCOUNT_ID/DirectPublishToPhoneNumber
. Verify this exact name after sending your first message, as you'll need it for the Lambda trigger.Create IAM Role for Lambda: Our Lambda function needs permission to be invoked by CloudWatch Logs, read those logs, query the DynamoDB GSI, and update the DynamoDB table.
AWSLambdaBasicExecutionRole
(for basic Lambda logging).SnsStatusUpdaterRole
(or your choice).SnsStatusUpdaterServicePermissions
.5. Implementing the Lambda Function (with GSI Lookup)
This function is triggered by CloudWatch Logs events from SNS delivery status logging. It parses the event, uses the
snsMessageId
to query the GSI for theinternalMessageId
, and updates the correct DynamoDB record.Create Lambda Function Directory: In your project root, create
lambda/sns-status-updater/
.Lambda Handler Code (with GSI Lookup): Create
lambda/sns-status-updater/index.mjs
(using.mjs
for native ES Modules support in Node.js Lambda runtime):