Frequently Asked Questions
Set up two-way SMS by configuring AWS services and a NestJS application. This involves linking an AWS phone number to an SNS topic, subscribing an SQS queue to the topic, and triggering a Lambda function containing the NestJS app to process messages from the queue. This architecture allows for receiving and sending SMS messages.
Amazon Pinpoint provisions phone numbers and manages SMS communication, including two-way messaging. It acts as the entry point for inbound SMS messages, forwarding them to SNS. Pinpoint's "SMS and Voice" console section is key for this setup.
SQS decouples the SMS receiving (SNS) and processing (Lambda/NestJS) parts of the system. This adds resilience by buffering messages and enabling retries if the Lambda function is unavailable. It also allows the system to handle spikes in incoming SMS traffic.
Enable raw message delivery when you want the SQS message body to directly contain the original SMS payload from Pinpoint. This simplifies parsing in your Lambda function by removing the extra SNS message wrapping. The code examples provided in the article assume raw delivery is enabled.
Yes, you can send replies using the AWS SDK for JavaScript v3 within your NestJS application. The `SmsService` example demonstrates using the `PinpointSMSVoiceV2Client` to send outbound text messages via the `SendTextMessageCommand`. Ensure your Lambda execution role has the necessary `pinpoint-sms-voice:SendTextMessage` permission.
Incoming SMS messages are handled by a controller in your NestJS application. Use the `@EventPattern` decorator with a matching pattern in your `main.ts` file to route incoming SQS messages to the correct controller method. This method then processes the message content (e.g., parsing keywords) and executes relevant logic.
Prerequisites include an AWS account with necessary permissions, Node.js and npm/yarn, NestJS CLI, AWS CLI, a provisioned phone number in Amazon Pinpoint, and basic understanding of TypeScript and NestJS. Familiarity with Serverless Framework or AWS CDK is recommended for deployment.
The custom transport strategy allows the Lambda handler to find the correct NestJS message handler based on the content or pattern of the incoming SQS message. It connects the Lambda event trigger to the @EventPattern decorators in your NestJS controller, mimicking microservice event handling.
Review the permissions of your IAM roles and policies, particularly for Pinpoint, SNS, SQS, and Lambda. Verify that the Pinpoint role can publish to SNS, the SNS topic allows the Pinpoint service, SQS allows messages from SNS, and the Lambda role can access SQS, CloudWatch Logs, and Pinpoint for sending replies.
Opt-out handling is crucial for compliance. You can implement custom logic in your NestJS application to handle keywords like "STOP" to unsubscribe users, likely updating your database or another system accordingly. AWS Pinpoint might also offer automatic opt-out handling based on region and number type.
The architecture uses Amazon Pinpoint (for phone numbers and SMS/MMS), SNS (for notifications), SQS (for queuing), Lambda (for serverless compute), and the AWS SDK for JavaScript. IAM roles and policies are used for permissions management, and CloudWatch for monitoring and logging.
Core technologies include NestJS (backend framework), Amazon Pinpoint (SMS management), SNS (messaging), SQS (queuing), AWS Lambda (serverless compute), and the AWS SDK for JavaScript v3. Serverless Framework or AWS CDK are recommended for infrastructure management.
This guide provides a step-by-step walkthrough for building a robust system capable of receiving and processing inbound SMS messages using NestJS, AWS Simple Notification Service (SNS), AWS Simple Queue Service (SQS), AWS Lambda, and Amazon Pinpoint (specifically its SMS and Voice capabilities).
We will construct a NestJS application triggered by incoming SMS messages, allowing you to build interactive experiences, automate responses, or process SMS data effectively.
Project Goals:
Core Technologies:
System Architecture:
Prerequisites:
npm install -g @nestjs/cli
Final Outcome:
By the end of this guide, you will have a deployed AWS Lambda function running a NestJS application. This application will automatically process incoming SMS messages sent to your configured AWS phone number, with logs visible in CloudWatch and a clear structure for adding custom business logic and replies.
1. Setting Up the Local NestJS Project
Let's initialize our NestJS project and install necessary dependencies.
Create NestJS Project: Open your terminal and run:
Choose your preferred package manager (npm or yarn) when prompted.
Project Structure: NestJS creates a standard project structure:
This structure promotes modularity and separation of concerns.
Install AWS SDK v3: We need the AWS SDK to interact with AWS services, specifically for potentially sending replies via the Pinpoint SMS/Voice v2 API.
Why SDK v3? It offers modularity (smaller bundles) and improved TypeScript support compared to v2.
Environment Variables: Create a
.env
file in the project root for local development configuration. Important: Never commit.env
files containing secrets (like AWS keys) to your source code repository. Use a.env.example
file to track required variables without their values. Use secure methods like CI/CD secrets or AWS Secrets Manager for production deployments.AWS_REGION
: The AWS region where you'll deploy resources (e.g.,us-east-1
,eu-west-2
).AWS_ACCESS_KEY_ID
/AWS_SECRET_ACCESS_KEY
: Generate these from the AWS IAM console for a dedicated user only for local development. In production Lambda, rely on execution roles for permissions.SNS_TOPIC_ARN
,SQS_QUEUE_URL
: You will create these in the next step. Note their ARNs/URLs.PINPOINT_PHONE_NUMBER
: The number obtained from Amazon Pinpoint (SMS and Voice section).PINPOINT_APP_ID
: If you created an Amazon Pinpoint project for analytics or campaigns, find its ID in the Pinpoint console. Often not strictly required just for basic two-way SMS configuration.Configuration Module (Optional but Recommended): Use NestJS's
@nestjs/config
module for managing environment variables.Update
src/app.module.ts
:2. Setting Up AWS Resources (SNS, SQS, Two-Way SMS)
Now, configure the necessary AWS infrastructure to route SMS messages to where our future Lambda function can process them. We recommend using Infrastructure as Code (IaC) like the Serverless Framework or AWS CDK for managing these resources in production, but we'll outline the manual console steps here for clarity.
Create an SNS Topic: This topic will receive notifications when an SMS arrives at your AWS phone number.
InboundSmsNotifications
).arn:aws:sns:us-east-1:123456789012:InboundSmsNotifications
). You'll need this for.env
and the phone number configuration.Create an SQS Queue: This queue will subscribe to the SNS topic and hold messages until the Lambda function processes them. This provides decoupling and automatic retries.
InboundSmsProcessingQueue
).YOUR_REGION
,YOUR_ACCOUNT_ID
,YOUR_SNS_TOPIC_ARN
, andYOUR_SQS_QUEUE_ARN
with your actual values.InboundSmsDeadLetterQueue
) and configure it here with aMaximum receives
count (e.g., 3).Subscribe SQS Queue to SNS Topic:
InboundSmsNotifications
).Amazon SQS
.InboundSmsProcessingQueue
) you created.Configure Two-Way SMS for Your Phone Number: Link your AWS-provisioned phone number to the SNS topic.
SNS topic
.InboundSmsNotifications
) you created from the dropdown.sms-voice.amazonaws.com
or is broadly permissive).YOUR_SNS_TOPIC_ARN
):sms-voice.amazonaws.com
service principal.Message Flow Recap: SMS Sent to AWS Number -> Amazon Pinpoint Service -> SNS Topic (
InboundSmsNotifications
) -> SQS Queue (InboundSmsProcessingQueue
) -> (Next Step: Lambda Trigger)3. Implementing Core Functionality in NestJS
Now we adapt the NestJS application to run in Lambda and process messages from the SQS queue. We'll use a custom strategy pattern similar to the one described in the research for handling microservice-like events triggered by Lambda.
Create a Custom Transport Strategy: This strategy won't actively listen like traditional NestJS microservice transports; instead, it provides a way for our Lambda handler to find the correct NestJS method based on the incoming message content.
Adapt
main.ts
for Lambda: Modify the main entry point to bootstrap NestJS once per Lambda container instance and handle incomingSQSEvent
records. We useReplaySubject
from RxJS to manage the bootstrapping process efficiently, reducing cold start impact on subsequent invocations within the same container.Key Points:
bootstrap()
initializes NestJS and returns the strategy instance.strategySubject.next(strategy)
stores the instance in a ReplaySubject.handler
function retrieves the strategy usingfirstValueFrom(strategySubject)
, ensuring Nest is ready.SQSEvent.Records
.processRecord
parses therecord.body
. Crucially, this logic assumes SNS raw message delivery is enabled. A prominent comment explains how to adjust if it's disabled.messagePattern
(e.g., based on keywords in themessageBody
, or a default pattern).strategy.getHandlerByPattern(messagePattern)
retrieves the corresponding NestJS handler function.handler(smsPayload, context)
executes the NestJS function.Create an SMS Handling Module and Controller: Organize the SMS processing logic within a dedicated NestJS module.
Update
src/sms/sms.module.ts
:Import
SmsModule
intosrc/app.module.ts
:Implement the controller with an
@EventPattern
decorator matching the pattern used inmain.ts
.Implement the SMS Service (Optional Reply Logic): Create methods for business logic, like sending replies using the AWS SDK.
4. Troubleshooting and Caveats
sns:Publish
to your SNS topic.sms-voice.amazonaws.com
) or the specific role ARN.sqs:SendMessage
from the SNS topic ARN (aws:SourceArn
condition).sqs:ReceiveMessage
,sqs:DeleteMessage
,sqs:GetQueueAttributes
for the SQS queue.logs:CreateLogGroup
,logs:CreateLogStream
,logs:PutLogEvents
for CloudWatch Logs.cloudwatch:PutMetricData
(often included in basic execution role).pinpoint-sms-voice:SendTextMessage
(or broadersms-voice:*
) if sending replies, using thepinpoint-sms-voice-v2
API.JSON.parse(record.body).Message
). Log the rawrecord.body
in CloudWatch to verify the structure you receive.SmsService
). This means the client is created once per Lambda container instance, which is efficient. Ensure the Lambda execution role provides the necessary credentials.