Frequently Asked Questions
Start by installing the NestJS CLI, creating a new project, and adding the MessageBird SDK, NestJS ConfigModule, and dotenv. Configure the ConfigModule to load environment variables, enable the ValidationPipe globally, and create a ".env" file to store your MessageBird API key and originator (virtual number).
MessageBird is a communication platform that provides APIs for various communication channels, including SMS. In this NestJS setup, MessageBird's SMS API and virtual numbers are used to receive incoming SMS messages and send replies, enabling two-way SMS communication.
A tunneling service like ngrok or localtunnel is necessary during development to expose your locally running NestJS application to the public internet. This allows MessageBird webhooks, which are triggered by incoming SMS messages, to reach your local server.
While not essential for simple SMS applications, a database becomes crucial when you need to persist message history, especially for features like customer support chats, interactive SMS campaigns, or scenarios requiring tracking message status and user responses over time.
Use the NestJS CLI to generate a module, controller, and service specifically for webhooks. Define an IncomingMessageDto with validation rules, implement the MessageBird service to initialize the SDK and send replies, and configure the controller to handle incoming SMS webhooks at the desired endpoint.
The IncomingMessageDto is a Data Transfer Object in NestJS that represents the structure of the incoming webhook payload from MessageBird. It is used with the ValidationPipe to automatically validate and sanitize incoming data, ensuring only expected fields with correct types are processed.
Implement try...catch blocks around MessageBird API calls within your service to handle potential errors during sending. Log the errors using NestJS's Logger and throw appropriate exceptions. Consider retry mechanisms for better resilience.
After starting your local server and a tunneling service, go to the MessageBird Flow Builder. Create a new flow triggered by SMS, select your virtual number, and add a "Call HTTP endpoint with SMS" action. Set the method to POST and the URL to your tunnel URL + webhook endpoint path.
class-validator and class-transformer enhance security and streamline data handling. class-validator ensures that only expected fields with correct types are processed, preventing vulnerabilities. class-transformer simplifies data transformations between different formats.
To handle duplicate messages, use a database to store message history. Upon receiving a webhook, query the database for a message with a similar timestamp, originator, and payload. If found, log it as a duplicate and avoid reprocessing.
Implement input validation, rate limiting using @nestjs/throttler, secure API key management, and regular dependency updates to enhance your application's security.
Use NestJS's built-in Logger for development. For production, integrate a more robust logging library like Pino or Winston to output structured logs, facilitating analysis and debugging.
Initialize the MessageBird Node.js SDK (v10+) using your API key. Use the messages.create method with appropriate MessageParameters, including originator, recipients, and body, to send SMS messages. Handle responses and errors appropriately.
Key dependencies include messagebird (Node.js SDK), @nestjs/config, dotenv, class-validator, class-transformer, and optionally @nestjs/typeorm, typeorm, and a database driver like pg for PostgreSQL.
Building Production-Ready Two-Way SMS with NestJS and MessageBird
This guide provides a step-by-step walkthrough for building a robust two-way SMS messaging system using the NestJS framework and the MessageBird API. We'll create an application capable of receiving incoming SMS messages via MessageBird webhooks and sending replies back to the original sender.
This implementation solves the common need for applications to engage in real-time, bidirectional SMS conversations with users, essential for features like customer support chat, notifications requiring user responses, or interactive SMS campaigns.
Key Technologies:
System Architecture:
Prerequisites:
ngrok
orlocaltunnel
installed globally.By the end of this guide, you will have a functional NestJS application that:
1. Setting up the Project
Let's initialize our NestJS project and install the necessary dependencies.
Install NestJS CLI: If you haven't already, install the NestJS CLI globally.
Create NestJS Project: Generate a new NestJS project.
Install Dependencies: We need the MessageBird SDK, NestJS config module, and
dotenv
for environment variable management.messagebird
: The official Node.js SDK (v10+).@nestjs/config
: For managing environment variables.dotenv
: To load environment variables from a.env
file during development.class-validator
&class-transformer
: For validating incoming webhook data using DTOs.Environment Configuration (
.env
): Create a.env
file in the project root. This file will store sensitive credentials and configuration, and should not be committed to version control (ensure.env
is listed in your.gitignore
file).+
and country code, e.g.,+12015550123
).Add these values to your
.env
file:Configure NestJS ConfigModule: Import and configure the
ConfigModule
in your main application module (src/app.module.ts
) to load environment variables from the.env
file.Enable Validation Pipe Globally: To automatically validate incoming request bodies against our DTOs, enable the
ValidationPipe
globally insrc/main.ts
.Now the basic project structure and configuration are in place.
2. Implementing Core Functionality
We'll create a dedicated module, controller, service, and DTO to handle incoming MessageBird webhooks.
Generate Module, Controller, and Service: Use the NestJS CLI to scaffold these components within a
webhooks
feature directory.--flat
: Prevents creating an extra subdirectory for the controller/service files.--no-spec
: Skips generating test files for now (you should add them later).Ensure the
WebhooksModule
is imported intoAppModule
(as shown in the previous step).Create Incoming Message DTO: Define a Data Transfer Object (DTO) to represent the expected payload from the MessageBird SMS webhook and add validation rules using
class-validator
.Implement the MessageBird Service: This service will contain the logic to initialize the MessageBird SDK (v10+) and send reply messages using
async/await
.Implement the MessageBird Controller: This controller defines the webhook endpoint. It uses the DTO for validation and delegates processing to the service.
With these components, the core logic for receiving and replying to SMS messages is implemented using the modern MessageBird SDK.
3. Building a Complete API Layer
In this specific scenario, the primary "API" is the webhook endpoint (
POST /webhooks/messagebird/sms
) that receives data from MessageBird. We aren't building a traditional REST API for external clients to call to initiate SMS actions (though you could certainly add endpoints likePOST /messages
for that purpose if needed)./webhook
).IncomingMessageDto
and the globalValidationPipe
withwhitelist: true
. Invalid requests (missing required fields, incorrect types) will automatically result in a400 Bad Request
response from NestJS before your controller code even runs. Fields not defined in the DTO are automatically stripped.POST
/webhooks/messagebird/sms
application/x-www-form-urlencoded
. NestJS handles both JSON and form-urlencoded by default. Crucially, even though MessageBird might send fields likerecipient
,id
, etc., ourIncomingMessageDto
combined withwhitelist: true
ensures that onlyoriginator
andpayload
are accepted and passed to our service logic in this example.200 OK
"OK"
(The body content isn't critical for MessageBird).400 Bad Request
4. Integrating with MessageBird (Flow Builder Setup)
This is a critical step. We need to tell MessageBird where to send incoming SMS messages directed to your virtual number.
Start Tunneling: Before configuring MessageBird, you need a public URL that points to your local NestJS application.
npm run start:dev
(oryarn start:dev
). It should be running (likely on port 3000).3000
if your app uses a different port):ngrok http 3000
lt --port 3000
https://random-subdomain.ngrok.io
orhttps://your-subdomain.localtunnel.me
). Copy this HTTPS URL. You'll need it in the next step. Keep this tunnel running while testing.Configure MessageBird Flow Builder:
+
icon below the trigger step to add an action.[Your Tunnel HTTPS URL]/webhooks/messagebird/sms
.https://random-subdomain.ngrok.io/webhooks/messagebird/sms
Environment Variables Recap:
MESSAGEBIRD_API_KEY
: Your Live API key from the MessageBird Dashboard (Developers > API Access). Used by the SDK to authenticate requests to MessageBird (like sending replies).MESSAGEBIRD_ORIGINATOR
: Your purchased virtual mobile number (e.g.,+12015550123
) from the MessageBird Dashboard (Numbers). Used as theFrom
number when sending replies via the SDK.Ensure these are correctly set in your
.env
file for local development and configured securely in your deployment environment.5. Implementing Error Handling, Logging, and Retry Mechanisms
ValidationPipe
. NestJS returns a 400 response.messagebird.service.ts
uses atry...catch
block around themessagebird.messages.create
call within theasync sendMessage
method. It logs the specific error from the SDK and throws a NestJSInternalServerErrorException
(500). This prevents leaking detailed SDK errors to the caller (the controller) while signaling a failure. The controller can decide how to handle this (log and return 200, or let it bubble up to return 500).Logger
(@nestjs/common
).messagebird.controller.ts
).messagebird.service.ts
).messagebird.service.ts
).messagebird.service.ts
).messagebird.service.ts
).pino
withnestjs-pino
) to output structured JSON logs, which are easier to parse and analyze with log aggregation tools (e.g., Datadog, ELK stack). Configure log levels appropriately (e.g., INFO for standard operations, WARN for recoverable issues, ERROR for failures).200 OK
quickly to acknowledge receipt. Offloading work to queues helps significantly here.messagebird.messages.create
call fails. For production robustness:sendMessage
method (e.g., usingasync-retry
npm package) with exponential backoff for transient network issues or temporary MessageBird API problems. Be cautious not to block the webhook response for too long.6. Creating a Database Schema and Data Layer (Optional)
While not strictly required for a simple echo bot, persisting message history is crucial for real applications (support chats, order updates). Here's a conceptual outline using TypeORM and PostgreSQL (adapt for your chosen database).
Install Dependencies:
Configure TypeOrmModule: Set up the database connection in
app.module.ts
(or a dedicated database module), loading credentials fromConfigService
.Remember to add
DB_HOST
,DB_PORT
,DB_USERNAME
,DB_PASSWORD
,DB_NAME
to your.env
.Define Message Entity: Create an entity representing a message record.
Integrate with Service:
Repository<Message>
intoMessagebirdService
.handleIncomingMessage
, create and save anINBOUND
message record before sending the reply.sendMessage
, create and save anOUTBOUND
message record after successfully sending via the SDK (include themessagebirdId
from the response).Migrations: For production, set
synchronize: false
and use TypeORM migrations to manage schema changes safely.package.json
.npm run typeorm -- migration:generate -n InitialSchema
npm run typeorm -- migration:run
This provides a basic structure for message persistence, enabling conversation history and state management.
7. Adding Security Features
Securing your webhook endpoint and application is vital.
class-validator
within theIncomingMessageDto
and the globalValidationPipe
withwhitelist: true
already provides strong input validation. It ensures only expected fields with correct types are processed and strips unexpected data, mitigating risks like prototype pollution.@nestjs/throttler
.npm install @nestjs/throttler
oryarn add @nestjs/throttler
app.module.ts
:@Throttle()
decorator if needed..env
for local development (and ensure it's in.gitignore
).npm outdated
,npm update
oryarn outdated
,yarn upgrade
). Use tools likenpm audit
oryarn audit
.8. Handling Special Cases
Real-world SMS interaction involves nuances:
200 OK
initially).id
usually refer to the message stored on MessageBird's side.originator
and with the identicalpayload
already exists in your database. If so, log the duplicate and return200 OK
without reprocessing. This isn't foolproof but helps prevent obvious double replies.200 OK
. Offloading processing to a background queue (as mentioned in Section 5) is the most robust way to achieve this, minimizing the chance MessageBird needs to retry.