Send MMS Messages with NestJS and Vonage
This guide provides a step-by-step walkthrough for building a production-ready NestJS application capable of sending MMS messages using the Vonage Messages API. We'll cover everything from initial project setup and Vonage configuration to implementing the core sending logic, building an API endpoint, handling errors, and considering security and deployment best practices.
By the end of this tutorial, you will have a functional NestJS API endpoint that accepts recipient details and an image URL, then uses the Vonage SDK to send an MMS message. This solves the common need for applications to programmatically send multimedia content via carrier networks, useful for notifications, alerts, marketing, or user engagement requiring images.
Technologies Used:
- NestJS: A progressive Node.js framework for building efficient, reliable, and scalable server-side applications. Chosen for its modular architecture, dependency injection, and excellent TypeScript support.
- Vonage Messages API: Enables sending messages across various channels, including MMS. We'll use the
@vonage/messages
SDK. - Node.js: The underlying JavaScript runtime.
- TypeScript: Superset of JavaScript adding static types.
System Architecture Diagram:
[ 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). Supported formats are
.jpg
,.jpeg
, and.png
.
1. Vonage Account and Application Setup
Before writing any code, we need to 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
- Log in to your Vonage API Dashboard.
- 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
andhttps://example.com/inbound
for now. For production apps receiving delivery receipts or inbound messages, you'd 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
Let's 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-mms
Choose your preferred package manager (npm or yarn) when prompted.
Step 2: Install Dependencies
We need 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.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:
touch .env
Add your Vonage credentials and configuration to .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:
# .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
):
// 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
We'll 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 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:
// 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:
- Dependencies & Lifecycle: Imports necessary modules and implements
OnModuleInit
to ensure configuration is loaded before initialization logic runs. - Logger & State: Uses NestJS
Logger
and adds anisInitialized
flag. - Constructor: Only injects
ConfigService
. Initialization logic moved toonModuleInit
. 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 forVONAGE_PRIVATE_KEY_PATH
and attempts to read the file usingfs.readFileSync
. Throws an error if neither is configured or if file reading fails. - Initializes the
Messages
client within atry...catch
block for robustness. - Sets
isInitialized
to true on success.
- Retrieves all necessary Vonage credentials and configuration options (
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 atry...catch
block. - Logs success/failure and returns the
message_uuid
or throwsInternalServerErrorException
.
Step 3: Register the Service
Make sure VonageService
is provided and exported by VonageModule
.
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 VonageService
to 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.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
:
// 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 thecaption
field to be omitted.
Step 3: Implement the MMS Controller
Open src/mms/mms.controller.ts
and 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:
- Dependencies: Imports necessary NestJS modules, the
VonageService
, and theSendMmsDto
. - 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 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 thesendMmsDto
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, 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 VonageService
available for injection in MmsController
, import VonageModule
into MmsModule
.
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
:
- 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 theMessages
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 theMMSImage
payload and uses the initializedmessagesClient.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 orprivate.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
inVonageService
) throwInternalServerErrorException
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 genericInternalServerErrorException
. - 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.
- Configuration errors during startup (
- 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 inSendMmsDto
and enforced byValidationPipe
in the controller. This prevents malformed requests and potential injection issues (e.g., ensuringimageUrl
is a valid URL). - Secrets Management: Handled via environment variables and
ConfigModule
. Never hardcode credentials. Ensure proper permissions on theprivate.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
:Configure it innpm install @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 {}
- 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.
- API Keys: Implement a custom Guard that checks for a valid
- HELMET: Use the
helmet
middleware for basic security headers (viaapp.use(helmet())
inmain.ts
).npm install helmet
// 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 and Caveats
401 Unauthorized
Error:- Cause: Incorrect API Key, API Secret, Application ID, or Private Key content/format. Mismatch between credentials and application. Number not linked to the Application ID used. MMS feature potentially not enabled for the account/number type. Using old SMS API credentials with Messages API.
- Solution: Double-check all credentials in environment variables against the Vonage Dashboard (Application details). Verify the
privateKey
content is correctly formatted (especially if usingVONAGE_PRIVATE_KEY_CONTENT
, ensure newlines are handled). Ensure theFROM_NUMBER
is linked to theVONAGE_APPLICATION_ID
in the dashboard. Confirm your number type supports MMS and is configured correctly. Ensure you are using credentials generated for the Messages API Application.
Could not read Vonage private key file...
Error:- Cause:
VONAGE_PRIVATE_KEY_CONTENT
is not set, and the path specified inVONAGE_PRIVATE_KEY_PATH
is incorrect. File doesn't exist at that path relative to the project root where the app is running. Insufficient file permissions in the runtime environment. - Solution: Verify the path in
VONAGE_PRIVATE_KEY_PATH
. Ensureprivate.key
is in the correct location relative to the running process. Check file permissions (chmod 600 private.key
is often recommended). Consider usingVONAGE_PRIVATE_KEY_CONTENT
instead, especially in containerized/serverless environments.
- Cause:
Vonage private key source not configured
Error:- Cause: Neither
VONAGE_PRIVATE_KEY_PATH
norVONAGE_PRIVATE_KEY_CONTENT
environment variables are set. - Solution: Set one of these environment variables with the appropriate value.
- Cause: Neither
- MMS Not Sending / Delivery Issues:
- Cause: Invalid
TO_NUMBER
format (not E.164).imageUrl
is not publicly accessible or is an unsupported format/size. Network issues. Carrier filtering/blocking.FROM_NUMBER
doesn't support MMS or isn't configured correctly. Sending outside supported regions (US to US). - Solution: Validate
TO_NUMBER
. TestimageUrl
in a browser's incognito mode. Try a known-good placeholder image URL for testing. Check Vonage dashboard logs for specific error messages related to the message UUID. Verify number capabilities and linkage in the Vonage dashboard. Confirm compliance with A2P 10DLC regulations if applicable.
- Cause: Invalid