How to Send MMS Messages with NestJS and Vonage Messages API (2025)
Learn how to send MMS messages with NestJS and Vonage Messages API. Complete TypeScript tutorial covering authentication, A2P 10DLC compliance, error handling, and production deployment for Node.js developers.
Learn how to build a production-ready NestJS application that sends MMS messages using the Vonage Messages API. This comprehensive guide covers everything from initial project setup and Vonage authentication to implementing the core sending logic, building REST API endpoints, handling errors, and following security best practices for TypeScript applications.
By the end of this tutorial, you'll have a fully functional NestJS MMS API endpoint that accepts recipient phone numbers and image URLs, then uses the Vonage Node.js SDK to deliver multimedia messages programmatically. This solution enables applications to send rich media content via carrier networks – essential for notifications, marketing campaigns, two-factor authentication, and customer engagement.
Technologies You'll Use:
NestJS: A progressive Node.js framework for building efficient, reliable, and scalable server-side applications. You'll benefit from its modular architecture, dependency injection, and excellent TypeScript support.
Vonage Messages API: Enables sending messages across various channels, including MMS. You'll use the @vonage/messages SDK.
Node.js: The underlying JavaScript runtime.
TypeScript: Superset of JavaScript adding static types.
Vonage API Account: Sign up if you don't have one. You get free credit to start.
Vonage US Number: A US-based virtual phone number purchased from Vonage, capable of sending SMS & MMS. Ensure it's not a Toll-Free Number (TFN) or Short Code if you haven't completed necessary compliance processes, as A2P (Application-to-Person) MMS often requires specific registration for these number types (e.g., A2P 10DLC campaign registration). A standard US local number is often easiest to start.
Publicly Accessible Image URL: The image you want to send must be hosted at a URL accessible from the internet (e.g., cloud storage, CDN). Vonage supports specific image formats for MMS:
Supported formats:.jpg, .jpeg, .png, and .gif
Accessibility: URL must be publicly accessible without authentication
File size: Keep images under 600 KB for optimal delivery across carriers
Network restrictions: MMS via Vonage Messages API is limited to US-based numbers. You can only send from US short codes, 10DLC numbers, or SMS-enabled toll-free numbers to US recipients on AT&T, T-Mobile (including Sprint legacy network), and Verizon networks.
All phone numbers used with the Vonage Messages API must adhere to the E.164 international telephone numbering standard (ITU-T Recommendation E.164). This standardised format ensures proper routing and delivery across global telecommunications networks.
E.164 Format Specification
Format Structure:+[country code][subscriber number including area code]
Key Characteristics:
Maximum length: 15 digits (excluding the + symbol)
No spaces, hyphens, or parentheses: Only digits after the + symbol
Always starts with +: The plus sign indicates international format
Routing Accuracy: Telecommunications networks use E.164 to route messages to the correct carrier and destination
International Compatibility: Ensures your application works across different countries without modification
API Validation: Vonage validates number format before attempting delivery
Delivery Success: Improperly formatted numbers result in immediate rejection (HTTP 400 errors)
What is A2P 10DLC and Why Does It Matter for MMS?
Application-to-Person (A2P) 10DLC is a regulatory framework in the United States that governs how businesses send messages using standard 10-digit long code phone numbers. Understanding and complying with A2P 10DLC requirements is essential for reliable MMS delivery.
What is A2P 10DLC?
A2P 10DLC refers to Application-to-Person messaging using 10-digit long codes (standard phone numbers with area codes). This system was introduced by US carriers to:
Reduce spam and unwanted messages
Improve message deliverability for legitimate businesses
Provide better throughput for registered campaigns
Ensure compliance with telecommunications regulations
Match Campaign Description: Only send messages that align with your registered campaign
Monitor Throughput: Respect daily volume limits assigned to your campaign
Maintain Records: Keep consent records and message logs for compliance audits
Non-Compliant Consequences
Sending MMS without proper A2P 10DLC registration can result in:
Message filtering or blocking by carriers
Reduced delivery rates (often below 50%)
Number suspension or permanent deactivation
Potential fines from carriers
Damage to brand reputation
1. Vonage Account and Application Setup
Before writing any code, you'll configure your Vonage account and create a Vonage Application. This application links your API credentials, private key, and virtual number.
On the main dashboard page, you'll find your API key and API secret. Note these down securely.
Step 2: Purchase a US MMS-Capable Number
Navigate to Numbers > Buy numbers in the dashboard sidebar.
Search for numbers in the United States (US).
Ensure the number's Capabilities include SMS and MMS.
Purchase a suitable number. Note down this number – it will be your FROM_NUMBER.
Step 3: Create a Vonage Application
Navigate to Applications > Create a new application.
Enter an Application name (e.g., NestJS MMS Sender).
Enable the Messages capability.
Webhook URLs (Status & Inbound): For sending MMS via this guide's basic API, you don't strictly need functional webhooks immediately. However, Vonage requires URLs. You can use a placeholder like https://example.com/status and https://example.com/inbound for now. For production apps receiving delivery receipts or inbound messages, replace these with real endpoints exposed via tools like ngrok during development or your deployed application URL.
Generate Public/Private Key Pair: Click the Generate public/private key pair link. This automatically populates the public key field in the dashboard and triggers a download of a private.key file. Save this file securely – you'll need it in your NestJS project. Do not commit this file to version control.
Click Create application.
You'll be redirected to the application's detail page. Note down the Application ID.
Step 4: Link Your Number to the Application
On the application's detail page (or navigate back to Applications and click your app name), scroll down to the Linked numbers section.
Find the US MMS-capable number you purchased earlier.
Click the Link button next to it.
You now have:
Vonage API Key
Vonage API Secret
Vonage Application ID
Your Vonage MMS-capable phone number (FROM_NUMBER)
The private.key file
2. Setting Up the NestJS Project
Create a new NestJS project and install the necessary dependencies.
Step 1: Create NestJS Project
Open your terminal and run:
Bash
nest new nestjs-vonage-mms
cd nestjs-vonage-mms
Choose your preferred package manager (npm or yarn) when prompted.
Step 2: Install Dependencies
Install the Vonage Messages SDK and NestJS configuration module:
Bash
# Using npm
npm install @vonage/messages @nestjs/config class-validator class-transformer
# Using yarn
yarn add @vonage/messages @nestjs/config class-validator class-transformer
@vonage/messages: The official Vonage SDK specifically for the Messages API (including MMS).
@nestjs/config: For managing environment variables securely.
class-validator & class-transformer: For request payload validation.
Step 3: Place the Private Key
Copy the private.key file you downloaded from Vonage into the root directory of your nestjs-vonage-mms project.
Step 4: Configure Environment Variables
Create a .env file in the project root:
Bash
touch .env
Add your Vonage credentials and configuration to .env:
.env
# .env
# Vonage API Credentials & Application Info
VONAGE_API_KEY=YOUR_API_KEY
VONAGE_API_SECRET=YOUR_API_SECRET
VONAGE_APPLICATION_ID=YOUR_APPLICATION_ID
VONAGE_FROM_NUMBER=YOUR_VONAGE_NUMBER # Include country code, e.g., 14155550100
# Option 1: Path to the private key file (relative to project root)
VONAGE_PRIVATE_KEY_PATH=./private.key
# Option 2: Content of the private key (for environments like serverless/containers)
# If using this, make sure the content is properly formatted (e.g., handle newlines)
# VONAGE_PRIVATE_KEY_CONTENT="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
Replace the placeholder values with your actual credentials obtained in Section 1. Choose EITHER VONAGE_PRIVATE_KEY_PATH OR VONAGE_PRIVATE_KEY_CONTENT depending on your deployment strategy.
Important: Add .env and private.key to your .gitignore file to prevent accidentally committing sensitive information:
Text
# .gitignore (add these lines)
.env
private.key
*.env
Step 5: Set Up Configuration Module
NestJS provides a ConfigModule for easy environment variable management. Import and configure it in your main application module (src/app.module.ts):
TypeScript
// src/app.module.tsimport { Module } from'@nestjs/common';
import { AppController } from'./app.controller';
import { AppService } from'./app.service';
import { ConfigModule } from'@nestjs/config'; // Import ConfigModuleimport { MmsModule } from'./mms/mms.module'; // We'll create this nextimport { VonageModule } from'./vonage/vonage.module'; // Import VonageModule@Module({
imports: [
ConfigModule.forRoot({ // Configure globallyisGlobal: true, // Make ConfigService available everywhereenvFilePath: '.env', // Specify the env file path
}),
MmsModule, // Import our MMS feature moduleVonageModule, // Import VonageModule (needed for VonageService)
],
controllers: [AppController], // Default controller, can be removed if unusedproviders: [AppService], // Default service, can be removed if unused
})
exportclassAppModule {}
This setup loads variables from .env and makes them accessible via the ConfigService throughout the application.
3. Implementing Core Functionality: The Vonage Service
Create a dedicated service to handle interactions with the Vonage API. This promotes separation of concerns and makes the logic reusable and testable.
Step 1: Create the Vonage Module and Service
Use the NestJS CLI to generate a module and service:
Bash
nest generate module vonage
nest generate service vonage --no-spec # Skip spec file for brevity
This creates src/vonage/vonage.module.ts and src/vonage/vonage.service.ts.
Step 2: Implement the Vonage Service
Open src/vonage/vonage.service.ts and implement the logic to initialize the Vonage client and send an MMS:
TypeScript
// src/vonage/vonage.service.tsimport { Injectable, Logger, InternalServerErrorException, OnModuleInit } from'@nestjs/common';
import { ConfigService } from'@nestjs/config';
import { Messages, MMSImage } from'@vonage/messages';
import * as fs from'fs'; // Import Node.js file system module@Injectable()
exportclassVonageServiceimplementsOnModuleInit {
privatereadonly logger = newLogger(VonageService.name);
privatevonageMessages: Messages;
privatevonageFromNumber: string;
private isInitialized = false;
constructor(privateconfigService: ConfigService) {}
// Use OnModuleInit to ensure ConfigService is readyonModuleInit() {
// Retrieve credentials and config from environment variables via ConfigServiceconst apiKey = this.configService.get<string>('VONAGE_API_KEY');
const apiSecret = this.configService.get<string>('VONAGE_API_SECRET');
const applicationId = this.configService.get<string>('VONAGE_APPLICATION_ID');
const privateKeyPath = this.configService.get<string>('VONAGE_PRIVATE_KEY_PATH');
const privateKeyContent = this.configService.get<string>('VONAGE_PRIVATE_KEY_CONTENT');
this.vonageFromNumber = this.configService.get<string>('VONAGE_FROM_NUMBER');
// Validate that essential configuration is presentif (!apiKey || !apiSecret || !applicationId || !this.vonageFromNumber) {
this.logger.error('Core Vonage configuration (API Key, Secret, App ID, From Number) is incomplete. Check environment variables.');
thrownewInternalServerErrorException('Core Vonage configuration is incomplete.');
}
letprivateKey: string;
// Determine private key source: environment variable first, then file pathif (privateKeyContent) {
this.logger.log('Using private key from VONAGE_PRIVATE_KEY_CONTENT environment variable.');
// Replace escaped newlines if necessary, depending on how the env var is set
privateKey = privateKeyContent.replace(/\\n/g, '\n');
} elseif (privateKeyPath) {
this.logger.log(`Attempting to read private key from path: ${privateKeyPath}`);
try {
privateKey = fs.readFileSync(privateKeyPath, 'utf8');
} catch (error) {
this.logger.error(`Failed to read private key file at ${privateKeyPath}`, error.stack);
thrownewInternalServerErrorException(`Could not read Vonage private key file from path: ${privateKeyPath}`);
}
} else {
this.logger.error('Private key source is missing. Set either VONAGE_PRIVATE_KEY_PATH or VONAGE_PRIVATE_KEY_CONTENT environment variable.');
thrownewInternalServerErrorException('Vonage private key source not configured.');
}
// Initialize the Vonage Messages clienttry {
this.vonageMessages = newMessages({
apiKey: apiKey,
apiSecret: apiSecret,
applicationId: applicationId,
privateKey: privateKey,
});
this.isInitialized = true;
this.logger.log('VonageService initialized successfully.');
} catch (error) {
this.logger.error('Failed to initialize Vonage Messages client.', error.stack);
thrownewInternalServerErrorException('Failed to initialize Vonage client. Check credentials and private key format.');
}
}
/**
* Sends an MMS message using the Vonage Messages API.
* @paramto - The recipient's phone number (E.164 format, e.g., 14155550101).
* @paramimageUrl - The publicly accessible URL of the image to send.
* @paramcaption - Optional text caption for the image.
* @returns The message UUID on success.
* @throws InternalServerErrorException on failure or if not initialized.
*/asyncsendMms(to: string, imageUrl: string, caption?: string): Promise<string> {
if (!this.isInitialized || !this.vonageMessages) {
this.logger.error('VonageService not initialized. Cannot send MMS.');
thrownewInternalServerErrorException('Vonage service is not available.');
}
const mmsPayload = newMMSImage({
to: to,
from: this.vonageFromNumber, // Use the configured Vonage numberimage: {
url: imageUrl,
caption: caption || '', // Use provided caption or empty string
},
// Optional: client_ref for tracking// client_ref: `mms-${Date.now()}`
});
this.logger.log(`Attempting to send MMS to ${to} from ${this.vonageFromNumber}`);
try {
const response = awaitthis.vonageMessages.send(mmsPayload);
this.logger.log(`MMS sent successfully to ${to}. Message UUID: ${response.message_uuid}`);
return response.message_uuid;
} catch (error) {
// Log the detailed error from the Vonage SDKthis.logger.error(`Failed to send MMS to ${to}: ${error?.message || 'Unknown error'}`, error?.response?.data || error.stack);
// Throw a generic server error to the caller (API layer)// You might want more specific error handling based on Vonage error codesthrownewInternalServerErrorException(`Failed to send MMS via Vonage.`);
}
}
}
Explanation:
Dependencies & Lifecycle: Imports necessary modules and implements OnModuleInit to ensure configuration is loaded before initialization logic runs.
Logger & State: Uses NestJS Logger and adds an isInitialized flag.
Constructor: Only injects ConfigService. Initialization logic moved to onModuleInit.
onModuleInit:
Retrieves all necessary Vonage credentials and configuration options (apiKey, apiSecret, applicationId, fromNumber, privateKeyPath, privateKeyContent).
Validates core credentials.
Private Key Handling: Checks for VONAGE_PRIVATE_KEY_CONTENT first. If present, uses its value (handling potential escaped newlines). If not present, it checks for VONAGE_PRIVATE_KEY_PATH and attempts to read the file using fs.readFileSync. Throws an error if neither is configured or if file reading fails.
Initializes the Messages client within a try...catch block for robustness.
Sets isInitialized to true on success.
sendMms Method:
Adds a check at the beginning to ensure the service was initialized successfully before attempting to send.
Creates the MMSImage payload as before.
Uses this.vonageMessages.send() within a try...catch block.
Logs success/failure and returns the message_uuid or throws InternalServerErrorException.
Step 3: Register the Service
Make sure VonageService is provided and exported by VonageModule.
Open src/vonage/vonage.module.ts:
TypeScript
// src/vonage/vonage.module.tsimport { Module } from'@nestjs/common';
import { VonageService } from'./vonage.service';
// ConfigModule is already global, no need to import here unless scoping config@Module({
providers: [VonageService],
exports: [VonageService], // Export VonageService so other modules can use it
})
exportclassVonageModule {}
4. Building the API Layer
Now, let's create an API endpoint that uses our VonageService to trigger sending an MMS.
Step 1: Create the MMS Module, Controller, and DTO
Step 2: Define the Request DTO (Data Transfer Object)
Create a DTO to define the expected shape and validation rules for the incoming request body.
Open src/mms/dto/send-mms.dto.ts:
TypeScript
// src/mms/dto/send-mms.dto.tsimport { IsString, IsNotEmpty, IsPhoneNumber, IsUrl, IsOptional } from'class-validator';
exportclassSendMmsDto {
@IsNotEmpty()
@IsPhoneNumber(null) // Use null for basic E.164 format check, specify region code if needed e.g. 'US'@IsString()
readonlyto: string; // e.g., ""14155550101""@IsNotEmpty()
@IsUrl({ require_protocol: true, protocols: ['http', 'https'] }) // Ensure it's a valid http/https URL@IsString()
readonlyimageUrl: string;
@IsOptional() // Caption is not required@IsString()
readonlycaption?: string;
}
Explanation:
Uses decorators from class-validator to enforce rules:
@IsNotEmpty(): Field cannot be empty.
@IsString(): Field must be a string.
@IsPhoneNumber(null): Validates if the string is a phone number (basic E.164 check). Use region codes like 'US' for stricter validation if necessary.
@IsUrl(): Validates if the string is a valid URL (requiring http/https).
@IsOptional(): Allows the caption field to be omitted.
Step 3: Implement the MMS Controller
Open src/mms/mms.controller.ts and define the API endpoint:
TypeScript
// src/mms/mms.controller.tsimport { Controller, Post, Body, ValidationPipe, UsePipes, Logger, HttpCode, HttpStatus } from'@nestjs/common';
import { VonageService } from'../vonage/vonage.service'; // Correct pathimport { SendMmsDto } from'./dto/send-mms.dto';
@Controller('mms') // Route prefix: /mmsexportclassMmsController {
privatereadonly logger = newLogger(MmsController.name);
constructor(privatereadonlyvonageService: VonageService) {} // Inject VonageService@Post('send') // Route: POST /mms/send@UsePipes(newValidationPipe({ transform: true, whitelist: true })) // Enable validation@HttpCode(HttpStatus.ACCEPTED) // Return 202 Accepted for async operationsasyncsendMms(@Body() sendMmsDto: SendMmsDto): Promise<{ message: string; messageId?: string }> {
this.logger.log(`Received request to send MMS: ${JSON.stringify(sendMmsDto)}`);
try {
const messageId = awaitthis.vonageService.sendMms(
sendMmsDto.to,
sendMmsDto.imageUrl,
sendMmsDto.caption,
);
// Return a success response including the message IDreturn {
message: 'MMS send request accepted.',
messageId: messageId
};
} catch (error) {
// Errors are caught and logged in VonageService, re-thrown as InternalServerErrorException// NestJS default exception filter will handle thisthis.logger.error(`Error in sendMms endpoint: ${error.message}`, error.stack);
// Let the default exception filter handle the response (usually 500)throw error;
}
}
}
Explanation:
Dependencies: Imports necessary NestJS modules, the VonageService, and the SendMmsDto.
Controller Decorator:@Controller('mms') sets the base route for all methods in this controller to /mms.
Logger: Standard NestJS logger.
Constructor: Injects VonageService so the controller can use its methods.
sendMms Method:
@Post('send'): Defines this method to handle POST requests to /mms/send.
@UsePipes(new ValidationPipe(...)): Automatically validates the incoming request body (@Body()) against the rules defined in SendMmsDto.
transform: true: Attempts to transform the payload to the DTO type.
whitelist: true: Strips any properties not defined in the DTO.
@HttpCode(HttpStatus.ACCEPTED): Sets the default success HTTP status code to 202 Accepted, which is suitable for operations that are initiated but may complete asynchronously.
@Body() sendMmsDto: SendMmsDto: Injects the validated request body into the sendMmsDto parameter.
Calls this.vonageService.sendMms with the validated data.
Returns a success message including the Vonage message_uuid.
Includes a try...catch block, primarily for logging at the controller level. The actual error handling (catching Vonage errors, throwing InternalServerErrorException) happens in VonageService. NestJS's built-in exception filter catches this and sends an appropriate HTTP 500 response.
Step 4: Import VonageModule into MmsModule
To make VonageService available for injection in MmsController, import VonageModule into MmsModule.
Open src/mms/mms.module.ts:
TypeScript
// src/mms/mms.module.tsimport { Module } from'@nestjs/common';
import { MmsController } from'./mms.controller';
import { VonageModule } from'../vonage/vonage.module'; // Import VonageModule@Module({
imports: [VonageModule], // Make VonageService availablecontrollers: [MmsController],
providers: [], // No providers specific to this module needed
})
exportclassMmsModule {}
5. Integrating with Vonage (Recap)
The core integration happens within VonageService:
Configuration:ConfigModule reads API Key, Secret, App ID, From Number, and Private Key source (Path or Content) from environment variables (.env file or system environment).
Authentication:VonageService reads the private key content (either directly from an environment variable or from the file specified by the path variable) during initialization (onModuleInit) and initializes the Messages SDK client. The SDK handles the JWT generation internally using your API Key, Secret, App ID, and the provided private key content for authenticated requests to the Vonage Messages API.
API Call:VonageService.sendMms constructs the MMSImage payload and uses the initialized messagesClient.send() method to make the API request to Vonage.
Secrets: API credentials and the private key (path or content) are stored securely in environment variables. Ensure .env files or private.key files are never committed to Git. Use platform-level secrets management for production.
6. Error Handling, Logging, and Retries
Logging: We use NestJS's built-in Logger in both the controller and service layers to provide context about requests and potential issues. Critical errors (config issues, API failures) are logged with stack traces where available.
Error Handling Strategy:
Configuration errors during startup (onModuleInit in VonageService) throw InternalServerErrorException immediately, preventing the app from starting in a broken state.
API call errors within VonageService.sendMms are caught, logged with details from the Vonage SDK error response (error?.response?.data), and then re-thrown as a generic InternalServerErrorException.
NestJS's default exception filter catches unhandled exceptions (like our InternalServerErrorException) and automatically sends a standardized JSON error response with an appropriate HTTP status code (usually 500).
For more granular control, you could implement a custom Exception Filter to map specific Vonage error codes (if identifiable from error?.response?.data) to different HTTP statuses or response formats.
Retry Mechanisms: This guide doesn't implement automatic retries for sending MMS. For simple API-triggered sends, retrying immediately might not be desirable (could lead to duplicate messages if the first request actually succeeded but the response failed).
Consideration: If sending MMS is part of a larger, critical workflow, you might implement a background job queue (e.g., BullMQ) with retry logic (exponential backoff) for failed sends. This decouples the API request from the actual Vonage interaction.
Vonage Dispatch API: For guaranteed delivery across channels or fallback scenarios (e.g., send SMS if MMS fails), explore the Vonage Dispatch API.
7. Adding Security Features
Input Validation: Implemented using class-validator decorators in SendMmsDto and enforced by ValidationPipe in the controller. This prevents malformed requests and potential injection issues (e.g., ensuring imageUrl is a valid URL).
Secrets Management: Handled via environment variables and ConfigModule. Never hardcode credentials. Ensure proper permissions on the private.key file if used in deployment. Prefer injecting key content via environment variables in production.
Rate Limiting: Protect your API from abuse. Use a module like @nestjs/throttler:
API Authentication/Authorization: The current endpoint is open. In production, you'd protect it:
API Keys: Implement a custom Guard that checks for a valid X-API-Key header.
JWT/OAuth: For user-based actions, implement standard authentication flows using @nestjs/passport or similar.
HELMET: Use the helmet middleware for basic security headers (via app.use(helmet()) in main.ts).
Bash
npm install helmet
TypeScript
// src/main.tsimport { NestFactory } from'@nestjs/core';
import { AppModule } from'./app.module';
import { ValidationPipe } from'@nestjs/common'; // Import ValidationPipe if using globallyimport helmet from'helmet';
asyncfunctionbootstrap() {
const app = awaitNestFactory.create(AppModule);
app.use(helmet()); // Add Helmet middleware// Optionally apply ValidationPipe globally instead of per-endpoint// app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true }));await app.listen(process.env.PORT || 3000); // Use PORT env var if available
}
bootstrap();
8. Handling Special Cases
Phone Number Formatting: Vonage expects E.164 format (e.g., 14155550101). The @IsPhoneNumber() validator provides basic checks. Ensure your input data adheres to this.
Image URL Accessibility: The imageUrlmust be publicly accessible without authentication. Vonage servers need to fetch this image. Private or localhost URLs will fail.
Supported Image Types: Vonage Messages API currently supports .jpg, .jpeg, and .png for MMS. Sending other types will likely result in an error. Add validation if needed.
Character Limits: While MMS supports longer text than SMS, captions might still have practical limits imposed by carriers or devices. Keep captions reasonably concise.
US-Only Limitation (Vonage Platform): Vonage MMS via the Messages API is typically limited to sending from US numbers to US numbers for A2P (Application-to-Person) traffic. Sending to international numbers or between virtual numbers might not work or require different Vonage products/configurations.
9. Performance Optimizations
For this specific use case (sending a single MMS via API):
Connection Pooling: The Vonage SDK likely manages underlying HTTP connections efficiently. No specific action needed here.
Asynchronous Operations: The entire flow (controller -> service -> Vonage SDK) is asynchronous (async/await), preventing the main Node.js thread from being blocked during the external API call.
Payload Size: Keep image sizes reasonable to reduce fetch time for Vonage and delivery time/cost. Captions are generally small.
Caching: Caching is unlikely to be beneficial for sending unique MMS messages. It might apply if you frequently send the same image, but the primary bottleneck is the external API call and carrier delivery.
10. Monitoring, Observability, and Analytics
Logging: Already implemented using NestJS Logger. Ensure logs are aggregated in production (e.g., CloudWatch, Datadog, ELK stack). Log levels (Info, Error, Warn) help filter noise.
Health Checks: Implement a basic health check endpoint (e.g., /health) using @nestjs/terminus that returns 200 OK if the application is running.
Performance Metrics: Monitor standard Node.js/NestJS metrics: request latency, request rate, error rate, CPU/Memory usage using tools like Prometheus/Grafana, Datadog APM, etc.
Vonage API Monitoring: Monitor the Vonage API status page for outages. Check your Vonage Dashboard for message logs, delivery status (requires webhooks), and billing/usage information.
Error Tracking: Integrate services like Sentry or Datadog Error Tracking to capture, aggregate, and alert on runtime exceptions.
11. Troubleshooting Common MMS Delivery Issues
401 Unauthorized Error:
What went wrong: Your API credentials are incorrect, incomplete, or mismatched with your Vonage Application. This error indicates Vonage cannot authenticate your request.
How to fix it:
Verify all credentials in your .env file match exactly what's shown in your Vonage Dashboard under your Application details
Confirm your privateKey content is correctly formatted. If using VONAGE_PRIVATE_KEY_CONTENT, ensure newlines are properly escaped (\n)
Check that your FROM_NUMBER is linked to your VONAGE_APPLICATION_ID in the dashboard under Applications > Linked numbers
Verify your number type supports MMS and is configured correctly
Ensure you're using credentials generated for the Messages API Application (not the legacy SMS API credentials)
Could not read Vonage private key file... Error:
What went wrong: Your application cannot find or access the private.key file at the specified path.
How to fix it:
Verify the path in VONAGE_PRIVATE_KEY_PATH is correct relative to where your application runs (usually the project root)
Confirm private.key exists in the specified location by running ls -la private.key
Check file permissions with ls -l private.key. If needed, set proper permissions: chmod 600 private.key
For containerized or serverless environments, use VONAGE_PRIVATE_KEY_CONTENT instead of file paths
Vonage private key source not configured Error:
What went wrong: Neither VONAGE_PRIVATE_KEY_PATH nor VONAGE_PRIVATE_KEY_CONTENT environment variables are set.
How to fix it: Set one of these environment variables in your .env file with the appropriate value. For local development, use VONAGE_PRIVATE_KEY_PATH=./private.key. For production deployments, use VONAGE_PRIVATE_KEY_CONTENT with the full private key content.
MMS Not Sending / Delivery Issues:
What went wrong: Your MMS message failed to send or deliver. This can have multiple causes.
How to fix it:
Validate phone format: Ensure TO_NUMBER is in E.164 format without the + symbol (e.g., 14155552671, not +1-415-555-2671)
Test image accessibility: Open your imageUrl in a browser's incognito mode. If it doesn't load, Vonage cannot fetch it
Verify image format: Confirm your image is .jpg, .jpeg, .png, or .gif. Keep file size under 600 KB
Check Vonage dashboard: Log into your dashboard and navigate to API Dashboard > Messages to view detailed error messages for your message UUID
Verify number capabilities: Confirm your FROM_NUMBER supports MMS and is properly linked in the Vonage dashboard
Confirm US-to-US routing: MMS via Vonage Messages API only works from US numbers to US recipients on AT&T, T-Mobile, or Verizon
Review A2P 10DLC compliance: If using a 10DLC number, ensure you've completed brand and campaign registration. Check your registration status in the Vonage dashboard under Numbers > 10DLC
Frequently asked questions
Use the Vonage Messages API and NestJS framework. This involves setting up a Vonage application, installing the necessary SDK, and creating a service to handle MMS sending logic within your NestJS project. This approach makes your MMS sending capability robust, maintainable, and scalable within the NestJS structure.
The Vonage Messages API is a service that allows you to send various types of messages, including MMS, programmatically. It's used with the Vonage SDK and provides methods for sending messages with rich media content such as images.
NestJS provides a structured and scalable framework for building server-side applications. Its features, including dependency injection and TypeScript support, help create maintainable, testable, and robust code for sending MMS messages with Vonage.
Consider using the Vonage Dispatch API when you need guaranteed delivery across multiple channels or fallback mechanisms. For instance, if an MMS fails to send, you can configure the Dispatch API to send an SMS instead, enhancing the reliability of your messaging system.
MMS messaging with Vonage Messages API, as covered in this guide, is primarily designed for US to US communications using a US-based Vonage Virtual Number. International MMS or sending between virtual numbers may have limitations or require different Vonage product configurations.
The Vonage Messages API supports JPG, JPEG, and PNG image formats for MMS messages. Ensure that your image URLs point to files of these supported types to avoid potential errors or delivery issues.
Implement a try-catch block in your Vonage service to handle potential errors during the API call. Log the error details using a logger service and consider re-throwing the error as a generic InternalServerErrorException for simplified error handling at the controller level.
Store your Vonage private key securely, never committing it to version control. Two options are provided: store the key in a .env file using the VONAGE_PRIVATE_KEY_PATH environment variable (ideal for local development), or directly within an environment variable VONAGE_PRIVATE_KEY_CONTENT for serverless or container environments.
Format all phone numbers using the E.164 format. This international standard ensures correct delivery of your MMS messages and can be checked in your application using validation libraries like class-validator with its IsPhoneNumber decorator.
The Vonage Application ID links your API credentials, private key, and virtual number to a specific application, enabling the Vonage platform to manage access and usage for MMS messaging. You can find this in the Vonage dashboard.
Secure your endpoint by using environment variables for sensitive information, validating incoming requests, and implementing rate limiting with @nestjs/throttler to prevent abuse. Also, consider adding API authentication (e.g., JWT) if not already in place.
Several factors can cause MMS failures, including incorrectly formatted phone numbers, inaccessible image URLs, or issues with the Vonage number configuration. Review the troubleshooting section of the documentation and check the Vonage dashboard for specific error messages.
For critical workflows, consider a background job queue with retry logic using exponential backoff to handle transient failures. This separates the API request from the Vonage interaction and improves reliability.
The guide doesn't specify default Vonage rate limits. Contact Vonage support or check the API documentation for specific limits. Implement rate limiting in your own application using @nestjs/throttler to prevent exceeding Vonage's limits and protect against abuse.
Learn how to build a production-ready NestJS application that sends MMS messages using the Vonage Messages API. This comprehensive guide covers everything from initial project setup and Vonage authentication to implementing the core sending logic, building REST API endpoints, handling errors, and following security best practices for TypeScript applications.
By the end of this tutorial, you'll have a fully functional NestJS MMS API endpoint that accepts recipient phone numbers and image URLs, then uses the Vonage Node.js SDK to deliver multimedia messages programmatically. This solution enables applications to send rich media content via carrier networks – essential for notifications, marketing campaigns, two-factor authentication, and customer engagement.
Technologies You'll Use:
@vonage/messagesSDK.System Architecture Diagram:
Prerequisites
node -v)npm install -g @nestjs/cli)..jpg,.jpeg,.png, and.gifWhat is E.164 Phone Number Format?
All phone numbers used with the Vonage Messages API must adhere to the E.164 international telephone numbering standard (ITU-T Recommendation E.164). This standardised format ensures proper routing and delivery across global telecommunications networks.
E.164 Format Specification
Format Structure:
+[country code][subscriber number including area code]Key Characteristics:
+symbol)+symbol+: The plus sign indicates international formatReference: ITU-T Recommendation E.164 - The International Public Telecommunication Numbering Plan
Learn more about E.164 phone number format and international dialing standards.
E.164 Format Examples
Vonage-Specific Format Note
Important: When using Vonage APIs, omit the leading
+symbol in your code. Vonage expects numbers in E.164 format but without the plus sign.Correct formats for Vonage:
14155552671(US number without+)442071838750(UK number without+)+14155552671(includes+, may cause errors)(415) 555-2671(contains formatting characters)415-555-2671(missing country code)Reference: Vonage Messages API - Send an MMS
Why E.164 Format Matters
What is A2P 10DLC and Why Does It Matter for MMS?
Application-to-Person (A2P) 10DLC is a regulatory framework in the United States that governs how businesses send messages using standard 10-digit long code phone numbers. Understanding and complying with A2P 10DLC requirements is essential for reliable MMS delivery.
What is A2P 10DLC?
A2P 10DLC refers to Application-to-Person messaging using 10-digit long codes (standard phone numbers with area codes). This system was introduced by US carriers to:
Reference: Vonage 10DLC Overview
Learn more about A2P 10DLC registration requirements and carrier compliance.
Registration Requirements
To send MMS using 10DLC numbers, you must complete the following registration process:
Brand Registration:
Campaign Registration:
Number Association:
Supported Number Types for MMS
According to Vonage documentation, only the following US number types support MMS via the Messages API:
Important: Unregistered 10DLC numbers sending A2P traffic may experience:
Reference: Vonage Messages API - Send an MMS
Carrier Network Coverage
MMS messages via Vonage can be delivered to the following US carrier networks:
Note: MMS delivery to regional carriers and MVNOs depends on their upstream carrier relationships.
Compliance Best Practices
Non-Compliant Consequences
Sending MMS without proper A2P 10DLC registration can result in:
1. Vonage Account and Application Setup
Before writing any code, you'll configure your Vonage account and create a Vonage Application. This application links your API credentials, private key, and virtual number.
Step 1: Obtain API Credentials
Step 2: Purchase a US MMS-Capable Number
FROM_NUMBER.Step 3: Create a Vonage Application
NestJS MMS Sender).https://example.com/statusandhttps://example.com/inboundfor now. For production apps receiving delivery receipts or inbound messages, replace these with real endpoints exposed via tools like ngrok during development or your deployed application URL.private.keyfile. Save this file securely – you'll need it in your NestJS project. Do not commit this file to version control.Step 4: Link Your Number to the Application
You now have:
FROM_NUMBER)private.keyfile2. Setting Up the NestJS Project
Create a new NestJS project and install the necessary dependencies.
Step 1: Create NestJS Project
Open your terminal and run:
nest new nestjs-vonage-mms cd nestjs-vonage-mmsChoose your preferred package manager (npm or yarn) when prompted.
Step 2: Install Dependencies
Install the Vonage Messages SDK and NestJS configuration module:
# Using npm npm install @vonage/messages @nestjs/config class-validator class-transformer # Using yarn yarn add @vonage/messages @nestjs/config class-validator class-transformer@vonage/messages: The official Vonage SDK specifically for the Messages API (including MMS).@nestjs/config: For managing environment variables securely.class-validator&class-transformer: For request payload validation.Step 3: Place the Private Key
Copy the
private.keyfile you downloaded from Vonage into the root directory of yournestjs-vonage-mmsproject.Step 4: Configure Environment Variables
Create a
.envfile in the project root:touch .envAdd your Vonage credentials and configuration to
.env:Replace the placeholder values with your actual credentials obtained in Section 1. Choose EITHER
VONAGE_PRIVATE_KEY_PATHORVONAGE_PRIVATE_KEY_CONTENTdepending on your deployment strategy.Important: Add
.envandprivate.keyto your.gitignorefile to prevent accidentally committing sensitive information:Step 5: Set Up Configuration Module
NestJS provides a
ConfigModulefor easy environment variable management. Import and configure it in your main application module (src/app.module.ts):// src/app.module.ts import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { ConfigModule } from '@nestjs/config'; // Import ConfigModule import { MmsModule } from './mms/mms.module'; // We'll create this next import { VonageModule } from './vonage/vonage.module'; // Import VonageModule @Module({ imports: [ ConfigModule.forRoot({ // Configure globally isGlobal: true, // Make ConfigService available everywhere envFilePath: '.env', // Specify the env file path }), MmsModule, // Import our MMS feature module VonageModule, // Import VonageModule (needed for VonageService) ], controllers: [AppController], // Default controller, can be removed if unused providers: [AppService], // Default service, can be removed if unused }) export class AppModule {}This setup loads variables from
.envand makes them accessible via theConfigServicethroughout the application.3. Implementing Core Functionality: The Vonage Service
Create a dedicated service to handle interactions with the Vonage API. This promotes separation of concerns and makes the logic reusable and testable.
Step 1: Create the Vonage Module and Service
Use the NestJS CLI to generate a module and service:
nest generate module vonage nest generate service vonage --no-spec # Skip spec file for brevityThis creates
src/vonage/vonage.module.tsandsrc/vonage/vonage.service.ts.Step 2: Implement the Vonage Service
Open
src/vonage/vonage.service.tsand implement the logic to initialize the Vonage client and send an MMS:// src/vonage/vonage.service.ts import { Injectable, Logger, InternalServerErrorException, OnModuleInit } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { Messages, MMSImage } from '@vonage/messages'; import * as fs from 'fs'; // Import Node.js file system module @Injectable() export class VonageService implements OnModuleInit { private readonly logger = new Logger(VonageService.name); private vonageMessages: Messages; private vonageFromNumber: string; private isInitialized = false; constructor(private configService: ConfigService) {} // Use OnModuleInit to ensure ConfigService is ready onModuleInit() { // Retrieve credentials and config from environment variables via ConfigService const apiKey = this.configService.get<string>('VONAGE_API_KEY'); const apiSecret = this.configService.get<string>('VONAGE_API_SECRET'); const applicationId = this.configService.get<string>('VONAGE_APPLICATION_ID'); const privateKeyPath = this.configService.get<string>('VONAGE_PRIVATE_KEY_PATH'); const privateKeyContent = this.configService.get<string>('VONAGE_PRIVATE_KEY_CONTENT'); this.vonageFromNumber = this.configService.get<string>('VONAGE_FROM_NUMBER'); // Validate that essential configuration is present if (!apiKey || !apiSecret || !applicationId || !this.vonageFromNumber) { this.logger.error('Core Vonage configuration (API Key, Secret, App ID, From Number) is incomplete. Check environment variables.'); throw new InternalServerErrorException('Core Vonage configuration is incomplete.'); } let privateKey: string; // Determine private key source: environment variable first, then file path if (privateKeyContent) { this.logger.log('Using private key from VONAGE_PRIVATE_KEY_CONTENT environment variable.'); // Replace escaped newlines if necessary, depending on how the env var is set privateKey = privateKeyContent.replace(/\\n/g, '\n'); } else if (privateKeyPath) { this.logger.log(`Attempting to read private key from path: ${privateKeyPath}`); try { privateKey = fs.readFileSync(privateKeyPath, 'utf8'); } catch (error) { this.logger.error(`Failed to read private key file at ${privateKeyPath}`, error.stack); throw new InternalServerErrorException(`Could not read Vonage private key file from path: ${privateKeyPath}`); } } else { this.logger.error('Private key source is missing. Set either VONAGE_PRIVATE_KEY_PATH or VONAGE_PRIVATE_KEY_CONTENT environment variable.'); throw new InternalServerErrorException('Vonage private key source not configured.'); } // Initialize the Vonage Messages client try { this.vonageMessages = new Messages({ apiKey: apiKey, apiSecret: apiSecret, applicationId: applicationId, privateKey: privateKey, }); this.isInitialized = true; this.logger.log('VonageService initialized successfully.'); } catch (error) { this.logger.error('Failed to initialize Vonage Messages client.', error.stack); throw new InternalServerErrorException('Failed to initialize Vonage client. Check credentials and private key format.'); } } /** * Sends an MMS message using the Vonage Messages API. * @param to - The recipient's phone number (E.164 format, e.g., 14155550101). * @param imageUrl - The publicly accessible URL of the image to send. * @param caption - Optional text caption for the image. * @returns The message UUID on success. * @throws InternalServerErrorException on failure or if not initialized. */ async sendMms(to: string, imageUrl: string, caption?: string): Promise<string> { if (!this.isInitialized || !this.vonageMessages) { this.logger.error('VonageService not initialized. Cannot send MMS.'); throw new InternalServerErrorException('Vonage service is not available.'); } const mmsPayload = new MMSImage({ to: to, from: this.vonageFromNumber, // Use the configured Vonage number image: { url: imageUrl, caption: caption || '', // Use provided caption or empty string }, // Optional: client_ref for tracking // client_ref: `mms-${Date.now()}` }); this.logger.log(`Attempting to send MMS to ${to} from ${this.vonageFromNumber}`); try { const response = await this.vonageMessages.send(mmsPayload); this.logger.log(`MMS sent successfully to ${to}. Message UUID: ${response.message_uuid}`); return response.message_uuid; } catch (error) { // Log the detailed error from the Vonage SDK this.logger.error(`Failed to send MMS to ${to}: ${error?.message || 'Unknown error'}`, error?.response?.data || error.stack); // Throw a generic server error to the caller (API layer) // You might want more specific error handling based on Vonage error codes throw new InternalServerErrorException(`Failed to send MMS via Vonage.`); } } }Explanation:
OnModuleInitto ensure configuration is loaded before initialization logic runs.Loggerand adds anisInitializedflag.ConfigService. Initialization logic moved toonModuleInit.onModuleInit:apiKey,apiSecret,applicationId,fromNumber,privateKeyPath,privateKeyContent).VONAGE_PRIVATE_KEY_CONTENTfirst. If present, uses its value (handling potential escaped newlines). If not present, it checks forVONAGE_PRIVATE_KEY_PATHand attempts to read the file usingfs.readFileSync. Throws an error if neither is configured or if file reading fails.Messagesclient within atry...catchblock for robustness.isInitializedto true on success.sendMmsMethod:MMSImagepayload as before.this.vonageMessages.send()within atry...catchblock.message_uuidor throwsInternalServerErrorException.Step 3: Register the Service
Make sure
VonageServiceis provided and exported byVonageModule.Open
src/vonage/vonage.module.ts:// src/vonage/vonage.module.ts import { Module } from '@nestjs/common'; import { VonageService } from './vonage.service'; // ConfigModule is already global, no need to import here unless scoping config @Module({ providers: [VonageService], exports: [VonageService], // Export VonageService so other modules can use it }) export class VonageModule {}4. Building the API Layer
Now, let's create an API endpoint that uses our
VonageServiceto trigger sending an MMS.Step 1: Create the MMS Module, Controller, and DTO
nest generate module mms nest generate controller mms --no-spec # No CLI command for DTOs, create manually mkdir -p src/mms/dto touch src/mms/dto/send-mms.dto.tsStep 2: Define the Request DTO (Data Transfer Object)
Create a DTO to define the expected shape and validation rules for the incoming request body.
Open
src/mms/dto/send-mms.dto.ts:// src/mms/dto/send-mms.dto.ts import { IsString, IsNotEmpty, IsPhoneNumber, IsUrl, IsOptional } from 'class-validator'; export class SendMmsDto { @IsNotEmpty() @IsPhoneNumber(null) // Use null for basic E.164 format check, specify region code if needed e.g. 'US' @IsString() readonly to: string; // e.g., ""14155550101"" @IsNotEmpty() @IsUrl({ require_protocol: true, protocols: ['http', 'https'] }) // Ensure it's a valid http/https URL @IsString() readonly imageUrl: string; @IsOptional() // Caption is not required @IsString() readonly caption?: string; }Explanation:
class-validatorto enforce rules:@IsNotEmpty(): Field cannot be empty.@IsString(): Field must be a string.@IsPhoneNumber(null): Validates if the string is a phone number (basic E.164 check). Use region codes like'US'for stricter validation if necessary.@IsUrl(): Validates if the string is a valid URL (requiring http/https).@IsOptional(): Allows thecaptionfield to be omitted.Step 3: Implement the MMS Controller
Open
src/mms/mms.controller.tsand define the API endpoint:// src/mms/mms.controller.ts import { Controller, Post, Body, ValidationPipe, UsePipes, Logger, HttpCode, HttpStatus } from '@nestjs/common'; import { VonageService } from '../vonage/vonage.service'; // Correct path import { SendMmsDto } from './dto/send-mms.dto'; @Controller('mms') // Route prefix: /mms export class MmsController { private readonly logger = new Logger(MmsController.name); constructor(private readonly vonageService: VonageService) {} // Inject VonageService @Post('send') // Route: POST /mms/send @UsePipes(new ValidationPipe({ transform: true, whitelist: true })) // Enable validation @HttpCode(HttpStatus.ACCEPTED) // Return 202 Accepted for async operations async sendMms(@Body() sendMmsDto: SendMmsDto): Promise<{ message: string; messageId?: string }> { this.logger.log(`Received request to send MMS: ${JSON.stringify(sendMmsDto)}`); try { const messageId = await this.vonageService.sendMms( sendMmsDto.to, sendMmsDto.imageUrl, sendMmsDto.caption, ); // Return a success response including the message ID return { message: 'MMS send request accepted.', messageId: messageId }; } catch (error) { // Errors are caught and logged in VonageService, re-thrown as InternalServerErrorException // NestJS default exception filter will handle this this.logger.error(`Error in sendMms endpoint: ${error.message}`, error.stack); // Let the default exception filter handle the response (usually 500) throw error; } } }Explanation:
VonageService, and theSendMmsDto.@Controller('mms')sets the base route for all methods in this controller to/mms.VonageServiceso the controller can use its methods.sendMmsMethod:@Post('send'): Defines this method to handle POST requests to/mms/send.@UsePipes(new ValidationPipe(...)): Automatically validates the incoming request body (@Body()) against the rules defined inSendMmsDto.transform: true: Attempts to transform the payload to the DTO type.whitelist: true: Strips any properties not defined in the DTO.@HttpCode(HttpStatus.ACCEPTED): Sets the default success HTTP status code to 202 Accepted, which is suitable for operations that are initiated but may complete asynchronously.@Body() sendMmsDto: SendMmsDto: Injects the validated request body into thesendMmsDtoparameter.this.vonageService.sendMmswith the validated data.message_uuid.try...catchblock, primarily for logging at the controller level. The actual error handling (catching Vonage errors, throwingInternalServerErrorException) happens inVonageService. NestJS's built-in exception filter catches this and sends an appropriate HTTP 500 response.Step 4: Import VonageModule into MmsModule
To make
VonageServiceavailable for injection inMmsController, importVonageModuleintoMmsModule.Open
src/mms/mms.module.ts:// src/mms/mms.module.ts import { Module } from '@nestjs/common'; import { MmsController } from './mms.controller'; import { VonageModule } from '../vonage/vonage.module'; // Import VonageModule @Module({ imports: [VonageModule], // Make VonageService available controllers: [MmsController], providers: [], // No providers specific to this module needed }) export class MmsModule {}5. Integrating with Vonage (Recap)
The core integration happens within
VonageService:ConfigModulereads API Key, Secret, App ID, From Number, and Private Key source (Path or Content) from environment variables (.envfile or system environment).VonageServicereads the private key content (either directly from an environment variable or from the file specified by the path variable) during initialization (onModuleInit) and initializes theMessagesSDK client. The SDK handles the JWT generation internally using your API Key, Secret, App ID, and the provided private key content for authenticated requests to the Vonage Messages API.VonageService.sendMmsconstructs theMMSImagepayload and uses the initializedmessagesClient.send()method to make the API request to Vonage..envfiles orprivate.keyfiles are never committed to Git. Use platform-level secrets management for production.6. Error Handling, Logging, and Retries
Loggerin both the controller and service layers to provide context about requests and potential issues. Critical errors (config issues, API failures) are logged with stack traces where available.onModuleInitinVonageService) throwInternalServerErrorExceptionimmediately, preventing the app from starting in a broken state.VonageService.sendMmsare caught, logged with details from the Vonage SDK error response (error?.response?.data), and then re-thrown as a genericInternalServerErrorException.InternalServerErrorException) and automatically sends a standardized JSON error response with an appropriate HTTP status code (usually 500).error?.response?.data) to different HTTP statuses or response formats.7. Adding Security Features
class-validatordecorators inSendMmsDtoand enforced byValidationPipein the controller. This prevents malformed requests and potential injection issues (e.g., ensuringimageUrlis a valid URL).ConfigModule. Never hardcode credentials. Ensure proper permissions on theprivate.keyfile if used in deployment. Prefer injecting key content via environment variables in production.@nestjs/throttler:app.module.ts:// src/app.module.ts import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { ConfigModule } from '@nestjs/config'; import { MmsModule } from './mms/mms.module'; import { VonageModule } from './vonage/vonage.module'; import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler'; import { APP_GUARD } from '@nestjs/core'; // Import APP_GUARD @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true, envFilePath: '.env', }), ThrottlerModule.forRoot([{ // Basic rate limiting: 10 requests per 60 seconds ttl: 60000, // Time-to-live in milliseconds (60 seconds) limit: 10, // Max requests per TTL per IP }]), MmsModule, VonageModule, ], controllers: [AppController], providers: [ AppService, { // Apply ThrottlerGuard globally provide: APP_GUARD, useClass: ThrottlerGuard, }, ], }) export class AppModule {}X-API-Keyheader.@nestjs/passportor similar.helmetmiddleware for basic security headers (viaapp.use(helmet())inmain.ts).// src/main.ts import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; // Import ValidationPipe if using globally import helmet from 'helmet'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.use(helmet()); // Add Helmet middleware // Optionally apply ValidationPipe globally instead of per-endpoint // app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true })); await app.listen(process.env.PORT || 3000); // Use PORT env var if available } bootstrap();8. Handling Special Cases
14155550101). The@IsPhoneNumber()validator provides basic checks. Ensure your input data adheres to this.imageUrlmust be publicly accessible without authentication. Vonage servers need to fetch this image. Private or localhost URLs will fail..jpg,.jpeg, and.pngfor MMS. Sending other types will likely result in an error. Add validation if needed.9. Performance Optimizations
For this specific use case (sending a single MMS via API):
controller->service->Vonage SDK) is asynchronous (async/await), preventing the main Node.js thread from being blocked during the external API call.10. Monitoring, Observability, and Analytics
Logger. Ensure logs are aggregated in production (e.g., CloudWatch, Datadog, ELK stack). Log levels (Info, Error, Warn) help filter noise./health) using@nestjs/terminusthat returns 200 OK if the application is running.11. Troubleshooting Common MMS Delivery Issues
401 UnauthorizedError:.envfile match exactly what's shown in your Vonage Dashboard under your Application detailsprivateKeycontent is correctly formatted. If usingVONAGE_PRIVATE_KEY_CONTENT, ensure newlines are properly escaped (\n)FROM_NUMBERis linked to yourVONAGE_APPLICATION_IDin the dashboard under Applications > Linked numbersCould not read Vonage private key file...Error:private.keyfile at the specified path.VONAGE_PRIVATE_KEY_PATHis correct relative to where your application runs (usually the project root)private.keyexists in the specified location by runningls -la private.keyls -l private.key. If needed, set proper permissions:chmod 600 private.keyVONAGE_PRIVATE_KEY_CONTENTinstead of file pathsVonage private key source not configuredError:VONAGE_PRIVATE_KEY_PATHnorVONAGE_PRIVATE_KEY_CONTENTenvironment variables are set..envfile with the appropriate value. For local development, useVONAGE_PRIVATE_KEY_PATH=./private.key. For production deployments, useVONAGE_PRIVATE_KEY_CONTENTwith the full private key content.TO_NUMBERis in E.164 format without the+symbol (e.g.,14155552671, not+1-415-555-2671)imageUrlin a browser's incognito mode. If it doesn't load, Vonage cannot fetch it.jpg,.jpeg,.png, or.gif. Keep file size under 600 KBFROM_NUMBERsupports MMS and is properly linked in the Vonage dashboard