Frequently Asked Questions
Use the Vonage Messages API and a RedwoodJS function. Install the Vonage Server SDK, create an API endpoint, and use the SDK's `messages.send()` method. Ensure your Vonage application is configured with the correct credentials and a linked virtual number.
The Vonage Messages API allows RedwoodJS apps to send and receive SMS messages and handle delivery statuses. It offers features like delivery receipts and two-way communication.
Prisma is RedwoodJS's default ORM, simplifying database interactions. The `SmsMessage` model in the Prisma schema stores message details, statuses, and Vonage IDs.
Ngrok is essential during development to expose your local RedwoodJS API for receiving Vonage webhooks. For production, use your platform's public URL/ingress.
Yes, configure a Vonage virtual number and set the inbound webhook URL in your Vonage Application settings. Create a RedwoodJS function to handle incoming messages at the specified endpoint.
Create a RedwoodJS function at the status webhook URL defined in your Vonage Application settings. This function will receive status updates (e.g., 'delivered', 'failed') and can update your database accordingly.
The `vonageMessageId`, returned by the Vonage API, uniquely identifies each message. It's crucial for correlating delivery status updates with the original outbound message in your database.
In your Vonage Application settings, set the Inbound and Status URLs to your public RedwoodJS API endpoints (e.g., `/api/webhooks/inbound`). Use `ngrok` for local development and your platform's URL for production.
Vonage relies on a quick `200 OK` response to acknowledge webhook receipt. Failure to do so can lead to Vonage retrying the webhook, potentially causing duplicate processing.
Store your private key file securely, never commit it to version control, and add it to `.gitignore`. For production, prefer using environment variables or secret stores managed by your platform.
Be mindful of how `process.cwd()` resolves in built functions. Ensure the `VONAGE_PRIVATE_KEY_PATH` is correctly set in your .env file. Consider reading the content of the key into an environment variable for improved portability.
Double-check your Vonage application setup, including the webhook URLs and linked number. Verify ngrok is running correctly during development. Use the ngrok interface to inspect incoming requests.
Ensure your database is running and accessible from the Redwood API. Verify the DATABASE_URL in your .env file is correct and Prisma migrations were applied successfully.
Vonage expects numbers in E.164 format (e.g., +14155551212). Use a library like `libphonenumber-js` or ensure your input is properly formatted before sending to Vonage.
Developer Guide: Implementing RedwoodJS SMS with Vonage Delivery Status
This guide provides a step-by-step walkthrough for building a RedwoodJS application capable of sending SMS messages via the Vonage Messages API, receiving inbound SMS messages, and handling delivery status callbacks. We'll cover everything from project setup and Vonage configuration to database integration, error handling, and deployment considerations.
By the end of this tutorial, you will have a functional RedwoodJS application with API endpoints to send SMS, and webhooks correctly configured to process incoming messages and delivery status updates from Vonage, storing relevant information in a database.
Project Overview and Goals
We aim to build a robust system within a RedwoodJS application that leverages the Vonage Messages API for SMS communication. This solves the common need for applications to programmatically send notifications, alerts, or engage in two-way conversations via SMS, while also providing visibility into message delivery success or failure.
Key Features:
Technologies Used:
@vonage/server-sdk
: The official Vonage Node.js SDK for simplified API interaction.ngrok
: A tool to expose local development servers to the internet, essential for testing webhooks.System Architecture:
(Note: For a final published version, consider replacing this ASCII diagram with a clearer graphical representation.)
Prerequisites:
ngrok
installed and authenticated (Download ngrok).1. Setting Up the Project
Let's initialize a new RedwoodJS project and configure our environment.
Create RedwoodJS App: Open your terminal and run:
This scaffolds a new RedwoodJS project in the
redwood-vonage-sms
directory.Install Vonage SDK: Navigate to the API workspace and install the Vonage Node.js SDK:
Configure Vonage Account:
"Redwood SMS App"
)."Generate public and private key"
. Save theprivate.key
file securely, for example, in the root directory of your Redwood project (outside theapi
orweb
folders). Do not commit this file to Git."Inbound URL"
and"Status URL"
blank or use placeholders likehttp://example.com/inbound
. We'll update these withngrok
URLs later."Generate application"
. Note down the Application ID."Link virtual numbers"
, and link your purchased Vonage virtual number to this application."SMS settings"
. Ensure that the API preference is set to Messages API. This is crucial for the webhooks and SDK usage in this guide. Click"Save changes"
. (Ensure this setting is correct in your dashboard; it determines how SMS features behave).Environment Variables: Create a
.env
file in the root of your RedwoodJS project (alongsideredwood.toml
). Add your Vonage credentials and number:VONAGE_PRIVATE_KEY_PATH
points correctly to where you saved theprivate.key
file relative to the project root. Note the potential runtime path resolution issue discussed later..env
andprivate.key
to your.gitignore
file to prevent committing secrets.Setup
ngrok
: Webhooks require a publicly accessible URL.ngrok
tunnels requests to your local machine.Open a new terminal window in your project root.
Run
ngrok
to forward to Redwood's default API port (8911):ngrok
will display forwarding URLs (e.g.,https://<unique-code>.ngrok-free.app
). Copy thehttps
URL. (Check your terminal for output similar to the example shown in ngrok documentation).Note:
ngrok
is for development/testing. For production, you'll need a stable public URL for your deployed API endpoint, managed via your hosting platform's ingress, load balancer, or direct server exposure.Update Vonage Webhook URLs:
YOUR_NGROK_HTTPS_URL/api/webhooks/inbound
.YOUR_NGROK_HTTPS_URL/api/webhooks/status
.POST
.Now, Vonage will send POST requests to these
ngrok
URLs, which will be forwarded to your local RedwoodJS API running on port 8911.2. Database Schema and Data Layer
We need a way to store information about the SMS messages we send and receive.
Define Prisma Schema: Open
api/db/schema.prisma
and add the following model:vonageMessageId
: Stores the unique ID returned by Vonage, crucial for correlating status updates. The@unique
attribute automatically creates a database index.direction
: Tracks if the message was sent or received by our app.status
: Holds the latest known status.errorCode
: Stores error details if delivery fails.Apply Migrations: Run the Prisma migration command to apply these changes to your database:
This creates/updates the database schema and generates the Prisma client.
3. Building the API Layer (RedwoodJS Functions)
RedwoodJS Functions are serverless functions deployed alongside your API. We'll create functions to send SMS and handle incoming webhooks.
> Important Production Consideration: Vonage webhooks expect a quick
200 OK
response (within a few seconds). Performing database operations directly within the webhook handler, as shown here for simplicity, is risky in production. If the database write fails temporarily, you'll return200 OK
(to prevent Vonage retries), but the data might be lost. For production, strongly consider using a background job queue (e.g., BullMQ, Redis Streams) to process webhook payloads asynchronously. This allows the handler to return200 OK
immediately while ensuring reliable data processing.Send SMS Function: Generate a function to handle sending messages:
Implement the logic in
api/src/functions/sendSms.ts
(or.js
):to
number andtext
from the POST request body.status: 'submitted'
.vonage.messages.send()
. Using specific SDK types likeMessagesSendRequest
is recommended overas any
.vonageMessageId
returned by the API.Inbound SMS Webhook: Generate a function to handle incoming messages:
Implement the logic in
api/src/functions/webhooks/inbound.ts
:/api/webhooks/inbound
(matching Vonage config).POST
request.Content-Type
if issues arise).message_uuid
,from
,to
,text
,timestamp
).SmsMessage
table withdirection: 'inbound'
andstatus: 'received'
.200 OK
status code promptly. Offload heavy processing (like DB writes) to queues in production.Status Update Webhook: Generate a function to handle delivery status updates:
Implement the logic in
api/src/functions/webhooks/status.ts
:/api/webhooks/status
.POST
.message_uuid
,status
,timestamp
, and potentialerror
details. Verify error structure in Vonage docs.db.smsMessage.updateMany
with thevonageMessageId
to find the original outbound message.status
,statusTimestamp
, anderrorCode
fields.200 OK
promptly. Offload heavy processing to queues in production.4. Integrating with Vonage (Recap & Details)
sendSms
function usingnew Vonage({ applicationId, privateKey })
. Ensure theprivateKey
source (path or env var content) is correctly configured and accessible at runtime. The path resolution (path.resolve(process.cwd(), ...)
insendSms.ts
) needs careful testing, especially after building the API (yarn rw build api
), asprocess.cwd()
will point to the build output directory (e.g.,api/dist
). Consider using environment variable content or copying the key during build for robustness..env
file for local development. Never commit these files. Use environment variable management provided by your deployment platform for production. The Messages API specifically uses Application ID and Private Key for authentication in this setup./api/webhooks/inbound
,/api/webhooks/status
) correctly set using your public URL (ngrok
for dev, actual domain for prod). Method must bePOST
.VONAGE_APPLICATION_ID
: Your Vonage application's unique ID.VONAGE_PRIVATE_KEY_PATH
: Relative path from the project root to your savedprivate.key
file (if using path method).VONAGE_NUMBER
: Your purchased Vonage virtual number in E.164 format (e.g., 14155551212).VONAGE_PRIVATE_KEY_CONTENT
: (Alternative/Recommended for Deployment) The actual content of the private key file.5. Error Handling, Logging, and Retry Mechanisms
try...catch
blocks are used in all functions interacting with the Vonage API or database.error.response.data
) are logged for better debugging.sendSms
.logger
(api/src/lib/logger.js
) is used.logger.info
,logger.warn
,logger.error
, andlogger.debug({ custom: payload })
for detailed payloads. Configure log levels and destinations (e.g., structured logging) for production.200 OK
response within its timeout period. This is why our webhook handlers must return200 OK
quickly.200 OK
, Vonage will not retry. This highlights the need for asynchronous processing (queues) for critical webhook tasks in production to handle transient internal failures reliably./api/sendSms
endpoint, implement retry logic there (e.g., using exponential backoff) for transient network errors or 5xx responses from your API.6. Database Schema (Recap)
The Prisma schema (
api/db/schema.prisma
) defines theSmsMessage
model with fields likevonageMessageId
(indexed),direction
,fromNumber
,toNumber
,body
,status
,errorCode
, and timestamps. The migration (yarn rw prisma migrate dev
) applies this schema. Consider adding indexes to other fields based on query patterns.7. Security Features
.env
locally and never committed to source control. Use secure environment variable management or secret stores in your deployment environment (see Section 12).sendSms
(checking forto
andtext
). Implement more robust validation (e.g., phone number format, length checks) for production using libraries likezod
."Signed Webhooks"
or"Webhook Security"
. You'll typically need to configure a secret in the Vonage dashboard and use a library (likejsonwebtoken
for JWT) in your webhook handlers to validate theAuthorization
header or signature provided in the request. Do not run unsigned webhooks in production./api/sendSms
endpoint to prevent abuse and manage costs. RedwoodJS doesn't have built-in rate limiting; use platform features (like API Gateway limits) or middleware patterns with external stores (like Redis).8. Handling Special Cases
+14155551212
). Ensure input numbers are normalized before sending to the API. Libraries likelibphonenumber-js
can help.status
might remain 'accepted' even if delivered. Check Vonage's country-specific feature documentation. Do not rely solely on 'delivered' status for critical logic if operating in regions with poor DLR support.sendSms
function finishes updating the database with thevonageMessageId
. Thestatus
webhook handler includes a warning log for this. More robust solutions might involve retries within the status handler or temporary caching if this becomes a frequent issue. Usingclient_ref
in the send request might also help correlation.9. Performance Optimizations
@unique
directive onvonageMessageId
creates an essential index. Add indexes to other frequently queried fields (status
,createdAt
,fromNumber
,toNumber
) inschema.prisma
based on your application's needs.inbound.ts
,status.ts
) to a background job queue (e.g., using Redis/BullMQ, platform-specific queues like AWS SQS) to ensure the required fast200 OK
response to Vonage and improve reliability.10. Monitoring, Observability, and Analytics
/api/health
) that verifies API and database connectivity, usable by monitoring systems.11. Troubleshooting and Caveats
ngrok
Issues:ngrok
is running and forwardinghttp
traffic to the correct Redwood API port (default:8911
).https
URL fromngrok
is correctly configured in the Vonage Application settings for both Inbound and Status webhooks.ngrok
web interface (usuallyhttp://localhost:4040
) to inspect incoming requests and responses.VONAGE_PRIVATE_KEY_PATH
, double-check the path relative to the built API directory (api/dist
) where the function executes, not the source directory.VONAGE_PRIVATE_KEY_CONTENT
environment variable for deployment to avoid path issues. Ensure the variable contains the exact key content, including newlines.ngrok
logs/interface for incoming requests. If none arrive, the issue might be with Vonage configuration orngrok
setup.DATABASE_URL
in.env
is correct.yarn rw prisma migrate dev
) were applied successfully.405 Method Not Allowed
: Ensure Vonage is configured to sendPOST
requests to your webhooks. Check your function code isn't incorrectly rejectingPOST
.vonageMessageId
on Update: This can happen due to timing (status webhook arrives beforesendSms
DB update completes) or if the status update is for an inbound message. The warning log helps identify this. Consider usingclient_ref
or more robust correlation logic if needed.VONAGE_APPLICATION_ID
and the private key (path or content) are correct and accessible. Ensure you are using Application ID/Private Key auth for the Messages API as intended.