code examples

Sent logo
Sent TeamMar 8, 2026 / code examples / Article

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.

Send MMS Messages with NestJS and Vonage

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.

System Architecture Diagram:

text
[ Client (e.g., Postman/Web App) ] --> [ NestJS API Endpoint (/mms/send) ]
      |                                       |
      | HTTP POST Request (JSON Payload)      | Uses VonageService
      v                                       v
[ MmsController ] <-- Injects --- [ VonageService ] <-- Reads Config -- [ ConfigModule/.env ]
                                        |                                      | (API Key, Secret, App ID, Key Path/Content)
                                        | Calls Vonage SDK                     |
                                        v
                                [ @vonage/messages SDK ] --> [ Vonage Messages API ] --> [ Carrier Network ] --> [ Recipient Device ]
                                        |                                      ^
                                        | Reads Private Key (File or Env Var)  | Uses private.key content
                                        v                                      |
                                    [ private.key file OR Env Var ] -----------

Prerequisites

  • Node.js: Version 16 or later installed. (Check with node -v)
  • npm or yarn: Package manager installed.
  • NestJS CLI: Installed globally (npm install -g @nestjs/cli).
  • 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
    • HTTPS recommended: Secure URLs improve delivery reliability
    • 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.
    • Reference: Vonage MMS Overview

What 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:

  • 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
  • Country code required: Even for domestic numbers

Reference: 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

CountryFormatExampleExplanation
United States+1AAABBBCCCC+14155552671+1 (country code) + 415 (area code) + 5552671 (local number)
United Kingdom+44AAABBBBBBBB+442071838750+44 (country code) + 20 (area code) + 71838750 (local number)
Australia+61ABBBBBBBBB+61291234567+61 (country code) + 2 (area code) + 91234567 (local number)
Canada+1AAABBBCCCC+14165551234+1 (country code) + 416 (area code) + 5551234 (local number)

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

  1. Routing Accuracy: Telecommunications networks use E.164 to route messages to the correct carrier and destination
  2. International Compatibility: Ensures your application works across different countries without modification
  3. API Validation: Vonage validates number format before attempting delivery
  4. 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

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:

  1. Brand Registration:

    • Register your business/brand with The Campaign Registry (TCR)
    • Provide business details: legal name, tax ID, business type
    • Verification typically takes 1-5 business days
    • One-time registration fee applies
  2. Campaign Registration:

    • Register each messaging use case as a campaign
    • Specify campaign type (e.g., marketing, notifications, 2FA)
    • Describe message content and frequency
    • Campaign approval typically takes 1-3 business days
  3. Number Association:

    • Link your 10DLC number to the approved campaign
    • Only messages matching the campaign description should be sent
    • Multiple numbers can be associated with one campaign

Supported Number Types for MMS

According to Vonage documentation, only the following US number types support MMS via the Messages API:

Number TypeDescriptionRegistration RequiredBest For
10DLC NumbersStandard 10-digit local numbers✅ Yes (A2P 10DLC)Most business use cases
US Short Codes5-6 digit numbers✅ Yes (separate process)High-volume messaging
SMS-Enabled Toll-Free800, 888, 877, etc.✅ Yes (TFN verification)Customer service, support

Important: Unregistered 10DLC numbers sending A2P traffic may experience:

  • Reduced message throughput (1-3 messages per second)
  • Higher filtering rates by carriers
  • Potential number suspension
  • Failed delivery to certain carriers

Reference: Vonage Messages API - Send an MMS

Carrier Network Coverage

MMS messages via Vonage can be delivered to the following US carrier networks:

  • AT&T
  • T-Mobile (includes Sprint legacy network)
  • Verizon

Note: MMS delivery to regional carriers and MVNOs depends on their upstream carrier relationships.

Compliance Best Practices

  1. Obtain Explicit Consent: Always get written consent before sending marketing MMS
  2. Honor Opt-Outs: Implement STOP/UNSUBSCRIBE functionality
  3. Match Campaign Description: Only send messages that align with your registered campaign
  4. Monitor Throughput: Respect daily volume limits assigned to your campaign
  5. 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.

Step 1: Obtain API Credentials

  1. Log in to your Vonage API Dashboard.
  2. 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

  1. Navigate to Numbers > Buy numbers in the dashboard sidebar.
  2. Search for numbers in the United States (US).
  3. Ensure the number's Capabilities include SMS and MMS.
  4. Purchase a suitable number. Note down this number – it will be your FROM_NUMBER.

Step 3: Create a Vonage Application

  1. Navigate to Applications > Create a new application.
  2. Enter an Application name (e.g., NestJS MMS Sender).
  3. Enable the Messages capability.
  4. 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.
  5. 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.
  6. Click Create application.
  7. You'll be redirected to the application's detail page. Note down the Application ID.

Step 4: Link Your Number to the Application

  1. On the application's detail page (or navigate back to Applications and click your app name), scroll down to the Linked numbers section.
  2. Find the US MMS-capable number you purchased earlier.
  3. 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:

dotenv
# .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.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 .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.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:

  1. Dependencies & Lifecycle: Imports necessary modules and implements OnModuleInit to ensure configuration is loaded before initialization logic runs.
  2. Logger & State: Uses NestJS Logger and adds an isInitialized flag.
  3. Constructor: Only injects ConfigService. Initialization logic moved to onModuleInit.
  4. 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.
  5. 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.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 VonageService to trigger sending an MMS.

Step 1: Create the MMS Module, Controller, and DTO

bash
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.ts

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.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:

  • 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.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:

  1. Dependencies: Imports necessary NestJS modules, the VonageService, and the SendMmsDto.
  2. Controller Decorator: @Controller('mms') sets the base route for all methods in this controller to /mms.
  3. Logger: Standard NestJS logger.
  4. Constructor: Injects VonageService so the controller can use its methods.
  5. 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.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:

  1. 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).
  2. 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.
  3. API Call: VonageService.sendMms constructs the MMSImage payload and uses the initialized messagesClient.send() method to make the API request to Vonage.
  4. 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:
    bash
    npm install @nestjs/throttler
    Configure it in app.module.ts:
    typescript
    // 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 {}
  • 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.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

  • 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 imageUrl must 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:
      1. Verify all credentials in your .env file match exactly what's shown in your Vonage Dashboard under your Application details
      2. Confirm your privateKey content is correctly formatted. If using VONAGE_PRIVATE_KEY_CONTENT, ensure newlines are properly escaped (\n)
      3. Check that your FROM_NUMBER is linked to your VONAGE_APPLICATION_ID in the dashboard under Applications > Linked numbers
      4. Verify your number type supports MMS and is configured correctly
      5. 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:
      1. Verify the path in VONAGE_PRIVATE_KEY_PATH is correct relative to where your application runs (usually the project root)
      2. Confirm private.key exists in the specified location by running ls -la private.key
      3. Check file permissions with ls -l private.key. If needed, set proper permissions: chmod 600 private.key
      4. 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:
      1. Validate phone format: Ensure TO_NUMBER is in E.164 format without the + symbol (e.g., 14155552671, not +1-415-555-2671)
      2. Test image accessibility: Open your imageUrl in a browser's incognito mode. If it doesn't load, Vonage cannot fetch it
      3. Verify image format: Confirm your image is .jpg, .jpeg, .png, or .gif. Keep file size under 600 KB
      4. Check Vonage dashboard: Log into your dashboard and navigate to API Dashboard > Messages to view detailed error messages for your message UUID
      5. Verify number capabilities: Confirm your FROM_NUMBER supports MMS and is properly linked in the Vonage dashboard
      6. 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
      7. 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