Frequently Asked Questions
Create a NestJS service that integrates with the MessageBird Verify API using their Node.js SDK. This service should handle sending the OTP to the user's phone number via SMS after validating the number format. The service will interact with MessageBird's API to generate and send the OTP, returning a verification ID to your application.
MessageBird's Verify API is a service for sending and verifying one-time passwords (OTPs) via SMS, commonly used for two-factor authentication (2FA). The API handles generating the OTP, delivering it to the user's phone number, and verifying user-submitted tokens, enhancing application security by adding an extra layer of verification beyond traditional passwords.
NestJS provides a structured and scalable framework for building server-side applications in Node.js, making it ideal for implementing OTP verification. It offers features like dependency injection, configuration management, and a modular architecture, making development more efficient and easier to maintain. Its support for TypeScript further improves code quality.
You can install the MessageBird Node.js SDK using either npm or yarn with the command npm install messagebird or yarn add messagebird. This SDK simplifies interaction with the MessageBird API, allowing your NestJS application to easily send SMS messages for OTP verification.
Store your MessageBird API key (test key for development, live key for production) in a .env file in your project root. Use the @nestjs/config module to load these environment variables securely into your NestJS application. Never commit .env to version control.
Class-validator, along with class-transformer, facilitates request data validation in NestJS. By defining Data Transfer Objects (DTOs) and decorating properties with validation rules, you ensure data integrity and prevent invalid data from reaching your service logic. This adds a layer of security and reliability.
Create a controller in your OTP module, define a POST /otp/verify endpoint, and inject the OtpService. Use the @Body() decorator and a DTO (VerifyOtpDto) to validate incoming data containing the verification ID and token. The controller calls the service's verifyOtp method, then returns the status ('verified' or an error) back to the client.
In your OTP controller, create a POST /otp/send endpoint and inject the OtpService. Use the @Body() decorator with a DTO (SendOtpDto) to validate the incoming phone number. The controller should then call the service's sendOtp method, returning the verification ID to the client.
The verification ID, returned by MessageBird's verify.create call, is a unique identifier associated with a specific OTP request. It links the OTP sent to the user with the subsequent verification process. This ID is then used along with the user-submitted OTP to complete the verification.
Implement comprehensive error handling in your OtpService by mapping MessageBird API error codes to appropriate HTTP exceptions. For example, an invalid token should return a 400 Bad Request, and an unexpected API failure should return a 500 Internal Server Error. Log errors with details to facilitate debugging.
Use class-validator's @IsPhoneNumber() decorator within the SendOtpDto to validate the phone number format before it reaches your service logic. Handle any remaining format issues or MessageBird-specific number errors (like code 21) in the OtpService by throwing a BadRequestException with a clear message.
Only use your live MessageBird API key in your production environment. During development and testing, use the test key. This prevents accidental charges and allows you to use MessageBird's test phone numbers. Remember to switch to the live key for real OTP delivery when deploying to production.
Implement phone number normalization in your OtpService to handle variations in user input (spaces, dashes, parentheses). A basic replace function can handle simple cases, or consider using a dedicated phone number formatting library for more robust normalization across international formats.
Build SMS OTP/2FA Authentication with MessageBird Verify API in NestJS
Integrate MessageBird's Verify API into your NestJS application to implement robust SMS-based Two-Factor Authentication (2FA) or phone number verification using One-Time Passwords (OTPs). This guide shows you how to build a secure, scalable backend API that sends OTPs via SMS and verifies user-submitted tokens.
Project Overview and Goals
What You'll Build:
Problem This Solves:
Verify user phone numbers and add a 2FA layer to your application. This prevents fraudulent account creation, secures accounts against unauthorized access, and verifies transactions by confirming control over a registered phone number. Common use cases include e-commerce checkout verification, financial transaction confirmation, and account recovery flows.
Technologies You'll Use:
System Architecture:
Prerequisites:
Install these requirements before starting:
npm install -g @nestjs/cli(NestJS v10+ requires Node.js v16 or higher)Final Outcome:
Build a functional NestJS API that:
After successful verification, integrate this into your full authentication flow by issuing JWT tokens or creating user sessions.
1. Setting Up the Project
Initialize a new NestJS project and install the necessary dependencies.
Create Your NestJS Project: Run the NestJS CLI command to create a new project named
nestjs-messagebird-otp.Choose your preferred package manager (npm or yarn) when prompted.
Navigate to Your Project Directory:
Install Required Dependencies: Install these essential packages:
@nestjs/config: Manages environment variablesmessagebird: Official MessageBird Node.js SDK (v4.0.1, last updated 2022 — monitor for alternatives if maintenance becomes a concern)class-validator,class-transformer: Validate request data using DTOsPackage Versions (as of 2025):
messagebird: v4.0.1 (last published 3 years ago — check for security vulnerabilities before production deployment)@nestjs/config: Compatible with NestJS v10+ and v11Security Note: The MessageBird SDK hasn't been updated since 2022. Before production deployment, check for known CVEs and consider alternatives like direct REST API integration or maintained community SDKs.
(Optional) Install Prisma Dependencies: If you plan to integrate with a database (e.g., to link verified numbers to users), install Prisma. This section is optional for the core OTP functionality.
Project Structure: Key directories:
src/: Application source code (main.ts,app.module.ts, etc.)src/otp/: Module for OTP logic (you'll create this).env: Environment variables (you'll create this)(Optional) prisma/: Prisma schema and migrations2. Environment Configuration
Securely manage API keys using the
@nestjs/configmodule and a.envfile. For production, use secret managers like AWS Secrets Manager, Azure Key Vault, or HashiCorp Vault instead of plain.envfiles.Create
.envfile: Create a file named.envin the project's root directory.Add MessageBird API Key: Obtain your API keys from the MessageBird Dashboard:
.envfile:Replace
YOUR_TEST_API_KEY_HEREwith your actual test key initially. Switch to the live key in your production environment.Purpose: This variable holds the secret key required to authenticate requests to the MessageBird API. Using test keys avoids costs and real messages during development.
(Optional) Add Database URL: If using Prisma (optional feature), add your database connection string:
Adjust the URL according to your database credentials and provider.
Purpose: This variable tells Prisma how to connect to your database.
Load
ConfigModule: Import and configureConfigModulein your main application module (src/app.module.ts) to make environment variables available throughout the app viaConfigService. Make it global and load.envvariables.Why
isGlobal: true? This makes theConfigServiceavailable in any module without needing to importConfigModulerepeatedly.3. Implementing Core Functionality (OTP Service)
Create a dedicated module and service to handle the logic of interacting with the MessageBird API.
Generate OTP Module and Service: Use the NestJS CLI to generate the
otpmodule and service.This creates
src/otp/otp.module.tsandsrc/otp/otp.service.ts(and spec file). NestJS automatically updatessrc/app.module.tsto importOtpModule.Implement
OtpService: Opensrc/otp/otp.service.ts. InjectConfigServiceto access the API key and instantiate the MessageBird client.Key Implementation Details:
new Promise? The MessageBird SDK uses callbacks. Wrap the callback logic in a Promise to work seamlessly with NestJS's async/await syntax.BadRequestExceptionfor known user-input issues or specific MessageBird error codes (like invalid number code 21, invalid token code 10). UseInternalServerErrorExceptionfor unexpected API failures or missing IDs. Logging the full error (JSON.stringify(err)) and specific codes helps diagnose issues.originatorshould ideally be a purchased virtual number from MessageBird or a short alphanumeric code (max 11 characters, check country-specific restrictions).templatemust include the%tokenplaceholder (verified from MessageBird Verify API documentation at https://developers.messagebird.com/api/verify/).phoneNumber.replace(/[\s\-()]/g, '')strips common characters before sending to MessageBird. For production, use a robust phone number library likelibphonenumber-jsfor comprehensive E.164 format validation and normalization, especially for international formats and country-specific validation.timeout: Range 30–172,801 seconds (default: 30s)tokenLength: Range 6–10 digits (default: 6)maxAttempts: Range 1–10 attempts (default: 1)type: Options aresms,flash,tts(text-to-speech), oremailCommon MessageBird Error Codes:
Register
ConfigServiceinOtpModule: SinceConfigModuleis global andConfigServiceis injected intoOtpService(which is part ofOtpModule), no explicit import ofConfigModuleor registration ofConfigServiceis needed withinOtpModuleitself.4. Building the API Layer (OTP Controller)
Create the controller with endpoints that clients can call. Protect these endpoints with rate limiting and authentication in production.
Generate OTP Controller:
This creates
src/otp/otp.controller.ts(and spec file) and adds it toOtpModule.Define Data Transfer Objects (DTOs): Create DTOs to define the expected request body structure and apply validation rules using
class-validator.Create
src/otp/dto/send-otp.dto.ts:About @IsPhoneNumber: This validator accepts E.164 format phone numbers (e.g., +14155552671) but has limitations with formatting variations. Service-level normalization remains necessary to handle spaces, dashes, and parentheses that users might include.
Create
src/otp/dto/verify-otp.dto.ts:Implement
OtpController: Opensrc/otp/otp.controller.ts. InjectOtpServiceand define the endpoints.Endpoint Details:
@Controller('otp'): Sets the base route for all methods in this controller to/otp@Post('send'),@Post('verify'): Define POST endpoints at/otp/sendand/otp/verify@Body(): Injects the request bodyValidationPipe: (Applied globally below) Automatically validates the incoming request body against the DTO@HttpCode(HttpStatus.OK): Ensures a200 OKstatus is returned on successProduction Security:
@nestjs/throttlerpackage)Enable
ValidationPipeGlobally (Recommended): Enable the validation pipe globally insrc/main.tsfor cleaner controllers.With the global pipe enabled, remove the
@UsePipes(...)decorators from the controller methods (as shown in the controller example above).Production Deployment Checklist:
helmetfor security headersTesting Endpoints with
curl:Start the application:
npm run start:devSend OTP: Replace
+12345678900with a real phone number (use a test number if using your test API key).Expected Response (Success):
Expected Response (Validation Error – e.g., invalid number format):
Verify OTP: Replace
some-verification-id-from-messagebirdwith the ID you received, and123456with the code sent to your phone (or the test code provided by MessageBird if using test keys).Expected Response (Success):
Expected Response (Incorrect Token):
5. Integrating with Third-Party Services (MessageBird)
Core integration points:
.env(use Test key for dev, Live key for prod) and load via@nestjs/config. Ensure the.envfile is never committed to version control (add it to.gitignore).messagebirdSDK in theOtpServiceconstructor using the API key fromConfigService. The type assertionas unknown as MessageBird.MessageBirdis included due to potential SDK typing nuances.verify.createandverify.verifymethods withinOtpServiceto interact with MessageBird.OtpServicearound the MessageBird calls, especially for transient network errors (distinguish from user errors like invalid tokens).type: 'tts'(Text-to-Speech). ModifysendOtpto offer this if SMS fails or as a preference.MessageBird Pricing Considerations:
Geographic Coverage: MessageBird supports SMS delivery to 200+ countries. Check country-specific restrictions and compliance requirements (e.g., sender ID registration, content filtering) before deploying internationally.
6. Implementing Error Handling and Logging
NestJS provides robust mechanisms for handling errors and logging.
Error Handling Strategy:
ValidationPipehandles these automatically, returning400 Bad Request.OtpServicethrows specificHttpExceptionsubclasses (BadRequestException,InternalServerErrorException) based on MessageBird API responses or internal issues. Specific MessageBird error codes (e.g., 10, 21) map toBadRequestException.500 Internal Server Error.Custom Exception Filters: Create custom exception filters to format error responses consistently or integrate with error tracking services like Sentry. Example:
Logging:
Logger(@nestjs/common) inOtpService,OtpController, andmain.ts.OtpServicelogs specific MessageBird errors.pino,nestjs-pino) for easier parsing by log aggregation toolsRetry Mechanisms (Conceptual Example): Add retries to
OtpServicecalls to improve resilience against transient network issues.Caveat: The
isTransientErrorfunction is critical and provided as a non-working placeholder. You must implement logic to correctly identify which errors (based on codes, status, or types) should trigger a retry. Retrying on user errors (like invalid number) is incorrect. The usage example withinsendOtpis commented out to avoid making the code block non-functional due to the placeholderisTransientErrorand context assumptions.Circuit Breaker Pattern: Protect your application from cascading failures when MessageBird is down. Use the circuit breaker pattern (libraries like
opossumornestjs-circuit-breaker) to temporarily stop sending requests to a failing service and provide fallback responses.7. (Optional) Creating a Database Schema and Data Layer (Prisma Example)
This section is optional and only relevant if you need to store user data or link verified numbers.
Initialize Prisma: If not done in Section 1, install Prisma dependencies and initialize:
Ensure your
.envhas the correctDATABASE_URL.Create User and Verification Schema:
Run Migrations:
Integration Example: Track verification attempts for audit trails, implement cooldown periods between OTP requests, and link verified phone numbers to user accounts:
Source Citations: