Frequently Asked Questions
Use AWS Pinpoint to send outbound messages and configure a dedicated phone number. Incoming messages are routed through AWS SNS to a webhook on your application, enabling two-way communication. This setup allows your app to both send and receive SMS messages.
Fastify serves as a high-performance Node.js web framework for creating the backend application. Its speed and efficiency make it ideal for handling API requests and serverless functions within the two-way SMS architecture.
AWS Lambda provides serverless compute, allowing you to run your Fastify application without managing servers. This offers scalability and cost-efficiency, as you only pay for the compute time used to process messages.
While this guide uses AWS SDK v2, AWS SDK v3 is recommended for new projects due to its modularity and modern features. Migrating to v3 requires adjustments to client initialization and API call syntax.
Yes, you can run the application locally and test outbound SMS sending using tools like curl or Postman and the /send endpoint. Testing inbound messages locally is more complex, requiring tools like ngrok to simulate SNS notifications, with full end-to-end testing often being easier after deployment.
The core AWS services are Pinpoint for sending, SNS for receiving, Lambda for running the application, API Gateway for the HTTP endpoint, and IAM for permissions. An AWS account with necessary permissions is a prerequisite.
Inbound SMS messages are routed from the user's phone to your Pinpoint number, which then triggers an SNS notification to your application's webhook. The application processes the message, enabling actions like auto-replies.
For local development, store credentials securely in a .env file (never commit to version control). For the deployed Lambda function, use an IAM Execution Role to grant the necessary permissions, avoiding the need to embed credentials directly in the function's environment.
API Gateway creates an HTTP endpoint that serves as the entry point for sending outbound SMS messages (via the /send route) and receiving inbound messages via SNS (via the /webhook/sns route). This enables communication between external systems and your Lambda function.
Create an SNS topic and link it as the incoming message destination in your Pinpoint phone number configuration. This ensures all messages sent to your Pinpoint number are published to this SNS topic, which will then forward them to your Fastify application.
Node.js version 18 or later is recommended for this project. This ensures compatibility with the latest features and dependencies used in the tutorial.
The Fastify application, triggered by an inbound SMS message via SNS, contains logic to process the message and generate an automatic reply. This is demonstrated with a simple example in the provided code.
SNS message signature validation is crucial for security. It prevents Server-Side Request Forgery (SSRF) attacks by verifying that incoming messages genuinely originate from SNS. Libraries like sns-validator are recommended for this purpose.
Package the application code and dependencies into a zip file. Create a Lambda function, configure its execution role with necessary permissions, upload the zip file, set the handler to src/lambda.handler, and configure the required environment variables.
This guide provides a complete walkthrough for building a production-ready two-way SMS messaging system using Node.js with the Fastify framework, AWS Pinpoint for sending messages, and AWS Simple Notification Service (SNS) for receiving incoming replies. We will build a Fastify application deployable as an AWS Lambda function, triggered by API Gateway, capable of sending SMS messages via an API endpoint and automatically handling replies received via SNS.
By the end of this tutorial, you will have a scalable serverless application that can:
Project Overview and Goals
Problem: Businesses often need to send notifications, alerts, or marketing messages via SMS (Application-to-Person, A2P) and handle replies from users (two-way communication). Building a reliable and scalable system for this requires integrating messaging providers and handling asynchronous incoming messages.
Solution: We will leverage AWS services for robust messaging capabilities and Fastify for a high-performance Node.js backend, deployed serverlessly for scalability and cost-efficiency.
Technologies Used:
System Architecture:
Prerequisites:
curl
or Postman).1. Setting up the Project
Let's initialize our Node.js project and install the necessary dependencies.
Create Project Directory:
Initialize Node.js Project:
Install Dependencies:
fastify
: The core web framework.aws-sdk
: AWS SDK for JavaScript (v2) to interact with Pinpoint and SNS.dotenv
: To load environment variables from a.env
file for local development.@fastify/aws-lambda
: Adapter to run Fastify on AWS Lambda.Note: This guide uses AWS SDK v2. For new projects, AWS SDK v3 is generally recommended due to its modularity and modern features like middleware support. Using v3 would require changes to the AWS client initialization and API call syntax (e.g., importing specific client commands instead of the full SDK).
Create Project Structure:
Create the
src
andsrc/utils
directories.Create
.gitignore
: Create a.gitignore
file in the project root to prevent committing sensitive files and dependencies:Create
.env.example
: Create a.env.example
file in the project root. This serves as a template. We will populate the actual.env
file later with credentials obtained from AWS.Security Note: The
.env
file containing actual secrets should never be committed to version control.2. AWS Configuration
Before writing code, we need to configure the necessary AWS resources.
Configure IAM Permissions (User for Local Dev, Role for Lambda):
IAM User (for Local Development/CLI): While an IAM user with access keys is needed for local development and interacting with AWS via the CLI or SDKs from your machine, the primary and recommended way for the deployed Lambda function to obtain permissions is through an IAM Execution Role (see Section 5.2). This avoids embedding long-lived credentials in the function's environment.
fastify-sms-dev-user
).FullAccess
policies, it is strongly recommended to create custom IAM policies granting only the necessary permissions for local testing. Examples include:pinpoint:SendMessages
(to send SMS)sns:Publish
,sns:Subscribe
,sns:ListTopics
(if needed for local SNS interaction/setup)iam:PassRole
if your local setup involves assuming roles.Access key ID
andSecret access key
. These will go into your local.env
file.IAM Role (for Lambda Execution): We will create or assign this role during Lambda deployment (Section 5.2). This role needs permissions like:
pinpoint:SendMessages
(to send replies)logs:CreateLogGroup
,logs:CreateLogStream
,logs:PutLogEvents
(for CloudWatch logging)sns:Publish
if the Lambda needs to publish to other topics.Set up AWS Pinpoint:
+12065550100
).Create an SNS Topic:
twoWaySMSHandler
).arn:aws:sns:us-east-1:123456789012:twoWaySMSHandler
.Link Pinpoint to SNS Topic:
twoWaySMSHandler
) from the dropdown.Update
.env
File (for Local Development): Create a.env
file in your project root (copy.env.example
) and populate it with the actual values you obtained for local testing:AWS_ACCESS_KEY_ID
,AWS_SECRET_ACCESS_KEY
,AWS_REGION
(from the IAM User created in Step 1).PINPOINT_APP_ID
(from Pinpoint Project).PINPOINT_ORIGINATION_NUMBER
(the Pinpoint phone number you acquired).SNS_TOPIC_ARN
(from the SNS Topic).3. Implementing Core Functionality
Now let's write the code for our Fastify application.
Initialize AWS SDK Clients (
src/utils/aws.js
): Create a utility file to centralize AWS SDK client initialization.dotenv
only locally..env
locally, relies on Lambda execution role when deployed.Create Fastify Application (
src/app.js
): This file sets up the Fastify instance, defines routes for sending and receiving SMS, and includes basic error handling.to
number in/send
schema./send
route based on error type.Create Lambda Handler (
src/lambda.js
): This file uses@fastify/aws-lambda
to wrap our Fastify application instance.@fastify/aws-lambda
proxy.4. Local Development and Testing
Before deploying, you can run the application locally.
Add Start Script: Add a script to your
package.json
for local execution:-r dotenv/config
: Preloadsdotenv
to load variables from.env
.Install
pino-pretty
and Create Local Runner (scripts/run-local.js
): First, install the development dependency for nice local logging:Now, create a
scripts
directory and addrun-local.js
. This script runs the Fastify app directly using its built-in server.pino-pretty
for improved local log readability.app.listen
for standard HTTP server behavior.Run Locally: Make sure your
.env
file is populated with your IAM User credentials and other config.The server should start, listening on port 3000.
Test Sending SMS (Local): Use
curl
or Postman to send a POST request tohttp://localhost:3000/send
:Replace
+1RECIPIENTNUMBER
with a valid test phone number. Check your terminal logs (nicely formatted bypino-pretty
) and the recipient's phone for the message.Note on testing inbound: Testing the
/webhook/sns
route locally requires simulating an SNS notification. This is complex because AWS SNS needs a publicly accessible HTTPS endpoint to send notifications to. Tools likengrok
can expose your local server to the public internet with an HTTPS URL, which you could temporarily use in the SNS subscription. However, full end-to-end testing of the inbound flow is often easier after deployment.5. Deployment to AWS Lambda and API Gateway
We'll deploy the application as a Lambda function triggered by API Gateway.
Package the Application: Create a zip file containing your code and only production dependencies.
This command packages the
src
directory (containingapp.js
,lambda.js
,utils/aws.js
), the productionnode_modules
, and package files.Create the Lambda Function:
fastify-sms-handler
(or similar).x86_64
orarm64
.fastify-sms-handler-role-xxxx
).pinpoint:SendMessages
(to send replies from the webhook).logs:CreateLogGroup
,logs:CreateLogStream
,logs:PutLogEvents
).Upload Code:
deployment_package.zip
file you created.Configure Handler and Environment Variables:
src/lambda.handler
(pointing toexports.handler
insrc/lambda.js
).src/utils/aws.js
when running in Lambda):AWS_REGION
: Your AWS region (e.g.,us-east-1
).PINPOINT_APP_ID
: Your Pinpoint Application ID.PINPOINT_ORIGINATION_NUMBER
: Your Pinpoint phone number (E.164 format).SNS_TOPIC_ARN
: The ARN of your SNS topic for inbound messages.LOG_LEVEL
(Optional): Set todebug
,warn
,error
to control logging verbosity (defaults toinfo
).AWS_ACCESS_KEY_ID
orAWS_SECRET_ACCESS_KEY
here. The Lambda function will use the permissions granted by its Execution Role.Create API Gateway (HTTP API): We'll use an HTTP API for simplicity and cost-effectiveness.
Lambda
.fastify-sms-handler
function.SMSTwoWayAPI
(or similar).POST
, Resource path:/send
. Integration target:fastify-sms-handler
.POST
, Resource path:/webhook/sns
. Integration target:fastify-sms-handler
.GET
, Resource path:/health
. Integration target:fastify-sms-handler
.$default
is fine for now. Ensure ""Auto-deploy"" is enabled.https://abcdef123.execute-api.us-east-1.amazonaws.com
).Subscribe SNS Topic to API Gateway Endpoint:
twoWaySMSHandler
topic.HTTPS
.https://abcdef123.execute-api.us-east-1.amazonaws.com/webhook/sns
.JSON.parse(snsMessage.Message)
). If unchecked, SNS wraps the message in its own JSON structure./webhook/sns
route). You should see the log message containing theSubscribeURL
from our Fastify app. Manually copy and paste this URL into your browser to confirm the subscription. Alternatively, you can confirm it within the SNS console if the endpoint responds correctly (which our code does by logging and returning 200). Once confirmed, the status will change to ""Confirmed"".6. End-to-End Testing (Deployed)
Test Outbound (
/send
): Usecurl
or Postman to send a POST request to your deployed API Gateway/send
endpoint:Replace
<your-api-id>
and<region>
with your API Gateway details, and+1RECIPIENTNUMBER
with a test number. Verify the message is received. Check CloudWatch Logs for your Lambda function for details.Test Inbound (
/webhook/sns
and Auto-Reply):fastify-sms-handler
Lambda function. You should see logs indicating:/webhook/sns
.console.log
showing the received message details.Test Health Check (
/health
):You should receive a JSON response like:
{""status"":""ok"",""timestamp"":""...""}
.Conclusion and Next Steps
You have successfully built and deployed a serverless two-way SMS application using Fastify, Node.js, and several AWS services. This provides a scalable foundation for handling SMS communication.
Potential Enhancements:
sns-validator
to prevent SSRF and ensure messages genuinely originate from your SNS topic.