Send SMS with Twilio in RedwoodJS: Step-by-Step API Integration Tutorial
Build a RedwoodJS API function to send SMS messages with Twilio's Programmable Messaging API. Complete tutorial covering Twilio setup, Node.js integration, error handling, testing, and deployment.
Build a RedwoodJS API function to send SMS messages with Twilio's Programmable Messaging API in this comprehensive tutorial. Learn how to integrate Twilio SMS into your RedwoodJS application using Node.js, covering project setup, authentication, error handling, testing, and production deployment.
This tutorial shows you how to create a production-ready /api/sendSms endpoint in under 30 minutes. Send notifications, alerts, two-factor authentication codes, and transactional messages through Twilio's reliable SMS infrastructure. Perfect for developers building RedwoodJS applications that need SMS capabilities.
Project Overview and Goals
Goal: Build a RedwoodJS API function that sends SMS messages via the Twilio API.
Problem Solved: Enables programmatic SMS communication directly from your RedwoodJS backend, facilitating features like user notifications, appointment reminders, or two-factor authentication prompts.
Technologies:
RedwoodJS: A full-stack JavaScript/TypeScript framework for building modern web applications. We leverage its serverless API functions for backend logic.
Node.js: The runtime environment for RedwoodJS's backend.
Twilio Programmable Messaging: The third-party SMS service that sends messages via its REST API.
(Optional) A frontend component or external service triggers the RedwoodJS API function.
The RedwoodJS API function (sendSms in this guide) receives the request containing recipient number and message body.
The function uses the Twilio Node.js SDK, configured with your credentials, to make an API call to Twilio.
Twilio processes the request and delivers the SMS to the recipient's phone.
Prerequisites:
Node.js: Install version 20.x or later. RedwoodJS requires Node.js >=20 as of version 8.x releases. (Download Node.js)
Yarn: Install version 1.x (Classic) with npm install --global yarn.
Twilio Account: Create a free or paid Twilio account. (Sign up for Twilio)
Twilio Phone Number: Purchase an SMS-enabled phone number within your Twilio account.
Verified Phone Number (for Trial Accounts): If using a Twilio trial account, verify the recipient phone number(s) in your Twilio Console.
Final Outcome: A RedwoodJS project with a functional /api/sendSms endpoint that securely sends SMS messages using Twilio.
1. Setting Up Your RedwoodJS Project with Twilio
Start by creating a new RedwoodJS project and installing the necessary dependencies.
Create a New RedwoodJS App:
Open your terminal and run:
Bash
yarn create redwood-app ./redwood-twilio-sms
Follow the prompts (you can choose JavaScript or TypeScript – this guide uses JavaScript examples, but the concepts are identical).
Navigate to Project Directory:
Bash
cd redwood-twilio-sms
Install Twilio Node.js Helper Library:
RedwoodJS uses Yarn workspaces. Install the Twilio library specifically in the api workspace.
Bash
yarn workspace api add twilio
This command adds the twilio package to the api/package.json file and installs it.
Note: This guide is compatible with Twilio Node.js SDK v5.x (current as of 2024), which includes TypeScript definitions and improved performance. The SDK uses Twilio's Programmable Messaging API base URL https://api.twilio.com/2010-04-01 for message resources. For SDK documentation, see Twilio Node Helper Library.
Configure Environment Variables:
Never hardcode sensitive credentials like API keys. RedwoodJS uses .env files for environment variables.
Create a .env file in the root of your project (redwood-twilio-sms/.env).
TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN: Find these on your main Twilio Console dashboard (https://www.twilio.com/console).
TWILIO_PHONE_NUMBER: This is the SMS-enabled Twilio phone number you purchased. Find it under "Phone Numbers" > "Manage" > "Active numbers" in your Twilio Console. Ensure it's in E.164 format (e.g., +15551234567).
Security: Ensure your .env file is listed in your .gitignore file (RedwoodJS includes this by default) to prevent accidentally committing secrets.
2. Creating the RedwoodJS API Function to Send SMS
Create a RedwoodJS API function to handle the SMS sending logic.
Generate the API Function:
Use the RedwoodJS CLI to scaffold a new function:
Bash
yarn rw g function sendSms
This creates the file api/src/functions/sendSms.js (or .ts) with some boilerplate code.
Implement the Sending Logic:
Replace the contents of api/src/functions/sendSms.js with the following code:
JavaScript
// api/src/functions/sendSms.jsimport { logger } from'src/lib/logger'import twilio from'twilio'// Import the Twilio library// Initialize Twilio Client// Ensure TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN are set in your .env fileconst accountSid = process.env.TWILIO_ACCOUNT_SIDconst authToken = process.env.TWILIO_AUTH_TOKENconst twilioPhoneNumber = process.env.TWILIO_PHONE_NUMBER// Validate environment variablesif (!accountSid || !authToken || !twilioPhoneNumber) {
logger.error(
'Missing Twilio environment variables. Set TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, and TWILIO_PHONE_NUMBER in your .env file.'
)
// Prevent the function from running if credentials are missing
}
// Only initialize client if credentials are availableconst client =
accountSid && authToken ? twilio(accountSid, authToken) : null/**
* Sends an SMS message using Twilio.
* Expects a POST request with a JSON body containing:
* {
* "to": "+15559876543", // Recipient phone number in E.164 format
* "body": "Your message here"
* }
*/exportconsthandler = async (event, context) => {
// 1. Check HTTP Methodif (event.httpMethod !== 'POST') {
logger.warn('Received non-POST request to sendSms function')
return {
statusCode: 405, // Method Not Allowedheaders: { 'Content-Type': 'application/json' },
body: JSON.stringify({ error: 'Method Not Allowed' }),
}
}
// 2. Check if Twilio client is initializedif (!client) {
logger.error('Twilio client failed to initialize. Check your credentials.')
return {
statusCode: 500,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ error: 'SMS service configuration error.' }),
}
}
// 3. Parse Request Body and Validate Inputlet requestBody
try {
requestBody = JSON.parse(event.body)
} catch (error) {
logger.error('Failed to parse request body:', error)
return {
statusCode: 400, // Bad Requestheaders: { 'Content-Type': 'application/json' },
body: JSON.stringify({ error: 'Invalid JSON body' }),
}
}
const { to, body } = requestBody
// Basic validationif (!to || typeof to !== 'string' || !to.startsWith('+')) {
logger.warn('Invalid "to" phone number provided:', to)
return {
statusCode: 400,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
error: 'Your phone number is invalid. Use E.164 format (+15551234567).',
}),
}
}
if (!body || typeof body !== 'string' || body.trim().length === 0) {
logger.warn('Invalid or empty "body" provided')
return {
statusCode: 400,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ error: 'Message body cannot be empty.' }),
}
}
// 4. Send the SMS using Twiliotry {
logger.info(`Attempting to send SMS to ${to}`)
const message = await client.messages.create({
body: body,
from: twilioPhoneNumber, // Your Twilio number from .envto: to, // Recipient number from request body
})
logger.info(`SMS sent successfully. Message SID: ${message.sid}`)
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
success: true,
messageSid: message.sid,
status: message.status, // e.g., 'queued', 'sending', 'sent'
}),
}
} catch (error) {
// Log Twilio-specific errors or generic errors
logger.error('Failed to send SMS via Twilio:', error)
// Determine appropriate status code based on error if possibleconst statusCode = error.status || 500// Use Twilio's status or default to 500return {
statusCode: statusCode,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
success: false,
error: 'Failed to send SMS.',
twilioErrorCode: error.code, // Include Twilio's error code if availabletwilioMoreInfo: error.moreInfo, // Include link to Twilio docs if available
}),
}
}
}
Code Explanation:
Imports: Import Redwood's logger for logging and the twilio library.
Twilio Client Initialization: Retrieve credentials from process.env and initialize the Twilio client. Check if the environment variables exist before initializing.
Handler Function: This is the main entry point for the API function.
HTTP Method Check: Ensures the function only responds to POST requests.
Client Initialization Check: Verifies the Twilio client creation succeeded.
Request Parsing & Validation: Parse the JSON body from the incoming event and validate the presence and basic format of the to phone number (must start with +) and the body.
SMS Sending: Use client.messages.create with the body, from (your Twilio number), and to parameters. This is an asynchronous operation, so we use await.
Success Response: If the API call succeeds, return a 200 OK status with the message SID and status.
Error Handling: A try...catch block handles potential errors during the API call (e.g., invalid credentials, network issues, Twilio errors). It logs the error and returns an appropriate error status code (using Twilio's status if available, otherwise 500) and a JSON error message, potentially including Twilio-specific error codes (error.code) and documentation links (error.moreInfo) for easier debugging.
3. Testing Your RedwoodJS SMS API Endpoint
The RedwoodJS function is our API layer for this specific task.
Testing with curl:
Make sure your RedwoodJS development server runs (yarn rw dev). Open a new terminal window and run:
Bash
curl -X POST http://localhost:8910/api/sendSms \
-H "Content-Type: application/json" \
-d '{
"to": "+1YOUR_VERIFIED_PHONE_NUMBER",
"body": "Hello from RedwoodJS and Twilio!"
}'
Replace +1YOUR_VERIFIED_PHONE_NUMBER with your actual mobile number (which must be verified in Twilio if using a trial account).
You should see a success JSON response, and shortly after, receive the SMS on your phone. Check the terminal running yarn rw dev for logs.
4. Configuring Twilio Credentials and Phone Numbers
Steps 1 (installation, environment variables) and 2 (client initialization, API call) covered this integration.
Key Configuration Points:
TWILIO_ACCOUNT_SID: Your unique account identifier. Find this on the Twilio Console dashboard. Purpose: Identifies your account to Twilio. Format: ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.
TWILIO_AUTH_TOKEN: Your secret API key. Find this on the Twilio Console dashboard. Treat this like a password. Purpose: Authenticates your API requests. Format: Alphanumeric string.
TWILIO_PHONE_NUMBER: The Twilio number messages send from. Find this in the Phone Numbers section of the Twilio Console. Purpose: Sets the from number for outgoing SMS. Format: E.164 (+1...).
Account SID & Auth Token: Visible directly on the main dashboard ("Account Info" section).
Phone Number: Navigate to "Develop" (left sidebar) -> "Phone Numbers" -> "Manage" -> "Active numbers". Copy the desired number in E.164 format.
(Trial Only) Verified Numbers: Navigate to "Develop" (left sidebar) -> "Phone Numbers" -> "Manage" -> "Verified caller IDs". Add any phone numbers you want to send messages to during your trial.
5. Implementing Error Handling and Logging for SMS
Error Handling: Implement error handling within the try...catch block in api/src/functions/sendSms.js. It catches errors from the client.messages.create call, logs them using Redwood's logger, and returns structured JSON error responses with appropriate HTTP status codes. It includes Twilio-specific error details when available (error.code, error.moreInfo).
logger.info: Logs successful operations and key steps.
logger.error: Logs exceptions and failure conditions.
logger.warn: Logs non-critical issues like invalid input.
Logs appear in the console where yarn rw dev runs. In production deployments, logs typically route to your hosting provider's logging service (e.g., Vercel Logs, Netlify Functions Logs).
Retry Mechanisms:
This basic implementation does not include automatic retries. Network issues or temporary Twilio outages might cause failures.
For Production: Implement retries with exponential backoff for transient errors (like network timeouts or 5xx errors from Twilio). Consider using a background job queue (e.g., using packages like bullmq and Redis) to handle sending and retries more robustly, preventing HTTP request timeouts for long-running processes.
Testing Error Scenarios:
Invalid Credentials: Temporarily change TWILIO_AUTH_TOKEN in .env to an incorrect value, restart yarn rw dev, and send a curl request. Expect a 401 or similar error.
Invalid to Number: Send a curl request with an improperly formatted to number (e.g., 5551234, +1). Expect a 400 error from our validation. Send a validly formatted but non-existent number – expect an error from Twilio (potentially logged, response might vary).
Empty Body: Send a curl request with "body": "". Expect a 400 error from our validation.
Trial Account Unverified Number: If using a trial account, send to a number not listed in your Verified Caller IDs. Expect a Twilio error (likely code 21608).
6. Database Schema and Data Layer
Not Applicable: This specific function does not require database interaction. It receives data via the API request and interacts directly with the external Twilio service. If you need to store message history, associate messages with users, or manage opt-outs, define Prisma schemas in api/db/schema.prisma, generate migrations (yarn rw prisma migrate dev), and use Redwood's services (yarn rw g service ...) to interact with the database before or after calling the Twilio API.
7. Security Features
Input Validation: The api/src/functions/sendSms.js function includes basic validation for the to and body parameters to prevent malformed requests or trivial injection attempts (though Twilio's API handles its own sanitization). Use more robust validation (e.g., using yup or zod) for complex inputs.
Secret Management: Store API credentials (TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN) securely in environment variables (.env) and never check them into version control (.gitignore). In production, configure these via the hosting provider's environment variable settings.
Authentication/Authorization:
By default, RedwoodJS API functions are publicly accessible. Anyone who knows the URL (/api/sendSms) can potentially call it.
For Production: Secure this endpoint. Use RedwoodJS's built-in authentication (yarn rw setup auth ...) and add the @requireAuth directive or check isAuthenticated / currentUser within the handler function to ensure only authorized users or services can trigger SMS sending. The function as presented without authentication is insecure and unsuitable for production environments. See RedwoodJS Auth Docs.
Rate Limiting:
Twilio: Twilio enforces its own rate limits based on your account, number type, and destination country. Exceeding these limits results in 429 Too Many Requests errors (Twilio error code 20429).
Application-Level: For high-traffic applications or to prevent abuse, implement rate limiting on the function itself (e.g., using an in-memory store for simple cases or Redis/database for distributed environments).
Common Vulnerabilities: Basic input validation helps, but securing the endpoint via authentication is the primary defense against unauthorized access and potential abuse (e.g., exhausting your Twilio balance).
8. SMS Best Practices: E.164 Format and Character Limits
E.164 Phone Number Formatting: Ensure the to number is always in E.164 format (+ followed by country code and number, no spaces or dashes). The validation enforces the leading +, but more comprehensive E.164 validation libraries exist if needed.
Internationalization (i18n): The message body sends as-is. If you need to send messages in different languages, your application logic (potentially in a RedwoodJS service calling this function) determines the correct language and provides the translated body text.
Character Limits & Encoding: SMS messages have character limits that depend on encoding. Single-segment messages: 160 characters for GSM-7 encoding (standard Latin alphabet), or 70 characters for UCS-2 encoding (required for emojis, Chinese, Arabic, etc.). Multi-segment messages: When your message exceeds these limits, Twilio automatically splits it into segments with a User Data Header, reducing capacity to 153 characters per segment (GSM-7) or 67 characters per segment (UCS-2). You're billed per segment. Twilio automatically selects the most compact encoding; if any non-GSM-7 characters are present, the entire message uses UCS-2. Source: Twilio SMS Character Limits.
MMS: To send MMS (images, etc.), add a mediaUrl parameter to the client.messages.create call, pointing to a publicly accessible URL of the media file. MMS is typically only supported in the US and Canada.
9. Optimizing SMS Performance and Handling High Volume
Twilio Client Initialization: The Twilio client initializes once when the function cold starts and gets reused for subsequent invocations within the same container instance. This is efficient.
Asynchronous Operations: The client.messages.create call is asynchronous (await), preventing the Node.js event loop from blocking during the API request.
High Volume:
Rate Limits: Twilio imposes rate limits. Distribute sending across multiple Twilio numbers or use a Messaging Service if needed. Learn more about Twilio rate limits and message queues.
Queuing: For sending bulk messages or ensuring resilience, use a background job queue (like BullMQ with Redis). The API function adds a job to the queue, and a separate worker process handles sending and retries. This prevents API gateway timeouts (typically 10-30 seconds) if Twilio takes longer to respond or if retries are needed.
Twilio Messaging Services: For scaling, opt-in/opt-out management, and intelligent routing, consider using a Twilio Messaging Service instead of a single from number. Pass the messagingServiceSid instead of from to client.messages.create.
10. Monitoring, Observability, and Analytics
RedwoodJS Logging: Logs provide basic observability into function executions and errors. Ensure your hosting provider (Vercel, Netlify, Render, etc.) captures logs.
Twilio Console Logs: The Twilio Console provides detailed logs for every message attempt:
Navigate to "Monitor" (left sidebar) -> "Logs" -> "Messaging".
Filter by date, number, status, etc.
See delivery status (queued, sent, delivered, undelivered, failed), error codes, message segments, and cost. This is invaluable for debugging delivery issues.
Error Tracking Services: Integrate services like Sentry or BugSnag with your RedwoodJS API side to capture, aggregate, and alert on errors proactively. RedwoodJS has recipes for integrating some of these.
Health Checks: While this specific function doesn't maintain state, you could create a simple health check function (/api/health) that returns 200 OK to ensure the API side is generally responsive.
Metrics/Dashboards: For business analytics (e.g., number of notifications sent), log events to a dedicated analytics platform or database from your application logic before calling the sendSms function.
11. Troubleshooting Common Twilio SMS Issues
Check Redwood API Server Logs First: Before diving into Twilio logs, always check the console output where yarn rw dev runs. This often shows immediate errors from your function code (like parsing errors, invalid input, or exceptions before the Twilio call is even made).
Invalid Credentials (Twilio Error 20003): Double-check TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN in your .env file and ensure they match the Twilio Console. Restart yarn rw dev after changes.
Trial Account Restrictions (Twilio Error 21608 – Unverified Number): Trial accounts can only send SMS to phone numbers verified in the Twilio Console ("Verified Caller IDs"). Upgrade your account or verify the recipient number.
Trial Account Restrictions (SMS Prefix): Messages sent from trial accounts include a "Sent from a Twilio trial account –" prefix. Upgrade to remove this.
Invalid From Number (Twilio Error 21212): Ensure TWILIO_PHONE_NUMBER in .env is a valid Twilio number associated with your account and enabled for SMS.
Invalid To Number (Twilio Error 21211): Ensure the to number provided in the request body is a valid phone number in E.164 format. Check Twilio logs for details if the format is correct but delivery fails.
Rate Limits Exceeded (Twilio Error 20429 / HTTP 429): Slow down your sending rate or use a Twilio Messaging Service for better throughput management. Implement application-side queuing/throttling.
Function Timeout: If the Twilio API is slow to respond, your serverless function might time out (typically 10-30 seconds depending on the provider). Use background queues for long-running/high-volume tasks.
.env Not Loaded: Ensure the .env file is in the project root and yarn rw dev was restarted after creating/modifying it. Check that the environment variables are correctly named.
Publicly Accessible Endpoint: Without adding authentication (@requireAuth), this endpoint is open. Secure it before deploying to production.
JSON Parsing Errors: Ensure the curl command or frontend request sends a valid JSON body with the correct Content-Type: application/json header.
12. Deploying Your RedwoodJS SMS Application
Deployment Targets: RedwoodJS supports various deployment targets like Vercel, Netlify, Render, AWS Serverless, etc. Follow the specific deployment guide for your chosen provider in the RedwoodJS Deployment Docs.
Environment Variables: Configure the TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, and TWILIO_PHONE_NUMBER environment variables within your hosting provider's settings dashboard (e.g., Vercel Environment Variables, Netlify Build environment variables). Do not commit your .env file.
CI/CD: Set up a CI/CD pipeline (e.g., using GitHub Actions, Vercel/Netlify Git integration) to automatically build, test, and deploy your application upon code changes. Your pipeline should install dependencies (yarn install), potentially run tests (yarn rw test), build the app (yarn rw build), and deploy (vercel deploy --prod, netlify deploy --prod, etc.).
Rollback: Most hosting providers offer mechanisms to roll back to previous deployments if a new deployment introduces issues. Familiarize yourself with your provider's rollback procedure.
13. Verification and Testing
Manual Verification:
Run yarn rw dev.
Use curl (as shown in Section 3) or a tool like Postman/Insomnia to send a POST request to http://localhost:8910/api/sendSms with a valid JSON body.
Check the curl/tool response for a 200 OK status and JSON body with "success": true.
Check the recipient phone for the incoming SMS message.
Check the yarn rw dev console output for logs.
Check the Twilio Console logs ("Monitor" -> "Logs" -> "Messaging") for the message record and its status.
Automated Testing (Jest):
RedwoodJS uses Jest for testing. Write a basic test for our function.
Create/Edit the test file api/src/functions/sendSms.test.js:
JavaScript
// api/src/functions/sendSms.test.jsimport { mockHttpEvent } from'@redwoodjs/testing/api'import { handler } from'./sendSms'// Import the handlerimport twilio from'twilio'// Import the original library// Mock the Twilio library
jest.mock('twilio', () => {
// Mock the messages.create functionconst create = jest.fn().mockResolvedValue({
sid: 'SMmockedsid1234567890abcdef',
status: 'queued',
})
// Mock the client constructorreturn jest.fn().mockImplementation(() => ({
messages: {
create,
},
}))
})
// Mock the logger to prevent console noise during tests
jest.mock('src/lib/logger', () => ({
logger: {
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
},
}))
describe('sendSms function', () => {
// Define mock environment variables before testsconstOLD_ENV = process.envbeforeAll(() => {
process.env = {
...OLD_ENV,
TWILIO_ACCOUNT_SID: 'ACtestmockaccount',
TWILIO_AUTH_TOKEN: 'testmocktoken',
TWILIO_PHONE_NUMBER: '+15550001111',
}
// Note: These mocked environment variables apply *only* during the execution// of this test suite (`yarn rw test api`). You still need the actual `.env`// file for local development (`yarn rw dev`) and correctly configured// environment variables in your production deployment.
})
afterAll(() => {
process.env = OLD_ENV// Restore old environment
})
beforeEach(() => {
// Reset mocks before each testtwilio().messages.create.mockClear()
twilio.mockClear() // Clear the constructor mock if needed
})
it('should return 405 for non-POST requests', async () => {
const response = awaithandler(
mockHttpEvent({ httpMethod: 'GET' }),
null
)
expect(response.statusCode).toBe(405)
expect(JSON.parse(response.body).error).toBe('Method Not Allowed')
})
it('should return 400 if "to" number is missing or invalid', async () => {
const event = mockHttpEvent({
httpMethod: 'POST',
body: JSON.stringify({ body: 'Test message' }), // Missing 'to'
})
const response = awaithandler(event, null)
expect(response.statusCode).toBe(400)
expect(JSON.parse(response.body).error).toContain(
'Your phone number is invalid'
)
})
it('should return 400 if "body" is missing or empty', async () => {
const event = mockHttpEvent({
httpMethod: 'POST',
body: JSON.stringify({ to: '+15551234567', body: ' ' }), // Empty 'body'
})
const response = awaithandler(event, null)
expect(response.statusCode).toBe(400)
expect(JSON.parse(response.body).error).toBe(
'Message body cannot be empty.'
)
})
it('should call Twilio client and return 200 on success', async () => {
const recipient = '+15559876543'const messageBody = 'Hello from test!'const mockTwilioNumber = process.env.TWILIO_PHONE_NUMBERconst event = mockHttpEvent({
httpMethod: 'POST',
body: JSON.stringify({ to: recipient, body: messageBody }),
})
const response = awaithandler(event, null)
// Check responseexpect(response.statusCode).toBe(200)
const responseBody = JSON.parse(response.body)
expect(responseBody.success).toBe(true)
expect(responseBody.messageSid).toBe('SMmockedsid1234567890abcdef')
expect(responseBody.status).toBe('queued')
// Check if twilio.messages.create was called correctlyconst mockCreate = twilio().messages.createexpect(mockCreate).toHaveBeenCalledTimes(1)
expect(mockCreate).toHaveBeenCalledWith({
to: recipient,
body: messageBody,
from: mockTwilioNumber,
})
})
it('should return error status code if Twilio API call fails', async () => {
// Configure the mock to reject this timeconst errorMessage = 'Twilio API error'const errorCode = 20003// Example: Authentication errorconst mockError = newError(errorMessage)
mockError.status = 401// Unauthorized
mockError.code = errorCode
mockError.moreInfo = `https://www.twilio.com/docs/errors/${errorCode}`twilio().messages.create.mockRejectedValue(mockError)
const event = mockHttpEvent({
httpMethod: 'POST',
body: JSON.stringify({
to: '+15559876543',
body: 'This will fail',
}),
})
const response = awaithandler(event, null)
expect(response.statusCode).toBe(401) // Should use error.statusconst responseBody = JSON.parse(response.body)
expect(responseBody.success).toBe(false)
expect(responseBody.error).toBe('Failed to send SMS.')
expect(responseBody.twilioErrorCode).toBe(errorCode)
expect(responseBody.twilioMoreInfo).toContain(errorCode.toString())
})
})
Explanation:
Use jest.mock('twilio', ...) to replace the actual Twilio library with a mock.
The mock simulates the client.messages.create function, allowing us to control its return value (mockResolvedValue) or make it throw an error (mockRejectedValue).
Mock src/lib/logger to avoid log output during tests.
Mock process.env variables needed by the function within the test suite using beforeAll and restore them with afterAll.
Each it(...) block tests a specific scenario: wrong HTTP method, invalid input, successful call, and failed API call.
Use mockHttpEvent from @redwoodjs/testing/api to simulate the event object passed to the handler.
Assert the statusCode and body of the response.
For the success case, also assert that the mocked twilio().messages.create function was called with the correct arguments.
For the failure case, assert that the error details (status code, Twilio error code) are correctly propagated to the response.
Frequently Asked Questions
How do I send SMS messages from RedwoodJS?
Send SMS from RedwoodJS by creating an API function that uses the Twilio Node.js SDK. Install the SDK with yarn workspace api add twilio, configure your Twilio credentials in a .env file, and use client.messages.create() to send messages. This guide provides complete implementation details including error handling and testing.
What Node.js version does RedwoodJS require for Twilio integration?
RedwoodJS requires Node.js version 20.x or later as of RedwoodJS 8.x releases. Install Node.js >=20 before starting your Twilio SMS integration project.
How much does it cost to send SMS with Twilio?
Twilio SMS costs vary by destination country. US SMS typically costs $0.0079 per message segment. Messages exceeding 160 characters (GSM-7) or 70 characters (UCS-2) split into multiple segments, with each segment billed separately. Check Twilio's pricing page for current rates in your target countries.
What is E.164 phone number format for Twilio?
E.164 is the international phone number format Twilio requires: +[country code][number] with no spaces, dashes, or parentheses. Example: +15551234567 for a US number. Always validate phone numbers to E.164 format before sending to avoid errors.
How do I handle Twilio error codes in RedwoodJS?
Catch Twilio errors in your try-catch block and access error.code (Twilio's error code), error.status (HTTP status), and error.moreInfo (documentation link). Common errors include 21608 (unverified trial number), 21211 (invalid 'to' number), and 20429 (rate limit exceeded). Log these details for easier debugging.
Can I send SMS from a RedwoodJS trial Twilio account?
Yes, but Twilio trial accounts have restrictions: (1) verify recipient phone numbers in the Twilio Console before sending, (2) messages include a "Sent from a Twilio trial account" prefix, and (3) you receive $15 in trial credit. Upgrade to a paid account to remove these limitations.
How do I secure my RedwoodJS SMS endpoint?
Secure your /api/sendSms endpoint by implementing RedwoodJS authentication. Use yarn rw setup auth to configure auth, then add the @requireAuth directive or check isAuthenticated in your handler function. Without authentication, your endpoint is publicly accessible and vulnerable to abuse.
What are SMS character limits with Twilio?
Single SMS messages support 160 characters (GSM-7 encoding) or 70 characters (UCS-2 encoding for emojis/non-Latin scripts). Multi-segment messages reduce to 153 characters per segment (GSM-7) or 67 characters (UCS-2) due to the User Data Header. Twilio automatically handles segmentation but bills per segment.
How do I test Twilio SMS in RedwoodJS locally?
Test locally by running yarn rw dev and using curl or Postman to POST to http://localhost:8910/api/sendSms with JSON body containing to and body fields. For automated testing, use Jest with mocked Twilio clients as demonstrated in Section 13 of this guide.
Can I send MMS images with Twilio in RedwoodJS?
Yes, add a mediaUrl parameter to client.messages.create() pointing to a publicly accessible image URL. MMS is primarily supported in the US and Canada. Note that MMS costs more than SMS – check Twilio's MMS pricing for your target countries.
Frequently asked questions
You can send SMS messages within a RedwoodJS application by creating a serverless function that integrates with the Twilio Programmable Messaging API. This function will handle the logic for sending messages using your Twilio credentials and the recipient's phone number.
Twilio Programmable Messaging is a cloud communications platform that allows developers to send and receive SMS, MMS, and WhatsApp messages programmatically. It provides a simple REST API and client libraries for various programming languages, including Node.js, which is used in RedwoodJS.
RedwoodJS uses serverless functions for SMS because they provide a scalable and cost-effective way to handle backend logic without managing servers. This is ideal for tasks like sending SMS messages, which are often triggered by user actions or scheduled events.
A message queue like BullMQ is recommended for high-volume SMS sending or when implementing retry mechanisms to handle temporary failures. It helps prevent API gateway timeouts by offloading the sending process to a background worker.
You should store your Twilio Account SID, Auth Token, and Twilio phone number as environment variables in a .env file in the root of your RedwoodJS project. This file is automatically excluded from version control via .gitignore for security.
The API endpoint for sending SMS is /api/sendSms. It accepts POST requests with a JSON body containing the recipient's phone number (to) in E.164 format and the message content (body).
The provided code example includes a try...catch block within the sendSms function's handler. This catches errors during the Twilio API call, logs them using Redwood's logger, and returns a JSON error response with an appropriate HTTP status code and details about the error, if available from Twilio.
The recipient's phone number (to) must be in E.164 format. This is an international standard and includes a plus sign (+) followed by the country code and phone number, without any spaces or dashes (e.g., +15551234567).
Yes, you can test the sendSms function locally by running yarn rw dev to start the development server and then using curl or a tool like Postman to send POST requests to the /api/sendSms endpoint with test data.
Common Twilio errors include 20003 (invalid credentials), 21608 (unverified recipient number for trial accounts), 21212 (invalid "from" number), 21211 (invalid "to" number), and 20429/HTTP 429 (rate limits exceeded). Always consult the Twilio error documentation for detailed explanations and troubleshooting.
Logs are available in your RedwoodJS development console when running yarn rw dev, within your hosting provider’s logging service in production, and in the Twilio Console under "Monitor" -> "Logs" -> "Messaging". Twilio logs provide detailed information on message status and delivery.
Critically, the example sendSms function is insecure without authentication. In production, you must secure it using RedwoodJS's authentication features (yarn rw setup auth) and the @requireAuth directive or by checking isAuthenticated / currentUser in your function handler.
For high-volume SMS, use a message queue (like BullMQ with Redis) to handle sending asynchronously and implement retry mechanisms. Consider using Twilio Messaging Services to improve deliverability and manage opt-outs, and distribute sending across multiple Twilio numbers if necessary.
Build a RedwoodJS API function to send SMS messages with Twilio's Programmable Messaging API in this comprehensive tutorial. Learn how to integrate Twilio SMS into your RedwoodJS application using Node.js, covering project setup, authentication, error handling, testing, and production deployment.
This tutorial shows you how to create a production-ready
/api/sendSmsendpoint in under 30 minutes. Send notifications, alerts, two-factor authentication codes, and transactional messages through Twilio's reliable SMS infrastructure. Perfect for developers building RedwoodJS applications that need SMS capabilities.Project Overview and Goals
Goal: Build a RedwoodJS API function that sends SMS messages via the Twilio API.
Problem Solved: Enables programmatic SMS communication directly from your RedwoodJS backend, facilitating features like user notifications, appointment reminders, or two-factor authentication prompts.
Technologies:
System Architecture:
sendSmsin this guide) receives the request containing recipient number and message body.Prerequisites:
npm install --global yarn.Final Outcome: A RedwoodJS project with a functional
/api/sendSmsendpoint that securely sends SMS messages using Twilio.1. Setting Up Your RedwoodJS Project with Twilio
Start by creating a new RedwoodJS project and installing the necessary dependencies.
Create a New RedwoodJS App: Open your terminal and run:
Follow the prompts (you can choose JavaScript or TypeScript – this guide uses JavaScript examples, but the concepts are identical).
Navigate to Project Directory:
cd redwood-twilio-smsInstall Twilio Node.js Helper Library: RedwoodJS uses Yarn workspaces. Install the Twilio library specifically in the
apiworkspace.This command adds the
twiliopackage to theapi/package.jsonfile and installs it.Note: This guide is compatible with Twilio Node.js SDK v5.x (current as of 2024), which includes TypeScript definitions and improved performance. The SDK uses Twilio's Programmable Messaging API base URL
https://api.twilio.com/2010-04-01for message resources. For SDK documentation, see Twilio Node Helper Library.Configure Environment Variables: Never hardcode sensitive credentials like API keys. RedwoodJS uses
.envfiles for environment variables..envfile in the root of your project (redwood-twilio-sms/.env)..envfile:Obtaining Credentials:
TWILIO_ACCOUNT_SIDandTWILIO_AUTH_TOKEN: Find these on your main Twilio Console dashboard (https://www.twilio.com/console).TWILIO_PHONE_NUMBER: This is the SMS-enabled Twilio phone number you purchased. Find it under "Phone Numbers" > "Manage" > "Active numbers" in your Twilio Console. Ensure it's in E.164 format (e.g.,+15551234567).Security: Ensure your
.envfile is listed in your.gitignorefile (RedwoodJS includes this by default) to prevent accidentally committing secrets.2. Creating the RedwoodJS API Function to Send SMS
Create a RedwoodJS API function to handle the SMS sending logic.
Generate the API Function: Use the RedwoodJS CLI to scaffold a new function:
yarn rw g function sendSmsThis creates the file
api/src/functions/sendSms.js(or.ts) with some boilerplate code.Implement the Sending Logic: Replace the contents of
api/src/functions/sendSms.jswith the following code:// api/src/functions/sendSms.js import { logger } from 'src/lib/logger' import twilio from 'twilio' // Import the Twilio library // Initialize Twilio Client // Ensure TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN are set in your .env file const accountSid = process.env.TWILIO_ACCOUNT_SID const authToken = process.env.TWILIO_AUTH_TOKEN const twilioPhoneNumber = process.env.TWILIO_PHONE_NUMBER // Validate environment variables if (!accountSid || !authToken || !twilioPhoneNumber) { logger.error( 'Missing Twilio environment variables. Set TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, and TWILIO_PHONE_NUMBER in your .env file.' ) // Prevent the function from running if credentials are missing } // Only initialize client if credentials are available const client = accountSid && authToken ? twilio(accountSid, authToken) : null /** * Sends an SMS message using Twilio. * Expects a POST request with a JSON body containing: * { * "to": "+15559876543", // Recipient phone number in E.164 format * "body": "Your message here" * } */ export const handler = async (event, context) => { // 1. Check HTTP Method if (event.httpMethod !== 'POST') { logger.warn('Received non-POST request to sendSms function') return { statusCode: 405, // Method Not Allowed headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ error: 'Method Not Allowed' }), } } // 2. Check if Twilio client is initialized if (!client) { logger.error('Twilio client failed to initialize. Check your credentials.') return { statusCode: 500, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ error: 'SMS service configuration error.' }), } } // 3. Parse Request Body and Validate Input let requestBody try { requestBody = JSON.parse(event.body) } catch (error) { logger.error('Failed to parse request body:', error) return { statusCode: 400, // Bad Request headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ error: 'Invalid JSON body' }), } } const { to, body } = requestBody // Basic validation if (!to || typeof to !== 'string' || !to.startsWith('+')) { logger.warn('Invalid "to" phone number provided:', to) return { statusCode: 400, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ error: 'Your phone number is invalid. Use E.164 format (+15551234567).', }), } } if (!body || typeof body !== 'string' || body.trim().length === 0) { logger.warn('Invalid or empty "body" provided') return { statusCode: 400, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ error: 'Message body cannot be empty.' }), } } // 4. Send the SMS using Twilio try { logger.info(`Attempting to send SMS to ${to}`) const message = await client.messages.create({ body: body, from: twilioPhoneNumber, // Your Twilio number from .env to: to, // Recipient number from request body }) logger.info(`SMS sent successfully. Message SID: ${message.sid}`) return { statusCode: 200, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ success: true, messageSid: message.sid, status: message.status, // e.g., 'queued', 'sending', 'sent' }), } } catch (error) { // Log Twilio-specific errors or generic errors logger.error('Failed to send SMS via Twilio:', error) // Determine appropriate status code based on error if possible const statusCode = error.status || 500 // Use Twilio's status or default to 500 return { statusCode: statusCode, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ success: false, error: 'Failed to send SMS.', twilioErrorCode: error.code, // Include Twilio's error code if available twilioMoreInfo: error.moreInfo, // Include link to Twilio docs if available }), } } }Code Explanation:
loggerfor logging and thetwiliolibrary.process.envand initialize the Twilio client. Check if the environment variables exist before initializing.POSTrequests.eventand validate the presence and basic format of thetophone number (must start with+) and thebody.client.messages.createwith thebody,from(your Twilio number), andtoparameters. This is an asynchronous operation, so we useawait.200 OKstatus with the message SID and status.try...catchblock handles potential errors during the API call (e.g., invalid credentials, network issues, Twilio errors). It logs the error and returns an appropriate error status code (using Twilio's status if available, otherwise 500) and a JSON error message, potentially including Twilio-specific error codes (error.code) and documentation links (error.moreInfo) for easier debugging.3. Testing Your RedwoodJS SMS API Endpoint
The RedwoodJS function is our API layer for this specific task.
Endpoint:
/api/sendSmsMethod:
POSTRequest Body (JSON):
{ "to": "+1recipient_phone_number", "body": "Your message content here." }Success Response (JSON – 200 OK):
{ "success": true, "messageSid": "SMxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "status": "queued" }Error Response (JSON – 4xx/5xx):
{ "success": false, "error": "Specific error message.", "twilioErrorCode": 60001, "twilioMoreInfo": "https://www.twilio.com/docs/errors/60001" }Testing with
curl: Make sure your RedwoodJS development server runs (yarn rw dev). Open a new terminal window and run:curl -X POST http://localhost:8910/api/sendSms \ -H "Content-Type: application/json" \ -d '{ "to": "+1YOUR_VERIFIED_PHONE_NUMBER", "body": "Hello from RedwoodJS and Twilio!" }'+1YOUR_VERIFIED_PHONE_NUMBERwith your actual mobile number (which must be verified in Twilio if using a trial account).yarn rw devfor logs.4. Configuring Twilio Credentials and Phone Numbers
Steps 1 (installation, environment variables) and 2 (client initialization, API call) covered this integration.
Key Configuration Points:
TWILIO_ACCOUNT_SID: Your unique account identifier. Find this on the Twilio Console dashboard. Purpose: Identifies your account to Twilio. Format:ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.TWILIO_AUTH_TOKEN: Your secret API key. Find this on the Twilio Console dashboard. Treat this like a password. Purpose: Authenticates your API requests. Format: Alphanumeric string.TWILIO_PHONE_NUMBER: The Twilio number messages send from. Find this in the Phone Numbers section of the Twilio Console. Purpose: Sets thefromnumber for outgoing SMS. Format: E.164 (+1...).Dashboard Navigation:
5. Implementing Error Handling and Logging for SMS
try...catchblock inapi/src/functions/sendSms.js. It catches errors from theclient.messages.createcall, logs them using Redwood's logger, and returns structured JSON error responses with appropriate HTTP status codes. It includes Twilio-specific error details when available (error.code,error.moreInfo).logger(api/src/lib/logger.js) provides logging capabilities.logger.info: Logs successful operations and key steps.logger.error: Logs exceptions and failure conditions.logger.warn: Logs non-critical issues like invalid input.yarn rw devruns. In production deployments, logs typically route to your hosting provider's logging service (e.g., Vercel Logs, Netlify Functions Logs).bullmqand Redis) to handle sending and retries more robustly, preventing HTTP request timeouts for long-running processes.Testing Error Scenarios:
TWILIO_AUTH_TOKENin.envto an incorrect value, restartyarn rw dev, and send acurlrequest. Expect a 401 or similar error.toNumber: Send acurlrequest with an improperly formattedtonumber (e.g.,5551234,+1). Expect a 400 error from our validation. Send a validly formatted but non-existent number – expect an error from Twilio (potentially logged, response might vary).curlrequest with"body": "". Expect a 400 error from our validation.6. Database Schema and Data Layer
Not Applicable: This specific function does not require database interaction. It receives data via the API request and interacts directly with the external Twilio service. If you need to store message history, associate messages with users, or manage opt-outs, define Prisma schemas in
api/db/schema.prisma, generate migrations (yarn rw prisma migrate dev), and use Redwood's services (yarn rw g service ...) to interact with the database before or after calling the Twilio API.7. Security Features
api/src/functions/sendSms.jsfunction includes basic validation for thetoandbodyparameters to prevent malformed requests or trivial injection attempts (though Twilio's API handles its own sanitization). Use more robust validation (e.g., usingyuporzod) for complex inputs.TWILIO_ACCOUNT_SID,TWILIO_AUTH_TOKEN) securely in environment variables (.env) and never check them into version control (.gitignore). In production, configure these via the hosting provider's environment variable settings./api/sendSms) can potentially call it.yarn rw setup auth ...) and add the@requireAuthdirective or checkisAuthenticated/currentUserwithin thehandlerfunction to ensure only authorized users or services can trigger SMS sending. The function as presented without authentication is insecure and unsuitable for production environments. See RedwoodJS Auth Docs.429 Too Many Requestserrors (Twilio error code 20429).8. SMS Best Practices: E.164 Format and Character Limits
tonumber is always in E.164 format (+followed by country code and number, no spaces or dashes). The validation enforces the leading+, but more comprehensive E.164 validation libraries exist if needed.bodysends as-is. If you need to send messages in different languages, your application logic (potentially in a RedwoodJS service calling this function) determines the correct language and provides the translatedbodytext.mediaUrlparameter to theclient.messages.createcall, pointing to a publicly accessible URL of the media file. MMS is typically only supported in the US and Canada.9. Optimizing SMS Performance and Handling High Volume
client.messages.createcall is asynchronous (await), preventing the Node.js event loop from blocking during the API request.fromnumber. Pass themessagingServiceSidinstead offromtoclient.messages.create.10. Monitoring, Observability, and Analytics
queued,sent,delivered,undelivered,failed), error codes, message segments, and cost. This is invaluable for debugging delivery issues./api/health) that returns200 OKto ensure the API side is generally responsive.sendSmsfunction.11. Troubleshooting Common Twilio SMS Issues
yarn rw devruns. This often shows immediate errors from your function code (like parsing errors, invalid input, or exceptions before the Twilio call is even made).TWILIO_ACCOUNT_SIDandTWILIO_AUTH_TOKENin your.envfile and ensure they match the Twilio Console. Restartyarn rw devafter changes.FromNumber (Twilio Error 21212): EnsureTWILIO_PHONE_NUMBERin.envis a valid Twilio number associated with your account and enabled for SMS.ToNumber (Twilio Error 21211): Ensure thetonumber provided in the request body is a valid phone number in E.164 format. Check Twilio logs for details if the format is correct but delivery fails..envNot Loaded: Ensure the.envfile is in the project root andyarn rw devwas restarted after creating/modifying it. Check that the environment variables are correctly named.@requireAuth), this endpoint is open. Secure it before deploying to production.curlcommand or frontend request sends a valid JSON body with the correctContent-Type: application/jsonheader.12. Deploying Your RedwoodJS SMS Application
TWILIO_ACCOUNT_SID,TWILIO_AUTH_TOKEN, andTWILIO_PHONE_NUMBERenvironment variables within your hosting provider's settings dashboard (e.g., Vercel Environment Variables, Netlify Build environment variables). Do not commit your.envfile.yarn install), potentially run tests (yarn rw test), build the app (yarn rw build), and deploy (vercel deploy --prod,netlify deploy --prod, etc.).13. Verification and Testing
Manual Verification:
yarn rw dev.curl(as shown in Section 3) or a tool like Postman/Insomnia to send aPOSTrequest tohttp://localhost:8910/api/sendSmswith a valid JSON body.curl/tool response for a200 OKstatus and JSON body with"success": true.yarn rw devconsole output for logs.Automated Testing (Jest): RedwoodJS uses Jest for testing. Write a basic test for our function.
api/src/functions/sendSms.test.js:// api/src/functions/sendSms.test.js import { mockHttpEvent } from '@redwoodjs/testing/api' import { handler } from './sendSms' // Import the handler import twilio from 'twilio' // Import the original library // Mock the Twilio library jest.mock('twilio', () => { // Mock the messages.create function const create = jest.fn().mockResolvedValue({ sid: 'SMmockedsid1234567890abcdef', status: 'queued', }) // Mock the client constructor return jest.fn().mockImplementation(() => ({ messages: { create, }, })) }) // Mock the logger to prevent console noise during tests jest.mock('src/lib/logger', () => ({ logger: { info: jest.fn(), warn: jest.fn(), error: jest.fn(), }, })) describe('sendSms function', () => { // Define mock environment variables before tests const OLD_ENV = process.env beforeAll(() => { process.env = { ...OLD_ENV, TWILIO_ACCOUNT_SID: 'ACtestmockaccount', TWILIO_AUTH_TOKEN: 'testmocktoken', TWILIO_PHONE_NUMBER: '+15550001111', } // Note: These mocked environment variables apply *only* during the execution // of this test suite (`yarn rw test api`). You still need the actual `.env` // file for local development (`yarn rw dev`) and correctly configured // environment variables in your production deployment. }) afterAll(() => { process.env = OLD_ENV // Restore old environment }) beforeEach(() => { // Reset mocks before each test twilio().messages.create.mockClear() twilio.mockClear() // Clear the constructor mock if needed }) it('should return 405 for non-POST requests', async () => { const response = await handler( mockHttpEvent({ httpMethod: 'GET' }), null ) expect(response.statusCode).toBe(405) expect(JSON.parse(response.body).error).toBe('Method Not Allowed') }) it('should return 400 if "to" number is missing or invalid', async () => { const event = mockHttpEvent({ httpMethod: 'POST', body: JSON.stringify({ body: 'Test message' }), // Missing 'to' }) const response = await handler(event, null) expect(response.statusCode).toBe(400) expect(JSON.parse(response.body).error).toContain( 'Your phone number is invalid' ) }) it('should return 400 if "body" is missing or empty', async () => { const event = mockHttpEvent({ httpMethod: 'POST', body: JSON.stringify({ to: '+15551234567', body: ' ' }), // Empty 'body' }) const response = await handler(event, null) expect(response.statusCode).toBe(400) expect(JSON.parse(response.body).error).toBe( 'Message body cannot be empty.' ) }) it('should call Twilio client and return 200 on success', async () => { const recipient = '+15559876543' const messageBody = 'Hello from test!' const mockTwilioNumber = process.env.TWILIO_PHONE_NUMBER const event = mockHttpEvent({ httpMethod: 'POST', body: JSON.stringify({ to: recipient, body: messageBody }), }) const response = await handler(event, null) // Check response expect(response.statusCode).toBe(200) const responseBody = JSON.parse(response.body) expect(responseBody.success).toBe(true) expect(responseBody.messageSid).toBe('SMmockedsid1234567890abcdef') expect(responseBody.status).toBe('queued') // Check if twilio.messages.create was called correctly const mockCreate = twilio().messages.create expect(mockCreate).toHaveBeenCalledTimes(1) expect(mockCreate).toHaveBeenCalledWith({ to: recipient, body: messageBody, from: mockTwilioNumber, }) }) it('should return error status code if Twilio API call fails', async () => { // Configure the mock to reject this time const errorMessage = 'Twilio API error' const errorCode = 20003 // Example: Authentication error const mockError = new Error(errorMessage) mockError.status = 401 // Unauthorized mockError.code = errorCode mockError.moreInfo = `https://www.twilio.com/docs/errors/${errorCode}` twilio().messages.create.mockRejectedValue(mockError) const event = mockHttpEvent({ httpMethod: 'POST', body: JSON.stringify({ to: '+15559876543', body: 'This will fail', }), }) const response = await handler(event, null) expect(response.statusCode).toBe(401) // Should use error.status const responseBody = JSON.parse(response.body) expect(responseBody.success).toBe(false) expect(responseBody.error).toBe('Failed to send SMS.') expect(responseBody.twilioErrorCode).toBe(errorCode) expect(responseBody.twilioMoreInfo).toContain(errorCode.toString()) }) })jest.mock('twilio', ...)to replace the actual Twilio library with a mock.client.messages.createfunction, allowing us to control its return value (mockResolvedValue) or make it throw an error (mockRejectedValue).src/lib/loggerto avoid log output during tests.process.envvariables needed by the function within the test suite usingbeforeAlland restore them withafterAll.it(...)block tests a specific scenario: wrong HTTP method, invalid input, successful call, and failed API call.mockHttpEventfrom@redwoodjs/testing/apito simulate the event object passed to the handler.statusCodeandbodyof the response.twilio().messages.createfunction was called with the correct arguments.Frequently Asked Questions
How do I send SMS messages from RedwoodJS?
Send SMS from RedwoodJS by creating an API function that uses the Twilio Node.js SDK. Install the SDK with
yarn workspace api add twilio, configure your Twilio credentials in a.envfile, and useclient.messages.create()to send messages. This guide provides complete implementation details including error handling and testing.What Node.js version does RedwoodJS require for Twilio integration?
RedwoodJS requires Node.js version 20.x or later as of RedwoodJS 8.x releases. Install Node.js >=20 before starting your Twilio SMS integration project.
How much does it cost to send SMS with Twilio?
Twilio SMS costs vary by destination country. US SMS typically costs $0.0079 per message segment. Messages exceeding 160 characters (GSM-7) or 70 characters (UCS-2) split into multiple segments, with each segment billed separately. Check Twilio's pricing page for current rates in your target countries.
What is E.164 phone number format for Twilio?
E.164 is the international phone number format Twilio requires:
+[country code][number]with no spaces, dashes, or parentheses. Example:+15551234567for a US number. Always validate phone numbers to E.164 format before sending to avoid errors.How do I handle Twilio error codes in RedwoodJS?
Catch Twilio errors in your try-catch block and access
error.code(Twilio's error code),error.status(HTTP status), anderror.moreInfo(documentation link). Common errors include 21608 (unverified trial number), 21211 (invalid 'to' number), and 20429 (rate limit exceeded). Log these details for easier debugging.Can I send SMS from a RedwoodJS trial Twilio account?
Yes, but Twilio trial accounts have restrictions: (1) verify recipient phone numbers in the Twilio Console before sending, (2) messages include a "Sent from a Twilio trial account" prefix, and (3) you receive $15 in trial credit. Upgrade to a paid account to remove these limitations.
How do I secure my RedwoodJS SMS endpoint?
Secure your
/api/sendSmsendpoint by implementing RedwoodJS authentication. Useyarn rw setup authto configure auth, then add the@requireAuthdirective or checkisAuthenticatedin your handler function. Without authentication, your endpoint is publicly accessible and vulnerable to abuse.What are SMS character limits with Twilio?
Single SMS messages support 160 characters (GSM-7 encoding) or 70 characters (UCS-2 encoding for emojis/non-Latin scripts). Multi-segment messages reduce to 153 characters per segment (GSM-7) or 67 characters (UCS-2) due to the User Data Header. Twilio automatically handles segmentation but bills per segment.
How do I test Twilio SMS in RedwoodJS locally?
Test locally by running
yarn rw devand using curl or Postman to POST tohttp://localhost:8910/api/sendSmswith JSON body containingtoandbodyfields. For automated testing, use Jest with mocked Twilio clients as demonstrated in Section 13 of this guide.Can I send MMS images with Twilio in RedwoodJS?
Yes, add a
mediaUrlparameter toclient.messages.create()pointing to a publicly accessible image URL. MMS is primarily supported in the US and Canada. Note that MMS costs more than SMS – check Twilio's MMS pricing for your target countries.