code examples

Sent logo
Sent TeamMay 3, 2025 / code examples / Article

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 use npx 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.

  1. Create Redwood App: Open your terminal and run:

    bash
    yarn create redwood-app ./redwood-sns-inbound --typescript
  2. Navigate to Project Directory:

    bash
    cd redwood-sns-inbound
  3. 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.prisma includes a SQLite provider.

    bash
    yarn rw prisma migrate dev --name initial-setup

    This 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.

  1. 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.
  2. 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.
  3. 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.
  4. 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.

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 SubscriptionConfirmation POST request to our endpoint. Our code must handle this request by retrieving the SubscribeURL from 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.

  1. Generate the API Function:

    bash
    yarn rw g function inboundSms --typescript

    This creates api/src/functions/inboundSms.ts.

  2. Install SNS Message Validator: We need a library to securely verify that incoming requests genuinely originate from AWS SNS.

    bash
    yarn workspace api add sns-validator
  3. 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:

    1. Parse Body: The request body from SNS (via API Gateway) is parsed from JSON. It handles potential base64 encoding.
    2. Validate Signature: sns-validator is used. It takes the parsed JSON object (the SNS wrapper). It fetches the AWS public certificate based on the SigningCertURL found within the SNS message body JSON and verifies the Signature against 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.
    3. Handle Message Type:
      • SubscriptionConfirmation: When you first link SNS to this endpoint, it sends this type. The code extracts the SubscribeURL from 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 the Message field within the SNS JSON (which itself contains a JSON string representing the Pinpoint payload) to get details like messageBody, originationNumber, and destinationNumber. The message.Timestamp from the SNS wrapper indicates when SNS received the message.
      • UnsubscribeConfirmation: Handles cases where the subscription might be removed.
    4. Business Logic: The placeholder shows where you'd add your application-specific logic, like storing the message using the Prisma service (db).
    5. Return 200 OK: It's crucial to return a 200 OK status code quickly, especially for Notification types, 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:

  1. 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])
    }
  2. Run Migration: Apply the schema changes to your database and generate the updated Prisma client.

    bash
    yarn rw prisma migrate dev --name add-inbound-message
  3. Create Prisma Service (Implicit): Redwood's db object imported in the function (import { db } from 'src/lib/db') allows direct interaction with the InboundMessage model, effectively acting as the service layer for this simple case. The function code already includes the db.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.

  1. 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
  2. Update .env.production: When deploying, you'll need a separate .env.production file (which is not committed to Git) or configure environment variables directly in your hosting environment (e.g., Serverless Dashboard parameters, AWS Lambda environment variables). Ensure SNS_TOPIC_ARN and the production DATABASE_URL are 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.

  1. Setup Serverless Deploy:

    bash
    yarn rw setup deploy serverless

    This adds necessary configuration files (serverless.yml in api and web) and dependencies.

  2. Configure Environment Variables for Deployment: If using Serverless Framework Dashboard for CI/CD or managing secrets, you might need to update api/serverless.yml to reference variables stored there (e.g., using ${param:VAR_NAME}). For manual deploys from your machine, ensure .env.production has the required variables (DATABASE_URL, SNS_TOPIC_ARN, AWS_REGION).

  3. First Deploy: The first deploy sets up the infrastructure, including the API Gateway endpoint for your function.

    bash
    yarn rw deploy serverless --first-run
    • Follow the prompts. It will deploy the API, detect the API URL, ask to add API_URL to .env.production, and then deploy the web side. Ensure you say Yes to adding the API_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.
  4. Subsequent Deploys: For future updates after code changes:

    bash
    yarn rw deploy serverless

8. Connecting SNS to the Deployed Function

Now, link the SNS topic to your live RedwoodJS function endpoint.

  1. Navigate to SNS Topic: Go back to the AWS Console -> SNS -> Topics -> Select the topic associated with your Pinpoint number.
  2. 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-validator library expect.
    • Click Create subscription.
  3. Confirm Subscription:
    • The subscription status will initially be ""Pending confirmation"".
    • SNS will immediately send a SubscriptionConfirmation POST request to your endpoint.
    • Your deployed inboundSms function should receive this request, log it, extract the SubscribeURL, 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 inboundSms Lambda function. Look for the SubscriptionConfirmation message and any errors during the confirmation process. Ensure the function logic correctly handles this message type and successfully makes the GET request to the SubscribeURL. You might need to manually copy the SubscribeURL from 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.

  1. Send SMS: Using your mobile phone, send an SMS message (e.g., "Hello Redwood!") to the AWS Pinpoint phone number you acquired.
  2. 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-inboundSms or similar, depending on your stage/service name in serverless.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 the message.Message field.
      • Successful database insertion log (if configured).
      • A 200 OK status code being returned.
  3. 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 the InboundMessage table.
  4. Send Test Failure: Try sending invalid JSON to your endpoint (using curl or 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 inboundSms function 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 inboundSms Lambda function (check CloudWatch).
  • Lambda function successfully validates the SNS signature (check CloudWatch).
  • Lambda function correctly parses the SMS message content from the nested Message field (check CloudWatch).
  • Lambda function returns a 200 OK status code (check CloudWatch).
  • (Optional) Message details are correctly stored in the database.

10. Error Handling & Logging

  • Current Implementation: The provided function includes basic try...catch blocks and uses Redwood's logger (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 OK response. 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 same providerMessageId already 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-validator library 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 .env and 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., 4xx or 5xx HTTP 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.validate function. Double-check that raw message delivery is indeed disabled in the SNS subscription.
    • Library Issues: Ensure sns-validator is installed correctly in the api workspace and deployed with your function.
  • Subscription Stuck in ""Pending Confirmation"":

    • Function Error: The function failed to process the SubscriptionConfirmation request (check logs for errors). Verify the code correctly identifies the SubscriptionConfirmation type and makes the GET request to the SubscribeURL.
    • 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 SubscribeURL from the CloudWatch logs (it should be logged when the SubscriptionConfirmation message is received) and paste it into a browser to manually confirm the subscription.
  • 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.