Frequently Asked Questions
You can send SMS messages by creating a RedwoodJS service that uses the Sinch SMS API. This service will handle the logic for sending messages and interacting with your database to store message history. A GraphQL mutation will expose this functionality to your RedwoodJS frontend.
Ngrok creates a secure tunnel from a public URL to your local development environment. This is essential for testing webhooks locally because Sinch needs to deliver messages to a publicly accessible URL. Without ngrok, Sinch cannot reach your local server during development.
The .env file stores sensitive configuration details such as API keys and database URLs, keeping them separate from your codebase. This approach enhances security and prevents accidental exposure of credentials in version control systems like Git. The file should *not* be committed to your repository.
Verification is the first step after the inboundSms function is invoked. Always verify the webhook's signature to confirm its origin and integrity. This process prevents malicious actors from spoofing requests, protecting your data and application.
Yes, this guide describes setting up a Prisma schema and migrations to store message data like body, sender/receiver numbers, timestamps, and Sinch's external ID for each message. This provides a robust record of all sent and received SMS activity for your application.
Set up a Redwood Function as a webhook handler. Configure the webhook in the Sinch dashboard and point its target URL to your Redwood function's publicly accessible endpoint (using ngrok during development). The webhook will then deliver incoming messages to your application as they arrive.
Redwood Functions handle serverless logic, perfect for webhook endpoints. The `inboundSms` function receives data from Sinch when an SMS is sent to your Sinch virtual number. The function then stores the message information in the database and can initiate logic based on the incoming message (like automated replies).
Sinch's Conversation API provides a unified approach for handling messages across multiple channels (SMS, chat, etc.). While the dedicated SMS API can handle outbound messages, the Conversation API's webhooks are the standard way to receive inbound SMS through Sinch.
For simple outbound-only SMS, the SMS API (used via `@sinch/sdk-core` here) might be simpler. For inbound messages or scenarios requiring more advanced Conversation features (e.g., multiple channels), use the Conversation API's webhooks and associated functionalities.
Yes, ngrok creates a tunnel to your local development environment, enabling Sinch to send webhook requests to your `inboundSms` function locally. Ensure both your Redwood server and ngrok are running during local tests.
Sinch automatically retries webhook deliveries based on the status code your function returns. Return 5xx status codes (e.g., 500 Internal Server Error) for errors you want Sinch to retry. Return a 2xx code (e.g., 200 OK) when successful.
The provided `sendSms` service includes a `try...catch` block. Enhance it for production by implementing retries for transient errors, catching Sinch-specific errors, and logging error details appropriately.
It's crucial for securing your webhook. It's used to verify the signature of incoming requests, ensuring they originate from Sinch. This prevents unauthorized actors from sending fake webhook requests.
Add a `Message` model to your `schema.prisma` file. This model should include fields like `body`, `fromNumber`, `toNumber`, `direction`, `status`, `externalId`, and timestamps. Run `yarn rw prisma migrate dev` to apply the changes to your database.
This guide provides a step-by-step walkthrough for integrating Sinch SMS capabilities into a RedwoodJS application, enabling both outbound and inbound (two-way) messaging. We'll build a system where your application can send SMS messages via Sinch and receive incoming messages sent to your Sinch virtual number through webhooks.
Project Goals:
Core Technologies:
ngrok
(for development): A tool to expose local development servers to the internet, necessary for testing Sinch webhooks.System Architecture:
Prerequisites:
ngrok
installed globally (npm install -g ngrok
oryarn global add ngrok
) for local webhook testing.Final Outcome:
By the end of this guide, you will have a functional RedwoodJS application capable of:
1. Project Setup & Configuration
Let's start by creating a new RedwoodJS project and setting up the necessary environment variables and dependencies.
1. Create RedwoodJS Project:
Open your terminal and run the following command:
Follow the prompts. Choose JavaScript or TypeScript based on your preference (this guide will use examples assuming JavaScript, but the concepts apply equally to TypeScript). Select your preferred database (PostgreSQL recommended).
2. Install Sinch SDK:
We need the Sinch Node.js SDK to interact with their API easily.
@sinch/sdk-core
: The official Sinch SDK for Node.js.cross-fetch
: Often required as a peer dependency or for environments wherefetch
isn't native.3. Environment Variables:
RedwoodJS uses a
.env
file for environment variables. Create one in the project root (redwood-sinch-messaging/.env
):DATABASE_URL
: Crucially, update this with the connection string for your chosen database (PostgreSQL, MySQL, SQLite, etc.). The example provided is for a local PostgreSQL setup.SINCH_PROJECT_ID
,SINCH_KEY_ID
,SINCH_KEY_SECRET
: Replace theYOUR_...
placeholders with your actual credentials found in your Sinch Dashboard underAccess Keys
. You might need to create a new API key pair. TheSINCH_KEY_SECRET
is only shown once upon creation – save it securely.SINCH_NUMBER
: ReplaceYOUR_SINCH_VIRTUAL_NUMBER
with the virtual phone number you acquired from Sinch, formatted in E.164 (e.g.,+12223334444
). Find it underNumbers
>Your virtual numbers
in the dashboard. Ensure this number is SMS-enabled and configured for the correct region/campaign if necessary (e.g., 10DLC in the US).SINCH_WEBHOOK_SECRET
: ReplaceYOUR_STRONG_RANDOM_WEBHOOK_SECRET
with a unique, cryptographically strong random string that you generate. This is used to secure your webhook endpoint.4. Initialize Sinch Client (Utility):
To avoid repeating client initialization, let's create a utility function.
Create
api/src/lib/sinch.js
:This utility safely initializes the Sinch client using environment variables and ensures only one instance is created (singleton pattern). It includes basic error handling for missing or placeholder credentials.
2. Database Schema and Data Layer
We need a way to store messages. Let's define a Prisma schema.
1. Define Prisma Schema:
Open
api/db/schema.prisma
and add aMessage
model:externalId
: Stores the unique ID assigned by Sinch to the message. Useful for correlating status updates later.direction
: Tracks whether the message was sent from our app (OUTBOUND
) or received by our app (INBOUND
).status
,sentAt
,receivedAt
: Timestamps and status information.2. Create Database Migration:
Apply the schema changes to your database:
Enter a name for the migration when prompted (e.g.,
add_message_model
). This command generates SQL migration files and applies them to your database.3. Implementing Core Functionality: Sending SMS
Let's build the service and GraphQL mutation to send SMS messages.
1. Create Redwood Service:
Generate a service to handle message logic:
This creates
api/src/services/messages/messages.js
,messages.scdl.js
, and test files.2. Implement
sendSms
Service Function:Edit
api/src/services/messages/messages.js
:requireAuth
(you'll need to set up Redwood Auth if you haven't). Customize roles as needed.SINCH_NUMBER
. Add more robust validation.try...catch
to handle potential errors during DB operations or Sinch API calls. Logs errors using Redwood's logger.Message
record before sending and updates it with the Sinch ID (response.id
from the batch send) and status upon success or failure. This ensures you have a record even if the Sinch call fails.sinchClient.sms.batches.send
to send the message.PENDING
, updates toSENT
(orACCEPTED
) if Sinch accepts the request, andFAILED
on error. Note thatSENT
here means "accepted by Sinch," not necessarily "delivered to handset." True delivery status requires setting up Delivery Report webhooks (an advanced topic).3. Define GraphQL Schema:
Edit
api/src/graphql/messages.sdl.js
to define the types and mutation:Message
type mirroring the Prisma model.Direction
enum.Query
types (protected by@requireAuth
).SendSmsInput
type for the mutation payload.sendSms
mutation, also protected by@requireAuth
.4. Test Sending SMS (GraphQL Playground):
Start your development server:
yarn rw dev
Navigate to
http://localhost:8911/graphql
(or your configured GraphQL endpoint).Use the following mutation (replace the placeholder
to
number with a real E.164 formatted number you can check):Execute the mutation. Check your console logs (
api
side) for success or error messages. Verify the recipient phone received the SMS. Check your database to see the newMessage
record.4. Implementing Core Functionality: Receiving SMS
Now, let's set up the webhook handler to receive incoming messages. Sinch uses its Conversation API webhooks for inbound messages across various channels, including SMS.
1. Create Redwood Function:
Redwood Functions are ideal for handling webhooks. Generate one:
This creates
api/src/functions/inboundSms.js
.2. Implement Webhook Handler Logic:
Edit
api/src/functions/inboundSms.js
:verifySinchSignature
. This is critical. It uses headers and yourSINCH_WEBHOOK_SECRET
. Strongly emphasized the unreliability of theJSON.stringify
workaround for raw body access in serverless environments and recommended alternatives for production. Added checks for placeholder secret and invalid timestamps.MESSAGE_INBOUND
,INCOMING
direction, and presence oftext_message
. Extracts relevant fields. Added checks for missing critical fields.externalId
to prevent storing duplicate messages if Sinch retries a webhook.Message
record withdirection: 'INBOUND'
.500
status code, potentially prompting Sinch to retry. Returns400
for malformed payloads.200 OK
quickly.3. Expose Local Endpoint with
ngrok
:To let Sinch send webhooks to your local development machine, use
ngrok
.Make sure your Redwood dev server is running (
yarn rw dev
). The API server typically runs on port 8911.Open a new terminal window and run:
ngrok
will display forwarding URLs. Copy thehttps://
URL (e.g.,https://<random-string>.ngrok-free.app
).4. Configure Sinch Webhook:
ngrok
https://
URL, appending the path to your function:/.redwood/functions/inboundSms
. The full URL will look like:https://<random-string>.ngrok-free.app/.redwood/functions/inboundSms
SINCH_WEBHOOK_SECRET
you defined in your.env
file (make sure it's not the placeholder!).MESSAGE_INBOUND
(from the CONTACT section)MESSAGE_DELIVERY
for outbound status updates (advanced).CONVERSATION_START
,EVENT_INBOUND
. Start withMESSAGE_INBOUND
.5. Test Receiving SMS:
yarn rw dev
andngrok http 8911
are running.SINCH_NUMBER
.yarn rw dev
). You should see logs from theinboundSms
function. Look for ""Sinch webhook signature verified successfully"" and ""Inbound message stored successfully.""Message
record withdirection: 'INBOUND'
has been created.ngrok
console (http://localhost:4040
) for incoming requests and responses (especially the status code returned by your function). Check the Redwood API logs for detailed errors (pay close attention to signature verification failures or errors during database operations).5. Error Handling, Logging, and Retries
Error Handling: The provided code includes basic
try...catch
blocks. For production, expand this:externalId
).Logging: Redwood's built-in
logger
is used. Configure log levels (trace
,debug
,info
,warn
,error
,fatal
) appropriately for different environments inapi/src/lib/logger.js
. Ensure sensitive data (like full message bodies if required by privacy rules) is redacted or handled carefully in logs. Structure logs as JSON for easier parsing by log aggregation tools.Retries (Outbound): For transient network errors when calling the Sinch API, implement a simple retry mechanism. Libraries like
async-retry
can help.Retries (Inbound): Sinch handles webhook retries based on the HTTP status code you return. Returning
5xx
signals an error and prompts Sinch to retry (check Sinch docs for their exact retry policy). Returning2xx
confirms receipt. Returning4xx
(like401
for bad signature or400
for bad payload) usually tells Sinch not to retry. Ensure your handler is idempotent – processing the same webhook multiple times should not cause duplicate data or unintended side effects (theexternalId
check helps with this).6. Security Features