Frequently Asked Questions
Configure a webhook in the MessageBird dashboard to forward incoming SMS messages to your application's endpoint (e.g., your-ngrok-url/messagebird). Use Flow Builder for attaching it to a number or the number's settings directly. Ensure you set the method to POST and enable request signing to get a signing key for verification on your side.
Fastify is a high-performance Node.js web framework known for its speed and extensibility. It is an ideal choice for building efficient, scalable SMS applications. Its plugin architecture and developer-friendly features make development smoother.
The guide details setting up a campaign sending endpoint (not fully implemented in the provided snippet). This would use MessageBird's messages.create API endpoint to dispatch messages to multiple recipients retrieved from the database. Be sure to protect this endpoint with authentication (an API Key is used as an example).
MessageBird uses request signing as a security measure to allow you to verify that incoming webhooks actually originate from MessageBird and haven't been tampered with in transit. This is important to prevent fraudulent requests.
Phone number normalization should be done *as early as possible* in the process—ideally immediately after receiving a number. Consistent formatting, such as E.164, is crucial for accurate storage, lookup, and sending of messages. This guide uses a basic normalization function, but a robust library is recommended for production.
The tutorial uses `better-sqlite3`, a lightweight SQLite library for Node.js, for simplicity. For production systems with higher load and scalability requirements, PostgreSQL or MySQL would be more suitable.
The webhook handler checks for keywords in uppercase (e.g., 'JOIN', 'STOP') in the message payload. If 'JOIN', it adds the subscriber to the database. If 'STOP', it unsubscribes them. The database functions are accessed through `fastify.db` provided by the database plugin.
Yes, the provided webhook handler logic can be easily extended. Add additional checks for other keywords and define corresponding actions. For more complex menu systems or automated responses, a more sophisticated message processing logic might be needed.
`ngrok` is a tunneling service that creates a publicly accessible URL to your local development server. This is needed so MessageBird can send webhook notifications to your application during development. For production, your server would have a dedicated public URL.
Setting "type": "module" in `package.json` tells Node.js to treat all files as ES modules, allowing you to use the import/export syntax. This is the modern standard for modules in JavaScript and offers several advantages.
Navigate to your project folder in your terminal, then use `npm install fastify messagebird dotenv better-sqlite3 @fastify/rate-limit @fastify/auth` command to install the necessary packages. This command downloads the necessary libraries for your application.
The `dotenv` package is used to load environment variables from a `.env` file. This is essential for securely managing sensitive information such as API keys and other secrets without committing them directly to your code.
E.164 format (e.g., `+14155552671`) ensures consistent and internationally recognized storage of phone numbers. It simplifies processing, validation, and avoids ambiguity when handling numbers from different regions. The database schema recommends this format.
You can obtain a MessageBird API key by signing up for or logging in to your MessageBird account. Go to the Developers > API access section of your dashboard, and retrieve the key from there.
This guide provides a step-by-step walkthrough for building a production-ready SMS marketing campaign management system using Node.js, the Fastify framework, and the MessageBird API. We'll cover everything from project setup and core logic implementation to security, deployment, and testing.
By the end of this tutorial, you'll have a functional application that can:
JOIN
) and opt-outs (STOP
).Target Audience: Developers familiar with Node.js and basic web framework concepts. Prior experience with Fastify or MessageBird is helpful but not required.
Technologies Used:
better-sqlite3
: A simple, file-based SQL database, suitable for this example. For production, consider PostgreSQL or MySQL.dotenv
: For managing environment variables.@fastify/rate-limit
: Plugin for basic API rate limiting.@fastify/auth
: Plugin for authentication strategy integration.System Architecture:
Prerequisites:
curl
or Postman).ngrok
or a similar tunneling service: Essential for exposing your local development server to MessageBird's webhooks. For production, you'll need a stable public URL for your deployed application (see Section 10). Install ngrokFinal Outcome:
A robust Node.js application capable of managing SMS subscriptions and sending broadcasts, ready for further extension and deployment.
1. Setting Up the Project
Let's initialize our Node.js project and install the necessary dependencies.
1.1 Create Project Directory & Initialize:
Open your terminal and run the following commands:
This creates a new directory, navigates into it, and initializes a
package.json
file with default settings.1.2 Enable ES Modules:
We'll use ES Modules (import/export syntax). Open
package.json
and add the following top-level key:Why ES Modules? It's the standard module system for JavaScript and offers benefits like static analysis and better tooling support compared to CommonJS (
require
).1.3 Install Dependencies:
Install Fastify_ the MessageBird SDK_
dotenv
for environment variables_better-sqlite3
for our database_@fastify/rate-limit
for protection_ and@fastify/auth
for authentication handling.fastify
: The core web framework.messagebird
: The official Node.js SDK for interacting with the MessageBird API.dotenv
: Loads environment variables from a.env
file intoprocess.env
. Essential for managing sensitive credentials.better-sqlite3
: A high-performance SQLite driver for Node.js. Chosen for simplicity in this guide.@fastify/rate-limit
: A plugin to protect API endpoints from abuse.@fastify/auth
: A plugin to handle various authentication strategies.1.4 Create Project Structure:
Organize the project for clarity and maintainability.
app.js
: The main application entry point..env
: Stores environment variables (API keys_ secrets). Never commit this file..gitignore
: Specifies intentionally untracked files that Git should ignore.db/
: Contains database-related files (schema_ connection logic).routes/
: Defines the application's API endpoints.plugins/
: Holds reusable Fastify plugins (like DB connection_ MessageBird client setup_ authentication).utils/
: Contains utility functions (like phone number normalization).1.5 Configure
.gitignore
:Add the following to your
.gitignore
file to prevent sensitive information and generated files from being committed:1.6 Set Up Environment Variables (
.env
):Create a
.env
file in the root of your project. We'll populate this with actual values in the next steps.MESSAGEBIRD_API_KEY
: Your live API key from the MessageBird dashboard.MESSAGEBIRD_WEBHOOK_SIGNING_KEY
: The key used to verify incoming webhooks. Found in MessageBird webhook settings.MESSAGEBIRD_ORIGINATOR
: The MessageBird virtual number (e.g._+12025550142
) or approved Alphanumeric Sender ID (e.g._MyCompany
) used to send messages.API_SECRET_KEY
: A secret key you generate to protect the campaign sending endpoint. Use a strong_ random string.DATABASE_PATH
: The file path for the SQLite database.2. Integrating with MessageBird
Now_ let's configure the connection to MessageBird and set up the necessary components on their platform.
2.1 Get MessageBird Credentials:
.env
file asMESSAGEBIRD_API_KEY
.+
and country code)..env
file asMESSAGEBIRD_ORIGINATOR
. (Alternatively, use an approved Alphanumeric Sender ID if applicable).MESSAGEBIRD_WEBHOOK_SIGNING_KEY
blank for now or use a placeholder.2.2 Initialize MessageBird Client Plugin:
Create a Fastify plugin to initialize and share the MessageBird client instance.
fastify-plugin
: Used to prevent Fastify from creating separate encapsulation contexts for the plugin, allowingfastify.decorate
to addmessagebird
to the global Fastify instance.fastify.decorate
: Makes the initializedmbClient
available throughout your application viafastify.messagebird
.3. Creating the Database Schema and Data Layer
We'll use
better-sqlite3
for storing subscriber phone numbers and their status.3.1 Define Database Schema:
Create the SQL schema definition.
phone_number
: Stores the subscriber's number, acting as the unique identifier. Storing in E.164 format (e.g.,+14155552671
) is highly recommended for consistency.subscribed
: A simple flag (integer 0 or 1) to indicate active subscription status.3.2 Implement Database Connection Plugin:
Create a plugin to manage the SQLite database connection and provide helper functions.
initDb
: Connects to the SQLite file and executes the schema definition.addSubscriber
: Inserts a new number or updates an existing one to be subscribed. UsesON CONFLICT...DO UPDATE
(UPSERT) for efficiency.removeSubscriber
: Sets thesubscribed
flag to 0 for a given number.isSubscribed
: Checks the subscription status.getAllSubscribers
: Retrieves a list of all active subscribers' phone numbers.closeDb
: Closes the database connection.3.3 Create the Fastify DB Plugin:
Wrap the database initialization in a Fastify plugin.
dbPath
from options, callsinitDb
, decoratesfastify
with the data access functions underfastify.db
, and ensurescloseDb
is called when the server shuts down using theonClose
hook.4. Implementing the Core Functionality (Webhook Handler)
This route will receive incoming SMS messages from MessageBird, parse them for keywords (
JOIN
,STOP
), update the database, and send a confirmation reply.4.1 Create the Webhook Route:
Key Points:
originator
andpayload
exist. Use@fastify/sensible
orfastify-type-provider-zod
for more complex validation in production.JOIN
orSTOP
.fastify.db
functions to update subscription status.normalizePhoneNumber
,isValidE164
) which we'll create next. The implementation is basic; stronger validation is recommended for production.fastify.messagebird.messages.create
to send an SMS back to the user. Theoriginator
here is your MessageBird number/sender ID.200 OK
to MessageBird. This acknowledges receipt and prevents MessageBird from retrying potentially failing requests indefinitely. Monitor logs for actual errors.preHandler
: We specify that this route needs authentication/verification usingfastify.auth
and a verification function (verifyMessageBird
) that we will create as a plugin. Note the route path is/messagebird
.4.2 Add Phone Number Utilities:
Create a utility file for handling phone numbers.
Important Note: The
normalizePhoneNumber
function provided here is intentionally basic and carries significant limitations. For any production system, using a dedicated library likelibphonenumber-js
is strongly recommended for accurate parsing, validation, and formatting of international phone numbers.4.3 Configure MessageBird Webhook:
Now, we need to tell MessageBird where to send incoming SMS messages.
Start
ngrok
: Expose your local development server to the internet. If your app runs on port 3000:ngrok
will give you a public HTTPS URL (e.g.,https://<unique-id>.ngrok-free.app
). Copy this URL.Configure Flow Builder or Number Settings:
ngrok
URL followed by the webhook path:https://<unique-id>.ngrok-free.app/messagebird
.POST
..env
file asMESSAGEBIRD_WEBHOOK_SIGNING_KEY
.ngrok
URL + path:https://<unique-id>.ngrok-free.app/messagebird
..env
.Important: Every time you restart
ngrok
, you get a new public URL. You must update the webhook URL in your MessageBird Flow/Number settings accordingly. For production, you'll use your server's permanent public URL.5. Adding Security Features
Security is paramount, especially when dealing with external webhooks and potentially sending bulk messages.
5.1 Verify MessageBird Webhook Signatures:
MessageBird signs its webhook requests so you can verify they genuinely came from MessageBird. Ensure you have installed
@fastify/auth
(npm install @fastify/auth
).Create a plugin for this verification logic.
@fastify/auth
being registered first (handled inapp.js
).request.rawBody
: Crucially relies on the custom content type parser defined inapp.js
to access the raw, unparsed request body. This is essential because the signature is calculated over the raw bytes, not the parsed JSON.crypto.timingSafeEqual
: Used for secure comparison of signatures to prevent timing attacks. Requires buffers of equal length.fastify.verifyMessageBird
available. It's intended to be used withinfastify.auth
in route options.5.2 Protect the Campaign Sending Endpoint:
Create a simple API key authentication plugin.