code examples
code examples
Guide: Implementing RedwoodJS AWS SNS Inbound/Two-Way Messaging
A walkthrough for setting up RedwoodJS to receive and handle inbound SMS messages using Amazon Pinpoint and AWS SNS.
This guide provides a complete walkthrough for setting up and handling inbound SMS messages within your RedwoodJS application using Amazon Pinpoint and AWS Simple Notification Service (SNS). By following these steps, you can enable two-way communication, allowing your application to receive and process SMS messages sent by users to your designated AWS phone number.
Project Goal: To build a system where a RedwoodJS application can reliably receive SMS messages sent to an AWS-provisioned phone number, process them, and optionally store them in a database.
Problem Solved: Enables applications to react to user-initiated SMS, opening possibilities for interactive SMS bots, customer support via text, notification responses, data collection, and more, without needing to poll an external service.
Technologies Used:
- RedwoodJS: A full-stack JavaScript/TypeScript framework for building modern web applications. We'll use its API-side functions to handle incoming webhooks.
- Amazon Pinpoint: The AWS service used to acquire and manage phone numbers capable of sending and receiving SMS messages. (Note: Some features related to SMS phone numbers were previously found under ""End User Messaging SMS"" in the AWS console, but are now integrated within the broader Pinpoint service.)
- AWS Simple Notification Service (SNS): A managed messaging service. Pinpoint will publish incoming SMS messages to an SNS topic, which then forwards them to our application.
- AWS Lambda: RedwoodJS API functions typically deploy as serverless functions (like AWS Lambda when using the Serverless Framework deploy target), which will execute our message handling logic.
- AWS IAM: Used to manage permissions between AWS services (Pinpoint -> SNS -> Lambda/API Gateway).
- Prisma: RedwoodJS's default ORM for database interaction (optional, but included for storing messages).
- (Optional) Serverless Framework: Used for deploying the RedwoodJS application to AWS.
System Architecture:
[User's Phone] --(SMS)--> [Amazon Pinpoint Phone Number]
|
'--(Publishes Message)--> [AWS SNS Topic]
|
'--(HTTPS POST Request)--> [RedwoodJS API Function (via API Gateway/Lambda)]
|
'--(Processes Message)--> [RedwoodJS Service Layer]
|
'--(Stores Message)--> [Database (e.g., PostgreSQL via Prisma)]
Prerequisites:
- An AWS account with permissions to manage Pinpoint, SNS, IAM, Lambda, and API Gateway.
- Node.js (LTS version recommended) and Yarn installed.
- A way to run RedwoodJS CLI commands (typically handled locally by creating a project with
yarn create redwood-app ..., or you can usenpx redwood ...). - AWS CLI installed and configured with appropriate credentials (
aws configure). - Basic familiarity with RedwoodJS concepts (API functions, services, Prisma).
- A mobile phone capable of sending SMS messages for testing.
Final Outcome: A deployed RedwoodJS application with an API endpoint that securely receives SMS messages forwarded by AWS SNS, verifies their authenticity, parses the content, and stores them in a database.
1. Setting up the RedwoodJS Project
First, create a new RedwoodJS application.
-
Create Redwood App: Open your terminal and run:
bashyarn create redwood-app ./redwood-sns-inbound --typescript -
Navigate to Project Directory:
bashcd redwood-sns-inbound -
Initialize Database (Optional but Recommended): If you plan to store messages, set up Prisma and your database. This example uses SQLite for simplicity during development, but you should configure PostgreSQL or another production database for deployment. The default
schema.prismaincludes a SQLite provider.bashyarn rw prisma migrate dev --name initial-setupThis command creates the initial database file and generates the Prisma client.
This establishes the basic RedwoodJS project structure. We'll add specific files and configurations as needed.
2. AWS Setup: Acquiring a Phone Number & Enabling Two-Way SMS
You need a dedicated phone number within AWS that supports two-way SMS. This is managed through Amazon Pinpoint.
-
Navigate to Amazon Pinpoint:
- Log in to your AWS Console.
- Use the search bar to find and navigate to ""Amazon Pinpoint"".
- In the Pinpoint console, navigate to SMS and voice (or similar section for phone number management). Note: If you search for ""End User Messaging SMS"", it might redirect you here.
- Ensure you are in the AWS Region where you want to operate. Two-way SMS availability varies by region and country. Check the SMS and MMS country capabilities and limitations documentation.
-
Request a Phone Number:
- In the navigation pane, under Configurations or Number management, find and click Phone numbers.
- Click Request phone number.
- Select the Country for the number.
- Under Capabilities, ensure SMS is selected.
- Choose the Number type (e.g., Long code, Toll-free). Long codes are often suitable for two-way interaction. Note: Sender IDs do not support two-way SMS.
- Click Next.
- Review the request and click Request. Provisioning might take a few minutes.
-
Enable Two-Way SMS and Configure SNS Destination:
- Once the number is provisioned and listed on the Phone numbers page, click on the number.
- Go to the Two-way SMS tab (or similar configuration section).
- Click Edit settings.
- Check the box Enable two-way message.
- For Destination type, select Amazon SNS.
- For Amazon SNS, select New Amazon SNS topic. AWS will create a topic specifically for this number. This simplifies initial permission setup.
- Alternatively, if you have an existing SNS topic, select Existing Amazon SNS topic and choose it from the dropdown. You will then need to manage IAM roles or SNS topic policies manually (see AWS docs). Using a New topic is recommended for this guide.
- AWS will typically handle the necessary permissions (
Two-way channel role) automatically when creating a new topic. - Click Save changes.
-
Note Down Key Information:
- Phone Number: The number you acquired (e.g.,
+12065550100). - SNS Topic ARN: After saving, find the ARN of the newly created or selected SNS topic. You can find this by navigating to the SNS service in the AWS Console, clicking ""Topics"", and finding the topic associated with your number (it might have a name related to Pinpoint or the number). It will look something like:
arn:aws:sns:us-east-1:123456789012:pinpoint-sms-voice-v2-sms-xxxxxxxx.
- Phone Number: The number you acquired (e.g.,
You now have a phone number configured to send incoming SMS messages to an SNS topic.
3. AWS Setup: Preparing SNS for HTTPS Subscription
While Pinpoint configures the SNS topic to receive messages, we need to configure the subscription later to send these messages to our RedwoodJS HTTPS endpoint. We don't create the subscription yet (as our endpoint isn't deployed), but keep these points in mind:
- HTTPS Endpoint: Our RedwoodJS function will expose an HTTPS URL.
- Subscription Confirmation: When we add the HTTPS subscription later, SNS will send a
SubscriptionConfirmationPOST request to our endpoint. Our code must handle this request by retrieving theSubscribeURLfrom the request body and making a GET request to that URL. - Raw Message Delivery: For standard validation using libraries like
sns-validator, you should disable "Raw message delivery" when creating the subscription later. When disabled (the default), SNS wraps the original Pinpoint message payload within its own standard JSON structure, which includes the necessary fields (SigningCertURL,Signature, etc.) for the validator library to work correctly. If raw delivery were enabled, SNS would send only the Pinpoint payload directly in the HTTP request body, skipping the SNS wrapper and breaking standard validation methods.
4. Implementing the RedwoodJS Webhook Handler
Now, let's create the RedwoodJS API function that will receive notifications from SNS.
-
Generate the API Function:
bashyarn rw g function inboundSms --typescriptThis creates
api/src/functions/inboundSms.ts. -
Install SNS Message Validator: We need a library to securely verify that incoming requests genuinely originate from AWS SNS.
bashyarn workspace api add sns-validator -
Implement the Handler Logic (
api/src/functions/inboundSms.ts):typescript// api/src/functions/inboundSms.ts import type { APIGatewayEvent, Context } from 'aws-lambda' import { logger } from 'src/lib/logger' import { db } from 'src/lib/db' // If storing messages import MessageValidator from 'sns-validator' import https from 'https' const validator = new MessageValidator() /** * The handler function is invoked by AWS Lambda. * * @param event The Lambda event input * @param context The Lambda context input (Unused) */ export const handler = async (event: APIGatewayEvent, _context: Context) => { logger.info('Inbound SMS function invoked') let requestBody: any // 1. Parse the request body // API Gateway might base64 encode the body try { if (event.isBase64Encoded && event.body) { requestBody = JSON.parse(Buffer.from(event.body, 'base64').toString('utf-8')) } else if (event.body) { requestBody = JSON.parse(event.body) } else { logger.error('Request body is missing or empty') return { statusCode: 400, body: 'Bad Request: Missing body' } } logger.debug({ custom: requestBody }, 'Parsed SNS request body') } catch (error) { logger.error({ error }, 'Failed to parse request body') return { statusCode: 400, body: 'Bad Request: Invalid JSON' } } // 2. Validate the SNS message signature try { const message = await new Promise<any>((resolve, reject) => { // Pass the parsed SNS message (the standard SNS wrapper JSON) to the validator validator.validate(requestBody, (err, message) => { if (err) { logger.error({ error: err }, 'SNS message validation failed') reject(err) } else { logger.info('SNS message signature validated successfully') resolve(message) // message here is the validated SNS message object } }) }) // 3. Handle different SNS message types const messageType = message.Type // Type is part of the SNS wrapper if (messageType === 'SubscriptionConfirmation') { logger.info( `Received SNS SubscriptionConfirmation. Confirming subscription...` ) // Confirm the subscription by visiting the SubscribeURL (part of SNS wrapper) await new Promise<void>((resolve, reject) => { const req = https.get(message.SubscribeURL, (res) => { logger.info( `Subscription confirmation request sent. Status: ${res.statusCode}` ) res.on('data', () => {}) // Consume response data res.on('end', () => resolve()) }) req.on('error', (e) => { logger.error( { error: e }, 'Error confirming SNS subscription' ) reject(e) }) req.end() }) return { statusCode: 200, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: 'SNS Subscription Confirmed' }), } } else if (messageType === 'Notification') { logger.info('Received SNS Notification (SMS Message)') // Because Raw Message Delivery is DISABLED, the actual Pinpoint SMS payload // is JSON stringified within the 'Message' field of the SNS wrapper. const smsPayload = JSON.parse(message.Message) logger.info({ custom: smsPayload }, 'Parsed SMS Payload from SNS Message field') const messageBody = smsPayload.messageBody const originationNumber = smsPayload.originationNumber // User's phone number const destinationNumber = smsPayload.destinationNumber // Your Pinpoint number // --- Your Business Logic Here --- // Example: Store the message in the database try { const storedMessage = await db.inboundMessage.create({ data: { body: messageBody, fromNumber: originationNumber, toNumber: destinationNumber, providerMessageId: smsPayload.messageId, // Optional: Store provider ID from Pinpoint payload // Use the timestamp from the SNS wrapper (when SNS received it) receivedAt: new Date(message.Timestamp), }, }) logger.info( { custom: storedMessage }, 'Successfully stored inbound message' ) } catch (dbError) { logger.error({ error: dbError }, 'Failed to store message in DB') // Decide if this should be a 500 error to potentially trigger SNS retries // return { statusCode: 500, body: 'Internal Server Error: Database operation failed' } } // --- End Business Logic --- // Return 200 OK quickly to SNS to acknowledge receipt return { statusCode: 200, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: 'SMS Received' }), } } else if (messageType === 'UnsubscribeConfirmation') { logger.warn({ custom: message }, 'Received UnsubscribeConfirmation from SNS') // Optional: Handle logic if your endpoint gets unsubscribed return { statusCode: 200, body: 'Unsubscribe Noted' } } else { logger.warn(`Received unknown SNS message type: ${messageType}`) return { statusCode: 400, body: 'Bad Request: Unknown message type' } } } catch (validationError) { logger.error({ error: validationError }, 'SNS Validation Promise Error') // Don't provide detailed error info back to potentially malicious caller return { statusCode: 403, body: 'Forbidden: Invalid signature' } } }Explanation:
- Parse Body: The request body from SNS (via API Gateway) is parsed from JSON. It handles potential base64 encoding.
- Validate Signature:
sns-validatoris used. It takes the parsed JSON object (the SNS wrapper). It fetches the AWS public certificate based on theSigningCertURLfound within the SNS message body JSON and verifies theSignatureagainst the canonical representation of the message. This is critical for security to ensure the request came from AWS SNS and hasn't been tampered with. - Handle Message Type:
SubscriptionConfirmation: When you first link SNS to this endpoint, it sends this type. The code extracts theSubscribeURLfrom the SNS message body and makes an HTTPS GET request to it, confirming to SNS that your endpoint is valid and ready.Notification: This is the actual SMS message notification. Because Raw Message Delivery is disabled, the code parses theMessagefield within the SNS JSON (which itself contains a JSON string representing the Pinpoint payload) to get details likemessageBody,originationNumber, anddestinationNumber. Themessage.Timestampfrom the SNS wrapper indicates when SNS received the message.UnsubscribeConfirmation: Handles cases where the subscription might be removed.
- Business Logic: The placeholder shows where you'd add your application-specific logic, like storing the message using the Prisma service (
db). - Return 200 OK: It's crucial to return a
200 OKstatus code quickly, especially forNotificationtypes, to signal to SNS that the message was received successfully. Failure to do so might cause SNS to retry delivery.
5. Database Integration (Optional)
If you want to store the incoming messages:
-
Define Prisma Schema (
schema.prisma): Add a model to store the message details.prisma// schema.prisma datasource db { provider = "sqlite" // Or "postgresql" for production url = env("DATABASE_URL") } generator client { provider = "prisma-client-js" } model InboundMessage { id String @id @default(cuid()) body String fromNumber String toNumber String providerMessageId String? // Optional: Store the AWS message ID from Pinpoint payload receivedAt DateTime // Timestamp from SNS when it received the message createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([fromNumber]) @@index([toNumber]) @@index([receivedAt]) } -
Run Migration: Apply the schema changes to your database and generate the updated Prisma client.
bashyarn rw prisma migrate dev --name add-inbound-message -
Create Prisma Service (Implicit): Redwood's
dbobject imported in the function (import { db } from 'src/lib/db') allows direct interaction with theInboundMessagemodel, effectively acting as the service layer for this simple case. The function code already includes thedb.inboundMessage.create()call. For more complex logic, you could generate a dedicated service:yarn rw g service inboundMessage.
6. Securely Handling Configuration
Store sensitive information and environment-specific settings using environment variables.
-
Update
.env: Add the AWS credentials and region (if not already configured globally via AWS CLI profiles) and the SNS Topic ARN you noted earlier.dotenv# .env (for local development, DO NOT commit) # DATABASE_URL=""file:./dev.db"" # Or your PostgreSQL URL for local dev # AWS Credentials (only needed if not using ~/.aws/credentials or IAM roles) # AWS_ACCESS_KEY_ID=""YOUR_AWS_ACCESS_KEY_ID"" # AWS_SECRET_ACCESS_KEY=""YOUR_AWS_SECRET_ACCESS_KEY"" AWS_REGION=""us-east-1"" # Replace with your AWS region # SNS Topic ARN for inbound messages SNS_TOPIC_ARN=""arn:aws:sns:us-east-1:123456789012:pinpoint-sms-voice-v2-sms-xxxxxxxx"" # Replace with your ARN -
Update
.env.production: When deploying, you'll need a separate.env.productionfile (which is not committed to Git) or configure environment variables directly in your hosting environment (e.g., Serverless Dashboard parameters, AWS Lambda environment variables). EnsureSNS_TOPIC_ARNand the productionDATABASE_URLare set there.dotenv# .env.production (for production builds/deploys, DO NOT commit) DATABASE_URL=""postgresql://user:password@host:port/database?schema=public"" # Replace with your production DB URL AWS_REGION=""us-east-1"" # Replace with your AWS region SNS_TOPIC_ARN=""arn:aws:sns:us-east-1:123456789012:pinpoint-sms-voice-v2-sms-xxxxxxxx"" # Replace with your ARN # Add any other production-specific env vars
Security Note: Never commit files containing secrets (.env, .env.production) to your version control system. Redwood's default .gitignore should already include these.
7. Deployment
Deploy your RedwoodJS application so the inboundSms function has a live HTTPS endpoint. This guide uses the Serverless Framework integration.
-
Setup Serverless Deploy:
bashyarn rw setup deploy serverlessThis adds necessary configuration files (
serverless.ymlinapiandweb) and dependencies. -
Configure Environment Variables for Deployment: If using Serverless Framework Dashboard for CI/CD or managing secrets, you might need to update
api/serverless.ymlto reference variables stored there (e.g., using${param:VAR_NAME}). For manual deploys from your machine, ensure.env.productionhas the required variables (DATABASE_URL,SNS_TOPIC_ARN,AWS_REGION). -
First Deploy: The first deploy sets up the infrastructure, including the API Gateway endpoint for your function.
bashyarn rw deploy serverless --first-run- Follow the prompts. It will deploy the API, detect the API URL, ask to add
API_URLto.env.production, and then deploy the web side. Ensure you say Yes to adding theAPI_URL. - Note the API Function URL: The deployment output (or the AWS API Gateway console) will show the URL for your API functions. The specific URL for the inbound SMS handler will be like:
https://<your-api-gateway-id>.execute-api.<your-region>.amazonaws.com/inboundSms. Copy this URL.
- Follow the prompts. It will deploy the API, detect the API URL, ask to add
-
Subsequent Deploys: For future updates after code changes:
bashyarn rw deploy serverless
8. Connecting SNS to the Deployed Function
Now, link the SNS topic to your live RedwoodJS function endpoint.
- Navigate to SNS Topic: Go back to the AWS Console -> SNS -> Topics -> Select the topic associated with your Pinpoint number.
- Create Subscription:
- Under the Subscriptions tab, click Create subscription.
- Topic ARN: Should be pre-filled.
- Protocol: Select HTTPS.
- Endpoint: Paste the API Function URL you copied after deployment (e.g.,
https://<your-api-gateway-id>.execute-api.<your-region>.amazonaws.com/inboundSms). - Enable raw message delivery: Leave this box unchecked (disabled). This ensures SNS sends the standard wrapper JSON containing validation fields, which our function and the
sns-validatorlibrary expect. - Click Create subscription.
- Confirm Subscription:
- The subscription status will initially be ""Pending confirmation"".
- SNS will immediately send a
SubscriptionConfirmationPOST request to your endpoint. - Your deployed
inboundSmsfunction should receive this request, log it, extract theSubscribeURL, and make a GET request to it. - Refresh the SNS Subscriptions page after a few seconds. The status should change to Confirmed.
- Troubleshooting: If it stays pending, check the CloudWatch Logs for your
inboundSmsLambda function. Look for theSubscriptionConfirmationmessage and any errors during the confirmation process. Ensure the function logic correctly handles this message type and successfully makes the GET request to theSubscribeURL. You might need to manually copy theSubscribeURLfrom the logs and paste it into your browser to confirm if the function failed.
9. Verification and Testing
Time to test the end-to-end flow.
- Send SMS: Using your mobile phone, send an SMS message (e.g., "Hello Redwood!") to the AWS Pinpoint phone number you acquired.
- Check CloudWatch Logs:
- Navigate to AWS Console -> CloudWatch -> Log groups.
- Find the log group for your Lambda function (usually named something like
/aws/lambda/redwood-sns-inbound-dev-inboundSmsor similar, depending on your stage/service name inserverless.yml). - Open the latest log stream. You should see logs indicating:
- Function invocation.
- Successful SNS signature validation.
- Receipt of an SNS
Notification. - The parsed SMS payload (
messageBody,originationNumber, etc.) extracted from themessage.Messagefield. - Successful database insertion log (if configured).
- A
200 OKstatus code being returned.
- Check Database (Optional):
If you stored the message, verify it's in the database.
- Locally (if testing against local DB):
yarn rw prisma studio - Production: Connect to your production database using a tool like
psql, pgAdmin, TablePlus, etc., and query theInboundMessagetable.
- Locally (if testing against local DB):
- Send Test Failure: Try sending invalid JSON to your endpoint (using
curlor Postman) to ensure error handling works. Test sending a request without a valid SNS signature (if possible, though harder to spoof) to verify the validator rejects it (it should return 403 Forbidden).
Verification Checklist:
- Pinpoint number acquired and Two-Way SMS enabled, pointing to the correct SNS Topic.
- SNS Topic exists.
- RedwoodJS
inboundSmsfunction deployed successfully. - SNS Subscription created with HTTPS protocol, correct endpoint URL, and "Raw message delivery" disabled.
- SNS Subscription status is "Confirmed".
- Sending an SMS to the Pinpoint number triggers the
inboundSmsLambda function (check CloudWatch). - Lambda function successfully validates the SNS signature (check CloudWatch).
- Lambda function correctly parses the SMS message content from the nested
Messagefield (check CloudWatch). - Lambda function returns a
200 OKstatus code (check CloudWatch). - (Optional) Message details are correctly stored in the database.
10. Error Handling & Logging
- Current Implementation: The provided function includes basic
try...catchblocks and uses Redwood'slogger(logger.info,logger.error,logger.debug). Errors during parsing, validation, or database operations are caught and logged. - CloudWatch: AWS Lambda automatically integrates with CloudWatch Logs. This is your primary tool for debugging function execution. Configure CloudWatch Alarms based on error logs or metrics (e.g.,
Errors,Throttles,Duration) for proactive monitoring. - SNS Delivery Status: Configure delivery status logging in SNS to log to CloudWatch Logs. This helps diagnose if SNS is having trouble delivering messages to your endpoint before they even reach your function (e.g., endpoint down, HTTPS errors, non-200 responses). You can set this up in the SNS Topic's properties under ""Delivery status logging"".
- SNS Retries: SNS has built-in retry policies for HTTPS endpoints if it doesn't receive a
200 OKresponse. Be mindful that your function might be invoked multiple times for the same message if processing fails or times out. Design your logic to be idempotent (safe to run multiple times with the same input) if necessary, perhaps by checking if a message with the sameproviderMessageIdalready exists in your database before inserting. - Dead-Letter Queues (DLQs): Configure a Dead-Letter Queue (an SQS queue) on the SNS subscription. If SNS fails to deliver the message to your endpoint after all retries, it can send the failed message to the DLQ for later inspection and manual reprocessing. This prevents message loss. You can configure this in the SNS Subscription's ""Redrive policy (dead-letter queue)"" settings.
11. Security Considerations
- SNS Signature Validation: Absolutely critical. The
sns-validatorlibrary handles this. Never disable this check. It prevents attackers from spoofing requests to your endpoint. - HTTPS: Always use HTTPS for your SNS subscription endpoint. API Gateway and Lambda provide this by default.
- Input Sanitization: While the immediate input is from AWS, if you further process the
messageBody(e.g., display it in a web UI, use it in commands), sanitize it appropriately to prevent cross-site scripting (XSS) or other injection attacks. - Least Privilege: Ensure the IAM role used by your Lambda function has only the necessary permissions (e.g., CloudWatch Logs access, database access if needed). The Serverless Framework typically creates a role with basic execution permissions. You may need to attach policies for database access or other AWS services.
- Rate Limiting: While less common for direct SNS pushes (as SNS controls the push rate), if you expose this functionality differently or anticipate very high volume, consider implementing rate limiting at the API Gateway level.
- Environment Variables: Never expose AWS keys or database credentials in your frontend code or commit them to Git. Use
.envand secure environment variable management in your deployment environment.
12. Troubleshooting and Caveats
-
Messages Not Arriving:
- Check Pinpoint: Is the number correctly configured for two-way SMS? Is the destination the correct SNS Topic ARN?
- Check SNS Topic: Does it exist? Are there any access policy restrictions?
- Check SNS Subscription: Is it Confirmed? Is the Endpoint URL exactly correct? Is Raw Message Delivery correctly disabled?
- Check CloudWatch Logs (Lambda): Is the function even being invoked? Are there errors immediately upon invocation (e.g.,
module not found, IAM permission errors, JSON parsing errors)? - Check CloudWatch Logs (SNS Delivery Status): If configured, do these show delivery failures to your endpoint (e.g.,
4xxor5xxHTTP errors)? - API Gateway Logs: Enable execution logging in API Gateway for more detailed request/response info.
- Region Mismatch: Ensure Pinpoint number, SNS topic, and Lambda function are in the same or compatible AWS regions.
-
SNS Signature Validation Fails:
- Clock Skew: Ensure the server running your Lambda function has reasonably accurate time. While this is a possible cause for signature validation issues, it's unlikely on standard AWS Lambda as AWS manages the time synchronization.
- Incorrect Parsing/Handling: Ensure you are passing the raw, unmodified JSON object received from SNS (after potential base64 decoding) directly to the
validator.validatefunction. Double-check that raw message delivery is indeed disabled in the SNS subscription. - Library Issues: Ensure
sns-validatoris installed correctly in theapiworkspace and deployed with your function.
-
Subscription Stuck in ""Pending Confirmation"":
- Function Error: The function failed to process the
SubscriptionConfirmationrequest (check logs for errors). Verify the code correctly identifies theSubscriptionConfirmationtype and makes the GET request to theSubscribeURL. - Network/Firewall: Ensure AWS SNS can reach your HTTPS endpoint (usually not an issue with standard Lambda/API Gateway setups).
- Timeout: The function took too long to respond or make the GET request to the
SubscribeURL. Lambda functions have execution time limits. - Manual Confirmation: As a last resort, copy the
SubscribeURLfrom the CloudWatch logs (it should be logged when theSubscriptionConfirmationmessage is received) and paste it into a browser to manually confirm the subscription.
- Function Error: The function failed to process the
-
AWS Service Limits: Be aware of potential limits for SNS message throughput, Lambda concurrent executions, etc., though standard SMS volumes are usually well within limits.
-
Pinpoint Number Capabilities: Not all number types or regions support two-way SMS. Verify compatibility.
-
Message Encoding: SMS messages might contain non-standard characters. Ensure your database and processing logic handle UTF-8 correctly. The Pinpoint payload usually indicates the encoding.
-
Delays: SMS delivery is not always instantaneous. Expect potential minor delays through the Pinpoint -> SNS -> Lambda chain.
13. Conclusion & Next Steps
You have successfully configured your RedwoodJS application to receive and process inbound SMS messages using Amazon Pinpoint and SNS. This setup provides a robust and scalable way to handle two-way SMS communication.
Next Steps & Enhancements:
- Send Outbound SMS: Use the AWS SDK for JavaScript v3 (
@aws-sdk/client-pinpoint) within a RedwoodJS service or function to send replies or initiate outbound messages via Pinpoint. - Build a UI: Create RedwoodJS pages/cells to display received messages or provide an interface for sending replies.
Frequently Asked Questions
how to receive sms messages in redwoodjs
You can receive SMS messages in your RedwoodJS application by integrating with Amazon Pinpoint and AWS SNS. Pinpoint provides the phone number, and SNS forwards incoming messages to your RedwoodJS API function as HTTPS POST requests. This allows two-way SMS communication within your application.
what is amazon pinpoint used for with redwoodjs
Amazon Pinpoint is used to acquire and manage the phone number that can send and receive SMS messages. It integrates with SNS to forward incoming messages to your RedwoodJS application, enabling two-way SMS communication.
why use aws sns with amazon pinpoint
AWS SNS is used with Amazon Pinpoint because Pinpoint publishes incoming SMS messages to an SNS topic. The SNS topic then acts as a distribution hub, securely forwarding the message to your RedwoodJS application via an HTTPS POST request to your designated API function.
when to use raw message delivery in sns
You should *disable* Raw Message Delivery in your SNS subscription settings. When disabled, you receive the standard SNS message wrapper, which includes important metadata and security information needed for validation with the `sns-validator` library. Enabling it sends only the Pinpoint payload, breaking compatibility with standard validation methods.
can I store received sms messages in a database
Yes, you can store received SMS messages in a database. The provided code example demonstrates how to use Prisma, RedwoodJS's ORM, to define a database schema, create a migration, and store message details like body, sender/receiver numbers, and timestamps in a database like PostgreSQL or SQLite.
how to set up two way sms with amazon pinpoint
Two-way SMS is enabled within the Amazon Pinpoint console. After acquiring a phone number, navigate to its settings, go to the Two-Way SMS tab, enable two-way messaging, and select a new or existing SNS topic as the message destination. This configures Pinpoint to forward incoming messages to SNS, which then forwards to your application.
what is the role of aws lambda in redwoodjs sms integration
RedwoodJS API functions, including the SMS webhook handler, typically deploy as AWS Lambda functions when using the Serverless Framework. Lambda executes the function code in a serverless environment whenever a message is received from SNS, triggering the message processing logic.
how to verify sms message authenticity from aws sns
Use the `sns-validator` library. It verifies the digital signature attached to incoming SNS messages, ensuring they originated from AWS and haven't been tampered with. The code provides an example of how to validate within your RedwoodJS function handler before processing the message content.
how to handle sns subscription confirmation in redwoodjs
When you first subscribe your RedwoodJS endpoint to the SNS topic, SNS sends a 'SubscriptionConfirmation' message. Your function must handle this by extracting the 'SubscribeURL' from the message and making an HTTPS GET request to that URL. This confirms to SNS that your endpoint is valid and ready to receive notifications.
what to do if sns messages are not arriving in redwoodjs
If messages aren't arriving, check your Pinpoint and SNS configurations in the AWS Console. Ensure the Pinpoint number is properly configured for two-way SMS, and that the SNS topic exists. Double-check that the subscription status is 'Confirmed', the endpoint URL is absolutely correct, and 'Raw Message Delivery' is *disabled*. Verify the subscription is connected to the intended RedwoodJS endpoint and look for any errors by checking your function's CloudWatch logs.
how to troubleshoot sns signature validation failures
SNS signature validation failures are often due to incorrect handling of the incoming JSON, attempting to validate only the Pinpoint payload, or having accidentally enabled raw message delivery on the SNS subscription. Ensure you're passing the full SNS message object to the `sns-validator`, the message hasn't been modified, and raw message delivery is disabled. In rare cases, clock skew on the server running your function could also be a factor.
why is my sns subscription stuck pending confirmation
A pending SNS subscription confirmation usually means your RedwoodJS function failed to process the initial 'SubscriptionConfirmation' message from SNS. Check your function's CloudWatch logs for errors. Verify your function can reach the 'SubscribeURL', and that the function is correctly configured to handle this specific message type (by making the confirmation GET request).
where to find redwoodjs sns integration logs
Logs for your RedwoodJS SNS integration are primarily found in AWS CloudWatch. Look for the log group associated with your deployed Lambda function. API Gateway logs can also provide valuable information on the incoming request and the response your function returns.
what are the security best practices for redwoodjs sns integration
Crucially, always validate the SNS message signature using a library like `sns-validator`. Never disable this. Always use HTTPS. Sanitize any user-provided data within the message body. Follow the principle of least privilege by granting your Lambda function only the necessary IAM permissions. Consider rate limiting at the API Gateway level if you anticipate high volumes or different exposure methods, and never commit AWS keys or database credentials to version control.