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 comprehensive tutorial shows you how to implement two-way SMS messaging using NestJS and the MessageBird API. You'll learn how to handle inbound SMS webhooks, send automated replies, secure your webhook endpoints with JWT verification, and deploy a production-ready messaging system.
Two-way SMS messaging solves critical business needs: customer support chat, notifications requiring user responses, and interactive SMS campaigns. By the end of this guide, you'll have a production-ready NestJS application with proper error handling, webhook security, and configuration management.
Key Technologies:
System Architecture:
Prerequisites:
ngrokorlocaltunnelinstalled globallyBy the end of this guide, you will have a functional NestJS application that:
1. Setting up Your NestJS Project
Initialize your NestJS project and install the necessary dependencies for MessageBird integration.
Install NestJS CLI: If you haven't already, install the NestJS CLI globally.
Create NestJS Project: Generate a new NestJS project.
Install Dependencies: Install the MessageBird SDK, NestJS config module, and
dotenvfor environment variable management.messagebird: The official Node.js SDK (v10+).@nestjs/config: Manages environment variables.dotenv: Loads environment variables from a.envfile during development.class-validator&class-transformer: Validates incoming webhook data using DTOs.Environment Configuration (
.env): Create a.envfile in the project root. This file stores sensitive credentials and configuration. Do not commit this file to version control. Ensure.envis listed in your.gitignorefile.+and country code, e.g.,+12015550123).Add these values to your
.envfile:Configure NestJS ConfigModule: Import and configure the
ConfigModulein your main application module (src/app.module.ts) to load environment variables from the.envfile.Enable Validation Pipe Globally: To automatically validate incoming request bodies against DTOs, enable the
ValidationPipeglobally insrc/main.ts.The basic project structure and configuration are now in place.
2. Implementing Webhook Handler for Inbound SMS
Create a dedicated module, controller, service, and DTO to handle incoming MessageBird SMS webhooks.
Generate Module, Controller, and Service: Use the NestJS CLI to scaffold these components within a
webhooksfeature directory.--flat: Prevents creating an extra subdirectory for the controller/service files.--no-spec: Skips generating test files for now (add them later for production code).The
WebhooksModuleshould already be 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. Add validation rules using
class-validator.Implement the MessageBird Service: This service contains the logic to initialize the MessageBird SDK and send reply messages. The MessageBird Node.js SDK uses callbacks by default, so use Node.js's
util.promisifyto convert callback-based methods to Promise-based ones for modern async/await patterns.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 MessageBird SDK.
3. Building a Complete Webhook API Endpoint
In this 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 can add endpoints likePOST /messagesfor that purpose if needed)./webhook).IncomingMessageDtoand the globalValidationPipewithwhitelist: truehandle this effectively. Invalid requests (missing required fields, incorrect types) result in a400 Bad Requestresponse from NestJS before your controller code runs. Fields not defined in the DTO are stripped automatically based on your ValidationPipe configuration.Method:
POSTPath:
/webhooks/messagebird/smsRequest Content-Type: MessageBird sends webhooks as
application/x-www-form-urlencoded(form-encoded) by default, though it can also sendapplication/jsondepending on Flow Builder configuration. NestJS handles both formats automatically with its body parser middleware.Request Body Structure:
Form-encoded format (default from MessageBird):
JSON format (if configured):
Note: MessageBird sends additional fields beyond
originatorandpayload. OurIncomingMessageDtocombined withwhitelist: trueensures only the fields we define are accepted and passed to our service logic. WithforbidNonWhitelisted: false(recommended for webhooks), extra fields are silently stripped. WithforbidNonWhitelisted: true, requests with extra fields are rejected with a 400 error.Successful Response:
200 OK"OK"(The body content isn't critical for MessageBird).Error Response (e.g., Validation Failed):
400 Bad Request4. Configuring MessageBird Flow Builder for Webhooks
This is a critical step. 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).3000if your app uses a different port):ngrok http 3000lt --port 3000https://random-subdomain.ngrok.ioorhttps://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/smsImportant Notes:
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 theFromnumber when sending replies via the SDK.MESSAGEBIRD_SIGNING_KEY: Your signing key from the MessageBird Dashboard (Developers > API Access). Used to verify webhook request signatures for security.Ensure these are correctly set in your
.envfile for local development and configured securely in your deployment environment.5. Implementing Error Handling and Retry Logic
ValidationPipe. NestJS returns a 400 response.messagebird.service.tsuses atry...catchblock around themessagebird.messages.createcall within theasync sendMessagemethod. 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).pinowithnestjs-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 OKquickly to acknowledge receipt. Offloading work to queues helps significantly here.messagebird.messages.createcall fails. For production robustness:sendMessagemethod (e.g., usingasync-retrynpm 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 for Message Persistence (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.Add
DB_HOST,DB_PORT,DB_USERNAME,DB_PASSWORD,DB_NAMEto your.env.Define Message Entity: Create an entity representing a message record.
Integrate with Service:
Repository<Message>intoMessagebirdService.handleIncomingMessage, create and save anINBOUNDmessage record before sending the reply.sendMessage, create and save anOUTBOUNDmessage record after successfully sending via the SDK (include themessagebirdIdfrom the response).Migrations: For production, set
synchronize: falseand use TypeORM migrations to manage schema changes safely.package.json.npm run typeorm -- migration:generate -n InitialSchemanpm run typeorm -- migration:runThis provides a basic structure for message persistence, enabling conversation history and state management.
7. Securing Your Webhook with JWT Signature Verification
Securing your webhook endpoint and application is vital.
Webhook Signature Verification (Highly Recommended): MessageBird signs all webhook requests with a JWT signature in the
MessageBird-Signature-JWTheader. Verifying this signature ensures the request genuinely comes from MessageBird and hasn't been tampered with, protecting against replay attacks and unauthorized webhook calls.Setup:
.envfile:Implementation: Create a NestJS guard to verify the signature. Install the required package if not already present:
Create the verification guard:
Apply the guard to your webhook controller:
Important Note: If your Node.js server is behind a proxy, configure Express to trust the proxy to correctly infer the protocol and hostname:
Input Validation and Sanitization:
class-validatorwithin theIncomingMessageDtoand the globalValidationPipewithwhitelist: trueprovides strong input validation. It ensures only expected fields with correct types are processed, mitigating risks like prototype pollution.Common Vulnerabilities:
Rate Limiting: Protect the webhook endpoint from brute-force/DoS attacks using
@nestjs/throttler.npm install @nestjs/throttleroryarn add @nestjs/throttlerapp.module.ts:@Throttle()decorator if needed.Secure API Key/Secrets Management:
.envfor local development (and ensure it's in.gitignore).Keep Dependencies Updated: Regularly update Node.js, NestJS, MessageBird SDK, and other dependencies to patch known security vulnerabilities (
npm outdated,npm updateoryarn outdated,yarn upgrade). Use tools likenpm auditoryarn audit.8. Handling Edge Cases in SMS Integration
Real-world SMS interaction involves nuances:
Duplicate Messages (Idempotency): MessageBird occasionally sends the same webhook twice (e.g., if it didn't receive a timely
200 OKinitially).idusually refer to the message stored on MessageBird's side.originatorand with the identicalpayloadalready exists in your database. If so, log the duplicate and return200 OKwithout 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.Long Messages and Message Concatenation: SMS messages are limited to 160 characters for standard GSM encoding or 70 characters for Unicode (including emojis). Longer messages are split into segments.
payloadin one piece.Handling Opt-Outs and Stop Keywords: For marketing messages, you're typically required to honor opt-out requests (e.g., when a user replies "STOP").
handleIncomingMessagemethod, check if thepayload(converted to lowercase) matches keywords like "stop", "unsubscribe", or "cancel". If matched, add theoriginatorto a suppression list (database table) and send a confirmation message. Before sending any outgoing message (especially marketing), check the suppression list to ensure the recipient hasn't opted out.Time Zone Handling: Store all timestamps in UTC in your database. When displaying messages to users, convert to their local time zone.
Rate Limits and Throughput: MessageBird has rate limits on API requests. Check the MessageBird documentation for your account tier. For high-volume applications, implement throttling on the sending side or use a queue to control the rate of outgoing messages.
Message Status Tracking: The MessageBird SDK returns a message ID when you send an SMS. Store this ID in your database. Set up a separate webhook endpoint to receive delivery status updates from MessageBird (delivered, failed, etc.). Update your database records based on these status callbacks to track message delivery.
9. Testing Your NestJS SMS Webhook Integration
Unit Tests: Write unit tests for your service logic using Jest (included with NestJS).
MessagebirdService.handleIncomingMessagewith various input payloads.Integration Tests: Test the full webhook endpoint flow:
@nestjs/testingto create a testing module./webhooks/messagebird/smswith various payloads.Manual Testing with cURL: Use the cURL examples from Section 3 to test your local endpoint.
End-to-End Testing: Send a real SMS to your MessageBird virtual number and verify that:
10. Production Deployment Best Practices
Environment Variables: Ensure all required environment variables are set in your production environment:
MESSAGEBIRD_API_KEYMESSAGEBIRD_ORIGINATORMESSAGEBIRD_SIGNING_KEYDB_HOST,DB_PORT,DB_USERNAME,DB_PASSWORD,DB_NAME(if using database)PORT(optional, defaults to 3000)NODE_ENV=productionUpdate Webhook URL: Once deployed, update the webhook URL in MessageBird Flow Builder to point to your production domain (e.g.,
https://yourdomain.com/webhooks/messagebird/sms).Scaling:
Monitoring:
Health Checks: Implement a health check endpoint using
@nestjs/terminus:Create a health module and endpoint that checks database connectivity, memory usage, and disk space. Configure your load balancer or orchestration system (Kubernetes) to use this endpoint for health checks.
Security Checklist:
NODE_ENV=productionto enable production optimizations and error handling.Database Migrations: Run TypeORM migrations on deployment to apply schema changes:
Ensure your CI/CD pipeline includes this step before starting the application.
11. Troubleshooting Common Issues
MESSAGEBIRD_SIGNING_KEYis correct and matches dashboardMESSAGEBIRD_API_KEYandMESSAGEBIRD_ORIGINATORin.env.envDebug Steps:
Conclusion
You've built a production-ready two-way SMS messaging system using NestJS and MessageBird. This application:
Next Steps:
For more information, refer to: