Frequently Asked Questions
Use MessageBird's webhooks and a RedwoodJS function to capture real-time delivery updates. Set up a webhook endpoint in your Redwood app and configure it in your MessageBird dashboard. The webhook will send an HTTP POST request to your app whenever a message status changes, allowing you to update its status in your database.
Prisma acts as an ORM (Object-Relational Mapper), simplifying database interactions. It allows you to define your database schema (like the Message model), manage migrations, and access data in a type-safe way using Prisma Client. This streamlines database operations within your RedwoodJS application.
Serverless functions are ideal for handling asynchronous events like webhooks. They are cost-effective because you only pay for the compute time used when a webhook is received. RedwoodJS integrates seamlessly with serverless providers, making webhook implementation straightforward.
The `APP_BASE_URL` is critical for webhook functionality and must be updated whenever your application's public URL changes. This includes using tools like ngrok for local development or deploying to a production environment. Ensure this variable accurately reflects the reachable address of your application.
Yes, you can use Alphanumeric Sender IDs, but be mindful of country-specific restrictions. Instead of a phone number, you can use an approved alphanumeric string (e.g., your brand name) as the sender. Consult MessageBird's documentation for supported countries and regulations.
RedwoodJS utilizes a `.env` file at the root of your project. In this file, you'll store sensitive information like your `MESSAGEBIRD_API_KEY`, `MESSAGEBIRD_SIGNING_KEY`, `DATABASE_URL`, and `APP_BASE_URL`. Remember to replace placeholders with your actual credentials and keep this file secure.
The correct path is `/.redwood/functions/messagebirdWebhook`. This corresponds to the Redwood function you create to handle incoming webhook requests from MessageBird. Make sure this path matches exactly in your service function and webhook handler.
The MessageBird Node.js SDK provides the `WebhookSignatureJwt` class for signature verification. You'll initialize it with your `MESSAGEBIRD_SIGNING_KEY` and use it to verify the signature in the `messagebirdWebhook` function. This ensures that incoming webhook requests genuinely originate from MessageBird.
Setting `bodyParser: false` in the `messagebirdWebhook` function's `config` object prevents RedwoodJS from automatically parsing the incoming request body as JSON. This is crucial because MessageBird's signature verification requires the raw, unparsed request body.
Your `updateMessageStatusFromWebhook` service function will receive the `status` from the MessageBird webhook payload. Use this value to update the corresponding message record in your database. Common statuses include 'sent', 'delivered', 'delivery_failed', and 'expired'.
The 'queued' status indicates that the message has been created in your database and is awaiting delivery via MessageBird. It's the initial state before the message is actually sent to the MessageBird API. The status will change to 'sent' once MessageBird accepts the message for delivery.
Implement error handling within the `sendMessage` service function to catch potential issues during the MessageBird API call. Update the message status in the database accordingly (e.g., 'failed_to_send') and log the error for debugging. You might also want to notify users or retry the send operation based on the error type.
The `messageBirdId` is essential for correlating webhook updates with existing messages in your database. If the payload is missing this ID, you cannot process the update. Log a warning and return an appropriate response to MessageBird (e.g., a 400 Bad Request).
The index on the `status` field in your Prisma schema improves the performance of queries that filter by message status. This optimization is beneficial when you need to quickly retrieve messages with specific statuses, such as 'delivered' or 'failed'.
This guide provides a step-by-step walkthrough for building a production-ready system within a RedwoodJS application to send SMS messages via MessageBird and track their delivery status using webhooks. We'll cover everything from project setup and core implementation to security, error handling, and deployment.
By the end of this tutorial, you will have a RedwoodJS application capable of:
This solution enables applications to reliably inform users or internal systems about the delivery success or failure of critical SMS communications.
Project Overview and Goals
Problem: Many applications need to send SMS notifications (e.g., order confirmations, alerts, OTPs) but lack visibility into whether the message was actually delivered to the recipient's handset. Relying solely on the initial API
sent
confirmation is insufficient; network issues, invalid numbers, or carrier blocks can prevent delivery.Solution: We will leverage MessageBird's SMS API to send messages and configure its webhook feature. MessageBird will send HTTP POST requests (webhooks) to a specific endpoint in our RedwoodJS application whenever the delivery status of a message changes (e.g.,
sent
,delivered
,delivery_failed
). Our application will securely verify these incoming webhooks and update the corresponding message record in our database.Technologies Used:
System Architecture:
Prerequisites:
npm install -g redwoodjs
ngrok
or a similar tool to expose your local webhook endpoint to the internet for MessageBird callbacks.1. Setting up the RedwoodJS Project
Let's create a new RedwoodJS application and install the necessary dependencies.
Create RedwoodJS App: Open your terminal and run:
This command scaffolds a new RedwoodJS project named
messagebird-redwood-status
using TypeScript.Navigate to Project Directory:
Install Dependencies: We need the MessageBird Node.js SDK and
dotenv
for managing environment variables on the API side.Configure Environment Variables: RedwoodJS uses a
.env
file at the project root for environment variables. Create this file (touch .env
) and add the following_ replacing placeholders with your actual credentials:DATABASE_URL
: Points to your PostgreSQL instance. Ensure the database exists.DATABASE_URL
uses a weak password (password
). Always use strong, unique passwords for your databases, even during development. Consider using a password manager.MESSAGEBIRD_API_KEY
: Your live API key from the MessageBird dashboard (Developers -> API access). Treat this like a password.MESSAGEBIRD_SIGNING_KEY
: Found below your API keys in the MessageBird dashboard. Used to verify webhook authenticity. Treat this securely.MESSAGEBIRD_ORIGINATOR
: The 'From' number or name for outgoing SMS. Must be a number purchased/verified in MessageBird or an approved Alphanumeric Sender ID (check MessageBird documentation for country support).APP_BASE_URL
: Crucial for constructing thestatusReportUrl
sent to MessageBird and the URL you configure in the MessageBird dashboard. Remember to update this when usingngrok
or deploying.Initialize MessageBird Client: Create a reusable MessageBird client instance. Create the file
api/src/lib/messagebird.ts
:.env
.2. Implementing Core Functionality (Database & Services)
We need a way to store message details and their status updates.
Define Database Schema: Open
api/db/schema.prisma
and define aMessage
model:id
: Standard CUID primary key.messageBirdId
: Stores the ID returned by MessageBird when a message is created. This is crucial for correlating webhook updates. It's nullable initially and marked unique.recipient
,body
: Basic message details.status
: Tracks the delivery status. Defaults topending
.status
Type: UsingString
provides flexibility if MessageBird introduces new status values not defined in your code. However, using a Prismaenum
(e.g.,enum MessageStatus { PENDING, QUEUED, SENT, DELIVERED, FAILED, EXPIRED }
) offers better type safety and clearly defines allowed states within your application code. Choose based on whether you prioritize flexibility or strict type checking.statusDetails
: Can store additional error information from failed delivery webhooks.statusUpdatedAt
: Timestamp from the webhook event.createdAt
,updatedAt
: Standard Prisma timestamps.relatedRecordId
/relatedRecord
: Shows how you might link this message back to another entity in your app (like anOrder
orUser
).@@index([status])
: Added an index to thestatus
field for potentially faster queries filtering by status.Apply Database Migrations: Create and apply the migration to your database:
Follow the prompts. This creates the
Message
table in your PostgreSQL database and applies the index.Generate GraphQL SDL and Services: Use Redwood's generators to create the boilerplate for GraphQL types, queries, mutations, and the service file for the
Message
model:This command creates:
api/src/graphql/messages.sdl.ts
: Defines GraphQL types (Message
), queries (messages
,message
), and mutations (createMessage
,updateMessage
,deleteMessage
).api/src/services/messages/messages.ts
: Contains Prisma logic for the generated queries/mutations.api/src/services/messages/messages.scenarios.ts
: For defining seed data for tests.api/src/services/messages/messages.test.ts
: Basic test structure.Implement Message Sending Service: Modify the generated
api/src/services/messages/messages.ts
to handle sending SMS via MessageBird and integrate the webhook logic. We'll replace the defaultcreateMessage
with a customsendMessage
mutation and add a function to handle webhook updates.sendMessage
:recipient
andbody
as input.statusReportUrl
usingAPP_BASE_URL
and the exact path to the Redwood function we'll create (/.redwood/functions/messagebirdWebhook
).Message
record in the DB with statusqueued
.messagebird.messages.create
, passing thestatusReportUrl
.Promise
wrapper around the callback-based SDK method for better async/await flow.messageBirdId
from the response and sets status tosent
.config_error
,failed_to_send
,send_issue
).updateMessageStatusFromWebhook
:MessageBirdWebhookPayload
(which is now exported).Message
record using the uniquemessageBirdId
.status
,statusDetails
, andstatusUpdatedAt
fields.P2025
(Record Not Found) Prisma error as a non-critical warning. Returns a boolean indicating success/failure.createMessage
is removed/commented out with warnings.updateMessage
anddeleteMessage
are kept but include warnings and TODOs for adding authorization, as direct manipulation might bypass business logic or security checks.3. Building the API Layer (GraphQL)
Redwood generated most of the GraphQL setup. We just need to adjust the SDL to match our new
sendMessage
mutation.Update GraphQL Schema Definition: Modify
api/src/graphql/messages.sdl.ts
:SendMessageInput
type for our custom mutation.createMessage
mutation withsendMessage(input: SendMessageInput!): Message!
.Message
type, queries (messages
,message
), and other mutations (updateMessage
,deleteMessage
).@requireAuth
is a basic check, might need role-based access) if the standardupdate/delete
mutations are exposed.scalar DateTime
definition is present (often included by default).Testing the Mutation (Conceptual): You can use the Redwood GraphQL Playground (usually available at
http://localhost:8911/graphql
when runningyarn rw dev
) to test thesendMessage
mutation.Example GraphQL Mutation:
sendMessage
service function, create a DB record, call the MessageBird API, update the record with themessageBirdId
andsent
status, and return the result.4. Integrating Third-Party Services (MessageBird Webhook)
This is the core of receiving status updates. We need a dedicated Redwood Function to handle incoming POST requests from MessageBird.
Create Redwood Function: Use the Redwood generator:
This creates
api/src/functions/messagebirdWebhook.ts
.Implement Webhook Handler and Signature Verification: Edit
api/src/functions/messagebirdWebhook.ts
to verify the signature and process the payload:WebhookSignatureJwt
from the MessageBird SDK.MESSAGEBIRD_SIGNING_KEY
from.env
.export const config = { api: { bodyParser: false } };
to ensureevent.body
contains the raw request string needed for verification.verifier
outside the handler.POST
method and the presence of theMessageBird-Signature-JWT
header.requestUrl
usingAPP_BASE_URL
andevent.path
. This is a critical step and needs careful testing in deployment.verifier.verify
with the signature, reconstructed URL, query parameters (event.queryStringParameters
), and the raw body (event.body
).401 Unauthorized
if verification fails.rawBody
into a JSON object only after successful signature verification.payload
to ensure required fields exist.400 Bad Request
if parsing or validation fails.updateMessageStatusFromWebhook
service function with the validatedpayload
.200 OK
if the service function indicates success (returnstrue
).500 Internal Server Error
if the service function indicates failure (returnsfalse
) or if an unexpected error occurs, potentially prompting MessageBird to retry.