code examples
code examples
Send MMS with Vonage Messages API, Node.js & Fastify: Complete Guide
Build a production-ready Node.js MMS sender using Vonage Messages API and Fastify. Includes 10DLC registration, authentication, error handling, and deployment best practices for 2025.
Send MMS with Vonage Messages API, Node.js & Fastify: Complete Guide
Build a Node.js application using the Fastify framework to send Multimedia Messaging Service (MMS) messages via the Vonage Messages API. This guide covers project setup, Vonage configuration, core sending logic, API endpoints, error handling, and deployment.
Complete this tutorial in approximately 45 minutes to create a functional Fastify server that accepts requests to send MMS messages containing images to specified recipients using your Vonage account.
Project Overview and Goals
Goal: Create a simple, robust backend service that programmatically sends MMS messages (specifically images) using Node.js and the Vonage API.
Problem Solved: Automates sending multimedia content via SMS (Short Message Service) channels – essential for notifications, alerts, marketing, or user engagement requiring images.
Technologies Used:
- Node.js: A JavaScript runtime environment for building server-side applications. Recommended: v20.x (LTS – Long-Term Support, maintenance mode until April 2027) or v22.x (Active LTS). Node.js v18 reached end-of-life in April 2025 and should not be used for new projects.
- Fastify: A high-performance, low-overhead web framework for Node.js, chosen for speed and developer-friendly features. Requires Fastify v4 or higher (v3 and lower are EOL – End of Life). Fastify v4+ requires Node.js v14 or higher, but v20+ is recommended for long-term support.
- Vonage Messages API: A unified API for sending messages across various channels, including SMS and MMS.
@vonage/messagesSDK (Software Development Kit): The official Vonage Node.js client library for interacting with the Messages API.dotenv: A module to load environment variables from a.envfile, keeping sensitive credentials out of source code.
Prerequisites:
- Node.js and npm (or yarn): Installed on your development machine. Check with
node -vandnpm -v. Minimum Node.js v20.x recommended; v22.x preferred for Active LTS support. - Vonage API Account: Sign up if you don't have one. New accounts receive $2.00 in free credits to start testing.
- Vonage API Key and Secret: Found on your Vonage API Dashboard.
- A Vonage Application: Create this during the setup process (Section 2, Step 5) to manage API access and generate authentication keys.
- A Vonage US Number with 10DLC Registration: Obtain an MMS-capable US number during setup (Section 2, Step 3) and complete 10DLC (10-Digit Long Code) brand and campaign registration. Important: All US geographic numbers are MMS-capable, but must be 10DLC enabled first before sending MMS messages. MMS sending via the Vonage Messages API is primarily supported from US 10DLC, Toll-Free, or Short Code numbers to US recipients. 10DLC registration is required for all SMS and MMS traffic to the United States and may take several days or weeks to complete. Pricing: MMS messages cost approximately $0.03–$0.05 per message segment to US recipients (varies by carrier and message size).
- Basic understanding of JavaScript, Node.js, and REST (Representational State Transfer) APIs.
System Architecture:
+-------------+ +-----------------+ +----------------+ +---------+
| Client | ----> | Fastify Server | ----> | Vonage API | ----> | Phone |
| (e.g. curl, | | (Node.js App) | | (Messages API) | | (Recipient) |
| Postman) | +-----------------+ +----------------+ +---------+
| | | ^
| | | Fetches Image |
| | | |
| | +-------------------> +-----------------+ |
| | | Image Host URL | ------+
| | | (e.g., PlaceKitten)|
| | +-----------------+
+-------------+
Final Outcome: A running Fastify application with a /send-mms endpoint that accepts a POST request containing recipient number, image URL (Uniform Resource Locator), caption, and sender number, then uses the Vonage API to send the MMS.
1. Setting Up the Project
Initialize your Node.js project using Fastify.
-
Create Project Directory: Open your terminal and create a new directory for your project, then navigate into it.
bashmkdir fastify-vonage-mms cd fastify-vonage-mms -
Initialize Node.js Project: Create a
package.jsonfile to manage dependencies and project metadata.bashnpm init -y -
Install Dependencies: Install Fastify for the web server,
@vonage/messagesfor the Vonage API interaction, anddotenvfor environment variables.bashnpm install fastify @vonage/messages dotenvRecommended Version Pinning: For production applications, pin major versions to ensure stability and security:
Package Recommended Version Notes fastify^4.28.0Latest v4 stable release with security patches @vonage/messages^1.11.0Latest Messages API SDK with bug fixes dotenv^16.4.0Stable environment variable loader Run
npm auditafter installation to check for known security vulnerabilities. -
Set Up Project Structure: Create a basic structure for clarity.
bashmkdir src touch src/server.js touch src/vonageClient.js touch .env touch .gitignoresrc/server.js: Contains your Fastify application code.src/vonageClient.js: Encapsulates the Vonage API interaction logic..env: Stores your sensitive API credentials and configuration. Never commit this file to version control..gitignore: Specifies intentionally untracked files that Git should ignore.
-
Configure
.gitignore: Addnode_modulesand.envto your.gitignorefile to prevent committing them.Codenode_modules/ .env private.key -
Set Up Environment Variables (
.env): Open the.envfile and add the following placeholders. Fill these in during the Vonage configuration step.dotenv# Vonage API Credentials (Found in Vonage Dashboard) VONAGE_API_KEY=YOUR_API_KEY VONAGE_API_SECRET=YOUR_API_SECRET # Vonage Application Details (Generated during Vonage Setup) VONAGE_APPLICATION_ID=YOUR_APPLICATION_ID VONAGE_APPLICATION_PRIVATE_KEY_PATH=./private.key # Path relative to project root # Phone Numbers VONAGE_FROM_NUMBER=YOUR_VONAGE_MMS_NUMBER # Your purchased Vonage number (E.164 format) DEFAULT_TO_NUMBER=RECIPIENT_PHONE_NUMBER # A test recipient number (E.164 format, e.g., +14155552671) # Optional: Server Port # PORT=3000Explanation of Variables:
VONAGE_API_KEY,VONAGE_API_SECRET: Your main account credentials from the Vonage Dashboard.VONAGE_APPLICATION_ID: Unique ID for the Vonage Application you'll create. Needed for Messages API authentication.VONAGE_APPLICATION_PRIVATE_KEY_PATH: Path to the private key file downloaded when creating the Vonage Application. Used for JWT (JSON Web Token) authentication with the Messages API.VONAGE_FROM_NUMBER: The MMS-capable US number you purchased from Vonage (in E.164 format, e.g., +12015550123).DEFAULT_TO_NUMBER: A default recipient number for easy testing (in E.164 format).
2. Configuring Vonage
Set up Vonage correctly before writing code.
-
Log in to Vonage: Access your Vonage API Dashboard.
-
Note API Key & Secret: Find your
API keyandAPI secretat the top of the dashboard. Copy these into your.envfile. -
Purchase an MMS-Capable Number:
- Navigate to Numbers > Buy Numbers.
- Select Country: United States.
- Under Features, ensure SMS and MMS are selected.
- Choose Type (e.g., Mobile, Toll-Free). Note: All US geographic numbers are MMS-capable, but must be 10DLC enabled before use.
- Click Search. Find a suitable number and click Buy.
- Copy this number (including the country code, e.g., +1…) into your
.envfile forVONAGE_FROM_NUMBER.
Pricing: US 10DLC numbers cost $1.00/month. Toll-Free numbers cost $5.00–$15.00/month depending on availability.
-
Register for 10DLC (Required for US MMS):
Critical Step: Before sending MMS to the United States, complete 10DLC registration. This involves:
Step Action Processing Time Cost 1 Register your 10DLC brand (declare your company) 1–3 business days $4.00 (one-time) 2 Brand verification by The Campaign Registry (TCR) 1–5 business days Included in brand fee 3 Register a 10DLC campaign (describe your messaging use case) 1–3 business days $15.00/month (recurring) 4 Link your US number to your 10DLC campaign Immediate Included Total Setup Cost: $4.00 one-time + $15.00/month ongoing campaign fee + $1.00/month number fee.
- Navigate to Campaigns in the Vonage Dashboard to begin registration.
- Processing Time: 10DLC registration requires manual reviews and may take several days or even weeks to complete. Plan accordingly before launching production messaging.
- Compatibility Note: Only +1 10-Digit Long Codes may be linked to 10DLC campaigns; Short Codes and Toll-Free Numbers have separate registration processes.
- For detailed guidance, see Vonage 10DLC Guide.
-
Create a Vonage Application:
- Navigate to Applications > Create a new application.
- Give your application a descriptive Name (e.g., "Fastify MMS Sender").
- Click Generate public and private key. This automatically adds a public key to the application settings and prompts you to download the corresponding
private.keyfile. - Crucially: Save the
private.keyfile directly into the root directory of yourfastify-vonage-mmsproject (the same level as yourpackage.json). Ensure the path in your.envfile (VONAGE_APPLICATION_PRIVATE_KEY_PATH=./private.key) matches this location. Also ensureprivate.keyis added to your.gitignore. - Scroll down to Capabilities.
- Toggle Messages ON.
- Inbound URL and Status URL: For sending MMS only, these aren't strictly required to be functional endpoints, but the Vonage dashboard requires placeholders. Enter temporary valid URLs like
https://example.com/webhooks/inboundandhttps://example.com/webhooks/status. If you later want to receive delivery receipts, update these with real endpoints accessible by Vonage.
- Inbound URL and Status URL: For sending MMS only, these aren't strictly required to be functional endpoints, but the Vonage dashboard requires placeholders. Enter temporary valid URLs like
- Scroll down and click Generate new application.
- You'll be taken to the application's overview page. Copy the Application ID and paste it into your
.envfile forVONAGE_APPLICATION_ID.
-
Link Your Number to the Application:
- On the application's overview page, scroll down to the Linked numbers section.
- Click Link next to the US MMS-capable number you purchased earlier.
-
Verify Default API Settings (Important):
- Navigate to API Settings in the left sidebar.
- Scroll to the SMS settings section.
- Ensure that Default SMS Setting is set to Messages API. If it's set to "SMS API", change it to "Messages API" and click Save changes. This ensures your account uses the correct API infrastructure for MMS.
Your Vonage account and application are now configured for sending MMS. Remember: Your number must complete 10DLC registration before you can send messages to US recipients.
3. Implementing the MMS Sending Logic
Create the core function to interact with the Vonage API in src/vonageClient.js.
// src/vonageClient.js
require('dotenv').config(); // Load environment variables from .env
const { Messages, MMSImage } = require('@vonage/messages');
const path = require('path');
const fs = require('fs'); // Required for checking private key existence
// Validate essential environment variables
const requiredEnv = [
'VONAGE_API_KEY',
'VONAGE_API_SECRET',
'VONAGE_APPLICATION_ID',
'VONAGE_APPLICATION_PRIVATE_KEY_PATH',
'VONAGE_FROM_NUMBER',
];
for (const variable of requiredEnv) {
if (!process.env[variable]) {
console.error(`Error: Environment variable ${variable} is not set.`);
process.exit(1); // Exit if critical config is missing
}
}
// Resolve the private key path relative to the project root
const privateKeyPath = path.resolve(process.env.VONAGE_APPLICATION_PRIVATE_KEY_PATH);
// Check if private key file exists
if (!fs.existsSync(privateKeyPath)) {
console.error(`Error: Private key file not found at path: ${privateKeyPath}`);
console.error('Please ensure VONAGE_APPLICATION_PRIVATE_KEY_PATH in .env is correct and the file exists.');
process.exit(1);
}
// Initialize Vonage Messages Client
// For Messages API v1 (which supports MMS), authentication requires
// API Key, API Secret, Application ID, and Private Key.
const messagesClient = new Messages({
apiKey: process.env.VONAGE_API_KEY,
apiSecret: process.env.VONAGE_API_SECRET,
applicationId: process.env.VONAGE_APPLICATION_ID,
privateKey: privateKeyPath, // Use the resolved absolute path
});
/**
* Sends an MMS message with an image using the Vonage Messages API.
*
* @param {string} to - The recipient phone number in E.164 format.
* @param {string} from - The Vonage sender number in E.164 format.
* @param {string} imageUrl - The publicly accessible URL of the image. Must not require authentication or be behind restrictive firewalls.
* @param {string} [caption=''] - Optional text caption for the image.
* @returns {Promise<object>} - A promise that resolves with the Vonage API response.
* @throws {Error} - Throws an error if sending fails.
*/
async function sendMms(to, from, imageUrl, caption = '') {
console.log(`Attempting to send MMS from ${from} to ${to} with image: ${imageUrl}`);
// Ensure the image URL is valid and appears publicly accessible
// Basic validation – more robust checks might be needed in production
if (!imageUrl || !imageUrl.startsWith('http')) {
throw new Error('Invalid or missing image URL. Must be a public URL starting with http:// or https://.');
}
// Add a note about accessibility requirements
console.log(`Info: Ensure image URL ${imageUrl} is publicly accessible to Vonage servers (no auth/firewall restrictions).`);
// Add file size reminder
console.log(`Info: MMS file size limit is 600 KB (limits vary by carrier and device). Supported formats: JPG, JPEG, PNG, GIF.`);
// Construct the MMS message payload
const mmsPayload = new MMSImage({
to: to,
from: from,
image: {
url: imageUrl,
caption: caption,
},
channel: 'mms', // Explicitly specify MMS channel
});
try {
// Send the message using the client
const response = await messagesClient.send(mmsPayload);
console.log('MMS Sent Successfully! Response:', response);
// The key identifier for the sent message is `message_uuid`
return response;
} catch (error) {
// Log detailed error from Vonage if available
const vonageErrorDetails = error?.response?.data;
if (vonageErrorDetails) {
console.error('Error sending MMS via Vonage:', JSON.stringify(vonageErrorDetails, null, 2));
} else {
console.error('Error sending MMS:', error.message);
}
// Rethrow or handle specific Vonage errors
throw new Error(`Failed to send MMS: ${vonageErrorDetails?.title || error.message}`);
}
}
module.exports = { sendMms };Explanation:
dotenv.config(): Loads the variables from your.envfile intoprocess.env.- Environment Variable Validation: Checks if critical variables are set, exiting if not.
- Private Key Path Resolution & Check: Uses
path.resolveto get the absolute path andfs.existsSyncto verify the private key file exists at the specified location before proceeding. @vonage/messagesInitialization: Creates an instance of theMessagesclient. Crucially, it uses the combination ofapiKey,apiSecret,applicationId, andprivateKey. This JWT-based authentication is required for the Messages API v1 used for MMS.sendMmsFunction:- Takes
to,from,imageUrl, and an optionalcaptionas arguments. - Performs basic validation on the
imageUrl. Important: The URL must point directly to a publicly accessible image file (.jpg,.jpeg,.png,.gif). URLs requiring authentication or blocked by firewalls will fail. - Creates an
MMSImageobject defining the recipient, sender, image URL, and caption. - Calls
messagesClient.send()with the payload. - Uses
async/awaitwith atry…catchblock for cleaner asynchronous code and error handling. - Logs the success response (which includes the
message_uuid) or detailed error information. Vonage API errors often contain useful details inerror.response.data.
- Takes
module.exports: Exports thesendMmsfunction so you can use it in your Fastify server.
4. Building the Fastify API Endpoint
Integrate the sendMms function into a Fastify API route in src/server.js.
// src/server.js
require('dotenv').config();
const fastify = require('fastify')({ logger: true }); // Enable built-in Pino logger
const { sendMms } = require('./vonageClient'); // Import our sending function
// --- Request Validation Schema ---
// Define the expected structure of the request body for robustness
const sendMmsBodySchema = {
type: 'object',
required: ['to', 'imageUrl'], // 'from' will use default from .env, 'caption' is optional
properties: {
to: {
type: 'string',
pattern: '^\\+[1-9]\\d{1,14}$', // E.164 format validation (+ followed by 1-15 digits)
description: 'Recipient phone number in E.164 format (e.g., +14155552671)',
},
imageUrl: {
type: 'string',
format: 'uri', // Basic URI format check
description: 'Publicly accessible URL of the image (JPG, PNG, GIF)',
},
caption: {
type: 'string',
description: 'Optional text caption for the image',
default: '', // Provide a default empty string
},
from: {
type: 'string',
pattern: '^\\+[1-9]\\d{1,14}$', // E.164 format validation
description: 'Optional sender number (must be a Vonage number linked to your Application ID)',
},
},
additionalProperties: false, // Disallow properties not defined in the schema
};
const sendMmsSchema = {
body: sendMmsBodySchema,
// Optional: Add response schemas for better documentation and validation
// response: {
// 200: { ... },
// 400: { ... },
// 500: { ... }
// }
};
// --- API Route ---
fastify.post('/send-mms', { schema: sendMmsSchema }, async (request, reply) => {
// `to`, `imageUrl`, `caption` are validated and available from request.body
// `caption` has a default value from the schema if not provided
const { to, imageUrl, caption } = request.body;
// Use provided 'from' or default from environment variable
const from = request.body.from || process.env.VONAGE_FROM_NUMBER;
// Check if a 'from' number is available (either from request or .env)
if (!from) {
// This check is still useful if VONAGE_FROM_NUMBER is missing in .env AND 'from' isn't in the request
reply.status(400).send({ error: 'Sender number (from) is missing. Provide in request body or set VONAGE_FROM_NUMBER in .env.' });
return; // Important to return after sending response
}
try {
// Call the function to send the MMS
const vonageResponse = await sendMms(to, from, imageUrl, caption);
// Send success response back to the client
reply.status(200).send({
message: 'MMS sending initiated successfully.',
recipient: to,
sender: from,
messageId: vonageResponse.message_uuid, // Include the Vonage message UUID
});
} catch (error) {
// Log the detailed error using Fastify's logger
// The error message already contains details logged in vonageClient.js
fastify.log.error(`MMS sending failed for recipient ${to}: ${error.message}`);
// Send error response back to the client
// Avoid leaking excessive internal details; send a structured error
reply.status(500).send({
error: 'Failed to send MMS.',
details: error.message, // Provide the specific error message from vonageClient/validation
});
}
});
// --- Health Check Route (Optional but Recommended) ---
fastify.get('/health', async (request, reply) => {
return { status: 'ok' };
});
// --- Start Server ---
const start = async () => {
try {
const port = process.env.PORT || 3000;
// Listen on 0.0.0.0 to be accessible inside/outside containers
await fastify.listen({ port: port, host: '0.0.0.0' });
// Logger already active, message handled by fastify.listen
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
start();Explanation:
require('dotenv').config(): Loads environment variables.fastify({ logger: true }): Initializes Fastify and enables its built-in Pino logger.require('./vonageClient'): Imports thesendMmsfunction.- Request Validation Schema (
sendMmsSchema):- Defines the expected JSON structure for the
POST /send-mmsrequest body using Fastify's JSON Schema support. - Requires
toandimageUrl. - Validates
toand optionalfromagainst an E.164 regex (^\\+[1-9]\\d{1,14}$). Adds descriptions. - Checks
imageUrlfor basic URI format. Adds description. - Makes
captionan optional string with a default value and description. - Adds
additionalProperties: falseto reject requests with extra, undefined fields. - This schema validation automatically handles incoming requests, returning a detailed 400 error if the schema doesn't match, significantly improving robustness.
- Defines the expected JSON structure for the
- POST
/send-mmsRoute:- Uses
async (request, reply)for asynchronous handling. - Includes the
{ schema: sendMmsSchema }option to enable automatic validation. - Extracts validated
to,imageUrl, andcaptionfromrequest.body. - Determines the
fromnumber: uses the one from the request body if provided, otherwise defaults toVONAGE_FROM_NUMBERfrom the.envfile. Includes a check to ensure afromnumber is available (in case.envis missing the variable and it's not in the request). - Calls
await sendMms(…)inside atry…catchblock. - Success: Sends a
200 OKresponse with a success message and themessage_uuidreceived from Vonage. - Error: Logs the error using
fastify.log.errorand sends a500 Internal Server Errorresponse with a structured error message including the specific detail from the caught error.
- Uses
- GET
/healthRoute: A simple health check endpoint, useful for load balancers or monitoring systems. startFunction:- Defines an asynchronous function to start the server.
- Listens on the port specified by the
PORTenvironment variable (defaults to 3000). - Listens on
0.0.0.0to be accessible from outside the container/machine if needed (common for deployment). - Includes error handling for server startup failures.
start(): Calls the function to start the server.
5. Running and Testing the Application
-
Ensure
.envis correct: Double-check all Vonage credentials, App ID, Private Key Path, and numbers in your.envfile. Make sureprivate.keyexists at the specified path. -
Start the Server: Open your terminal in the project root directory (
fastify-vonage-mms) and run:bashnode src/server.jsYou should see output indicating the server is listening, similar to:
{"level":30,"time":…,"pid":…,"hostname":"…","msg":"Server listening at http://0.0.0.0:3000"}NPM Scripts for Different Environments:
Add these scripts to your
package.jsonfor streamlined workflows:json{ "scripts": { "start": "node src/server.js", "dev": "NODE_ENV=development node --watch src/server.js", "test": "NODE_ENV=test node src/server.js", "prod": "NODE_ENV=production node src/server.js" } }Run
npm run devfor development with auto-reload (Node.js v18.11+ required),npm startfor quick testing, ornpm run prodfor production with optimizations. -
Test with
curlor Postman: Open another terminal window and usecurl(or configure Postman) to send a POST request to your running server.Replace:
YOUR_RECIPIENT_NUMBERwith the actual phone number (E.164 format) you want to send the MMS to (you can use theDEFAULT_TO_NUMBERfrom.envif set).- Optionally change the
imageUrlandcaption.
bash# Basic Test curl -X POST http://localhost:3000/send-mms \ -H "Content-Type: application/json" \ -d '{ "to": "YOUR_RECIPIENT_NUMBER", "imageUrl": "https://placekitten.com/200/300", "caption": "Hello from Fastify & Vonage!" }' # Test without caption (should use default "") curl -X POST http://localhost:3000/send-mms \ -H "Content-Type: application/json" \ -d '{ "to": "YOUR_RECIPIENT_NUMBER", "imageUrl": "https://placekitten.com/g/200/300" }' # Test overriding the sender number (if you have another linked number) # Replace YOUR_OTHER_VONAGE_NUMBER with a valid, linked number in E.164 format curl -X POST http://localhost:3000/send-mms \ -H "Content-Type: application/json" \ -d '{ "to": "YOUR_RECIPIENT_NUMBER", "from": "YOUR_OTHER_VONAGE_NUMBER", "imageUrl": "https://placekitten.com/200/287", "caption": "Sent from alternate number!" }' # Test invalid request (missing required field 'imageUrl') – Should get 400 error curl -X POST http://localhost:3000/send-mms \ -H "Content-Type: application/json" \ -d '{ "to": "YOUR_RECIPIENT_NUMBER", "caption": "This will fail" }' # Test invalid request (bad 'to' format) – Should get 400 error curl -X POST http://localhost:3000/send-mms \ -H "Content-Type: application/json" \ -d '{ "to": "12345", "imageUrl": "https://placekitten.com/200/300" }' -
Check Results:
- Terminal (curl): For successful requests, you should receive a JSON response like:
For invalid requests (schema validation failure), you'll get a 400 error with details:json
{ "message": "MMS sending initiated successfully.", "recipient": "YOUR_RECIPIENT_NUMBER", "sender": "YOUR_VONAGE_FROM_NUMBER", "messageId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" }Or a 500 error if the Vonage API call fails:json{ "statusCode": 400, "error": "Bad Request", "message": "body must have required property 'imageUrl'" // Example }json{ "error": "Failed to send MMS.", "details": "Specific error message from Vonage or validation" } - Terminal (Server Logs): Check the logs where
node src/server.jsis running. You'll see Fastify's request logs (including request IDs) and the logs fromvonageClient.js(attempting send, success/error details, including detailed Vonage errors if they occur). - Recipient Phone: The recipient number should receive the MMS message with the image and caption shortly after a successful API call.
- Vonage Dashboard: Navigate to Logs > Messages API logs in your Vonage dashboard to see the status of the sent message (e.g., Submitted, Delivered, Failed). This is crucial for debugging delivery issues.
- Terminal (curl): For successful requests, you should receive a JSON response like:
6. Error Handling and Logging
Your current setup includes robust error handling and logging:
- Fastify Schema Validation: Automatically handles malformed requests (missing required fields, incorrect types/formats, extra fields), returning detailed 400 errors before the route handler is even called.
vonageClient.jsErrors: Thetry…catchblock catches errors during themessagesClient.send()call. It logs detailed errors (especially Vonage-specific error codes/messages found inerror.response.data) to the console/server logs.- Fastify Route Errors: The route's
try…catchcatches errors fromsendMms, logs them usingfastify.log.error, and returns a structured 500 error to the client. - Fastify Logging:
fastify({ logger: true })provides structured JSON logging (level, time, pid, hostname, msg, reqId, request/response details) for incoming requests and errors, suitable for production log aggregation.
Centralized Error Handler Implementation:
// Add this to src/server.js after creating the fastify instance
fastify.setErrorHandler((error, request, reply) => {
// Log the error
fastify.log.error(error);
// Map Vonage-specific errors to HTTP status codes
const vonageErrorType = error.response?.data?.type;
let statusCode = 500;
let message = 'Internal server error.';
if (vonageErrorType) {
// Parse specific Vonage error types
if (vonageErrorType.includes('unauthorized') || error.statusCode === 401) {
statusCode = 401;
message = 'Authentication failed. Check your Vonage credentials.';
} else if (vonageErrorType.includes('forbidden') || error.statusCode === 403) {
statusCode = 403;
message = 'Permission denied. Verify your number is linked and 10DLC registration is complete.';
} else if (vonageErrorType.includes('rate-limit') || error.statusCode === 429) {
statusCode = 429;
message = 'Rate limit exceeded. Please retry after some time.';
} else if (error.statusCode === 400 || error.validation) {
statusCode = 400;
message = error.message || 'Invalid request format.';
}
} else if (error.validation) {
// Fastify validation errors
statusCode = 400;
message = error.message;
}
reply.status(statusCode).send({
error: message,
details: process.env.NODE_ENV === 'development' ? error.message : undefined,
});
});Further Improvements:
- Specific Vonage Error Codes: Parse
error.response.data.typeorerror.response.data.titlefrom Vonage errors in the route handler'scatchblock to provide more specific HTTP status codes (e.g., 403 for permission issues, 429 for rate limits) or user feedback. Refer to Vonage API Error Codes. - Log Levels: Configure Pino logger levels (e.g., only log
infoand above in production,debugortracein development) via Fastify options or environment variables (LOG_LEVEL). - Log Aggregation: In production, forward logs to a service like Datadog, Logstash, Splunk, or CloudWatch Logs for centralized monitoring, analysis, and alerting. The JSON format from Pino is ideal for this.
7. Security Considerations
-
Environment Variables: Crucial. Never hardcode API keys, secrets, private key content, or phone numbers in your source code. Use
.envlocally and secure environment variable management in production (e.g., AWS Secrets Manager, HashiCorp Vault, platform-specific environment variables). Always add.envandprivate.keyto.gitignore. -
Input Validation: Fastify's schema validation (
additionalProperties: false, type/format checks) is your first line of defense against injection attacks and ensures data integrity for the API endpoint. Sanitize any user-providedcaptiontext if it's ever displayed elsewhere (e.g., in a web UI) to prevent Cross-Site Scripting (XSS). -
Rate Limiting: Protect your API endpoint and Vonage account from abuse or accidental loops. Use a plugin like
@fastify/rate-limit.bashnpm install @fastify/rate-limitjavascript// In server.js, register the plugin, preferably within an async context like `start` // Example registration within the start function: const start = async () => { try { // Register rate limit plugin before routes that need it await fastify.register(require('@fastify/rate-limit'), { max: 100, // Max requests per windowMS per IP timeWindow: '1 minute' }); // Define routes (like fastify.post('/send-mms', …)) after registering plugins const port = process.env.PORT || 3000; await fastify.listen({ port: port, host: '0.0.0.0' }); } catch (err) { fastify.log.error(err); process.exit(1); } }; // Ensure routes are defined *after* plugin registration if they need the plugin. // If routes are defined globally, register plugins before defining routes.CORS Configuration Example:
If your API needs to accept requests from browser-based clients on different domains, configure CORS:
bashnpm install @fastify/corsjavascript// In src/server.js, register CORS plugin const start = async () => { try { await fastify.register(require('@fastify/cors'), { origin: ['https://yourdomain.com', 'https://app.yourdomain.com'], // Whitelist specific origins methods: ['POST'], // Only allow POST for /send-mms credentials: true, // Allow cookies/auth headers }); // Register other plugins and routes... const port = process.env.PORT || 3000; await fastify.listen({ port: port, host: '0.0.0.0' }); } catch (err) { fastify.log.error(err); process.exit(1); } }; -
Authentication/Authorization: The current
/send-mmsendpoint is open. In a real-world application, you must protect it:- API Key: Require clients to send a secret API key in a header (e.g.,
Authorization: Bearer YOUR_SECRET_KEYorX-API-Key: YOUR_SECRET_KEY). Validate this key on the server using a Fastify hook (preHandleroronRequest). - JWT: Implement user authentication (e.g., with
@fastify/jwtand@fastify/auth) and verify JWTs to authorize requests based on user identity or roles.
- API Key: Require clients to send a secret API key in a header (e.g.,
-
HTTPS: Always run your application behind HTTPS in production. Load balancers (like AWS ALB, Nginx, Caddy, Cloudflare) typically handle this by terminating SSL/TLS before forwarding traffic to your Node.js server.
-
Private Key Handling: Be extremely careful with the
private.keyfile. Ensure its file permissions are restrictive (readable only by the application user). In production, consider loading the key content from a secure secret store or environment variable instead of relying on the file system path, especially in containerized environments.Loading Private Key from Environment Variable:
javascript// In src/vonageClient.js, modify the private key handling: let privateKey; if (process.env.VONAGE_APPLICATION_PRIVATE_KEY_CONTENT) { // Load private key content directly from environment variable privateKey = process.env.VONAGE_APPLICATION_PRIVATE_KEY_CONTENT; console.log('Using private key from environment variable.'); } else if (process.env.VONAGE_APPLICATION_PRIVATE_KEY_PATH) { // Fall back to loading from file path const privateKeyPath = path.resolve(process.env.VONAGE_APPLICATION_PRIVATE_KEY_PATH); if (!fs.existsSync(privateKeyPath)) { console.error(`Error: Private key file not found at path: ${privateKeyPath}`); process.exit(1); } privateKey = fs.readFileSync(privateKeyPath, 'utf8'); console.log('Using private key from file path.'); } else { console.error('Error: Neither VONAGE_APPLICATION_PRIVATE_KEY_CONTENT nor VONAGE_APPLICATION_PRIVATE_KEY_PATH is set.'); process.exit(1); } // Initialize Vonage Messages Client const messagesClient = new Messages({ apiKey: process.env.VONAGE_API_KEY, apiSecret: process.env.VONAGE_API_SECRET, applicationId: process.env.VONAGE_APPLICATION_ID, privateKey: privateKey, // Use the key content string });Update your
.envto use either approach:dotenv# Option 1: Use environment variable (preferred for containers) VONAGE_APPLICATION_PRIVATE_KEY_CONTENT="-----BEGIN PRIVATE KEY-----\nYOUR_KEY_CONTENT_HERE\n-----END PRIVATE KEY-----" # Option 2: Use file path (for local development) VONAGE_APPLICATION_PRIVATE_KEY_PATH=./private.key
8. Caveats and Troubleshooting
-
US A2P Focus and 10DLC Requirement: Vonage MMS via the Messages API is primarily designed for Application-to-Person (A2P) use cases originating from US 10DLC, Toll-Free, or Short Code numbers to US recipients. 10DLC registration is mandatory for all SMS and MMS traffic to the United States. All US geographic numbers are MMS-capable but must be 10DLC enabled first. The registration process involves brand verification by The Campaign Registry (TCR) and campaign registration, which may take several days or weeks. For non-US use cases, consult Vonage's pricing details and country-specific documentation, as MMS support, sender ID requirements, and regulations vary significantly by region.
-
Virtual Number Limitations: You generally cannot reliably send MMS messages between two Vonage virtual numbers using the API. Always test sending to real mobile phone numbers provided by carriers.
-
Publicly Accessible Image URL: The
imageUrlmust resolve directly to the image file (.jpg,.jpeg,.png,.gif) and be publicly accessible to Vonage's servers, meaning it cannot require authentication or be behind a firewall that blocks external access. Services like AWS S3 (with public read permissions), Cloudinary, or public file hosts work. Test the URL in an incognito browser window or usingcurlfirst.AWS S3 Bucket Policy Example:
json{ "Version": "2012-10-17", "Statement": [ { "Sid": "PublicReadForMMSImages", "Effect": "Allow", "Principal": "*", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::your-bucket-name/mms-images/*" } ] }Store MMS images in a dedicated
mms-images/folder within your bucket and apply this policy to allow public read access. Ensure the bucket does not block public access in the bucket settings. -
File Size and Format Limits: Vonage MMS has a maximum file size limit of 600 KB, though actual limits vary by carrier, country, and device. Although MMS can be successfully delivered to the carrier, it can be rejected by the device. Supported image formats for 10DLC, Toll-Free Numbers, and Short Codes: JPG, JPEG, PNG, GIF. The Vonage Messages API also supports video (.mp4 recommended), audio (.mp3 recommended), and vCard (.vcf) files via MMS. For larger media files (up to 100 MB), consider using RCS (Rich Communication Services) messaging instead. Source: Vonage MMS FAQ.
-
10DLC Limitations: 10DLC does not support Videotron for MMS. Only +1 10-Digit Long Codes may be linked to 10DLC campaigns; Short Codes and Toll-Free Numbers require separate registration processes.
-
Authentication Errors (401 Unauthorized):
- Double-check
VONAGE_API_KEY,VONAGE_API_SECRET,VONAGE_APPLICATION_IDin your.envfile. - Verify
VONAGE_APPLICATION_PRIVATE_KEY_PATHpoints correctly to theprivate.keyfile, the file exists, is readable by the Node.js process, and its content is the correct private key associated with the Application ID. Check server logs for "Private key file not found" errors. - Confirm the Vonage number (
VONAGE_FROM_NUMBER) is correctly linked to theVONAGE_APPLICATION_IDin the Vonage dashboard. - Verify API Settings > Default SMS Setting is set to Messages API in the Vonage dashboard.
- Double-check
-
Number Provisioning/Permission Errors (e.g., 403 Forbidden, "Non White-listed Destination"):
- Ensure the
VONAGE_FROM_NUMBERis MMS capable (all US geographic numbers are MMS-capable but check that 10DLC registration is complete). - Verify 10DLC registration is complete and approved. Incomplete or pending 10DLC registration will prevent message delivery.
- Confirm the number is correctly linked to the Application ID used for authentication and to your approved 10DLC campaign.
- Verify the recipient number (
to) is a valid, reachable US number (especially check country code and E.164 format). Some destinations might require pre-registration (whitelisting) depending on regulations or account setup. - Check Vonage status page for any ongoing incidents.
- Ensure the
-
Invalid Image URL Errors: Vonage API errors might indicate issues fetching ("Cannot get media URL") or processing the image. Verify the URL's public accessibility, format (direct link to image), ensure it's not blocked by firewalls or requires login, and confirm the file size is under 600 KB.
-
Rate Limits (429 Too Many Requests): Vonage enforces the following rate limits for the Messages API:
Account Type Rate Limit Free/Trial 1 request/second Paid 10 requests/second Enterprise Custom (contact support) If sending many messages quickly, you might hit these limits. Implement appropriate delays or use a queueing system (e.g., Bull, BullMQ with Redis) if necessary. The
@fastify/rate-limitplugin helps prevent accidental self-inflicted rate limiting.
Frequently Asked Questions About Vonage MMS with Node.js and Fastify
What Node.js version do I need for Vonage MMS with Fastify?
Use Node.js v20.x (LTS, maintenance until April 2027) or v22.x (Active LTS) for building MMS applications with Vonage and Fastify. Node.js v18 reached end-of-life in April 2025 and should not be used for new projects. Fastify v4 or higher is required (v3 and lower are EOL). While Fastify v4+ technically supports Node.js v14+, v20+ is strongly recommended for long-term support, security updates, and compatibility with the latest Vonage SDK features.
What is the MMS file size limit for Vonage Messages API?
The maximum MMS file size limit is 600 KB for Vonage Messages API, though actual limits vary by carrier, country, and device. Although MMS can be successfully delivered to the carrier, it may be rejected by the receiving device if it exceeds carrier-specific limits. For 10DLC, Toll-Free Numbers, and Short Codes, Vonage supports JPG, JPEG, PNG, and GIF image formats. The API also supports video (.mp4 recommended), audio (.mp3 recommended), and vCard (.vcf) files. For larger media files up to 100 MB, consider using RCS (Rich Communication Services) messaging instead.
Is 10DLC registration required for sending MMS with Vonage?
Yes, 10DLC registration is mandatory for all SMS and MMS traffic to the United States. All US geographic numbers are MMS-capable but must be 10DLC enabled before sending messages. The registration process involves 4 steps: (1) Register your 10DLC brand (declare your company) – $4.00 one-time fee, (2) Brand verification by The Campaign Registry (TCR) – included in brand fee, (3) Register a 10DLC campaign (describe your messaging use case) – $15.00/month recurring fee, and (4) Link your US number to your 10DLC campaign – no additional fee. The process requires manual reviews and may take several days or even weeks to complete. Only +1 10-Digit Long Codes may be linked to 10DLC campaigns; Short Codes and Toll-Free Numbers have separate registration processes.
How do I authenticate with the Vonage Messages API for MMS?
The Vonage Messages API v1 (which supports MMS) requires JWT-based authentication using a combination of 4 credentials: API Key, API Secret, Application ID, and Private Key. Create a Vonage Application in your dashboard to generate an Application ID and download the private.key file. Store these credentials securely in environment variables using a .env file. The @vonage/messages SDK handles JWT token generation automatically when you initialize the Messages client with these credentials. Never hardcode credentials in your source code or commit the private.key file to version control.
Can I send MMS between two Vonage virtual numbers?
No, you generally cannot reliably send MMS messages between two Vonage virtual numbers using the API. MMS is designed for Application-to-Person (A2P) messaging from Vonage numbers to real mobile phone numbers provided by carriers like AT&T, Verizon, or T-Mobile. Always test MMS sending to actual carrier-provided mobile numbers rather than between virtual numbers. For non-US use cases, consult Vonage's country-specific documentation, as MMS support, sender ID requirements, and regulations vary significantly by region.
What image URL format does Vonage MMS require?
The imageUrl parameter must be a publicly accessible direct link to an image file (JPG, JPEG, PNG, or GIF format) that does not require authentication or is blocked by firewalls. The URL must be accessible to Vonage's servers without login credentials or IP restrictions. Test the URL in an incognito browser window or using curl to verify public accessibility.
AWS S3 Example: Create a bucket with public read permissions on specific objects:
# Upload image with public read ACL
aws s3 cp image.jpg s3://your-bucket/mms-images/image.jpg --acl public-read
# Get the public URL
https://your-bucket.s3.amazonaws.com/mms-images/image.jpgPresigned URL Example (temporary access):
const AWS = require('aws-sdk');
const s3 = new AWS.S3();
const params = {
Bucket: 'your-bucket',
Key: 'mms-images/image.jpg',
Expires: 3600 // URL valid for 1 hour
};
const url = s3.getSignedUrl('getObject', params);
// Use this URL as imageUrl in MMS requestServices like Cloudinary, ImgBB, or other public file hosts also work well. Ensure the image file is under 600 KB and uses http:// or https:// protocol.
How do I handle Vonage MMS errors in Fastify?
Implement comprehensive error handling using Fastify's schema validation and try-catch blocks. Fastify's JSON Schema validation automatically returns detailed 400 errors for malformed requests (missing required fields, incorrect E.164 phone number formats, invalid URLs) before your route handler executes.
Error Response Parsing Example:
fastify.post('/send-mms', { schema: sendMmsSchema }, async (request, reply) => {
try {
const vonageResponse = await sendMms(to, from, imageUrl, caption);
reply.status(200).send({ message: 'MMS sent successfully.', messageId: vonageResponse.message_uuid });
} catch (error) {
// Parse Vonage-specific error codes
const vonageError = error.response?.data;
let statusCode = 500;
let message = 'Failed to send MMS.';
if (vonageError) {
if (vonageError.type?.includes('unauthorized') || vonageError.status === 401) {
statusCode = 401;
message = 'Authentication failed. Check your Vonage credentials.';
} else if (vonageError.type?.includes('forbidden') || vonageError.status === 403) {
statusCode = 403;
message = 'Permission denied. Verify 10DLC registration is complete.';
} else if (vonageError.type?.includes('rate-limit') || vonageError.status === 429) {
statusCode = 429;
message = 'Rate limit exceeded. Retry after 1 minute.';
}
}
fastify.log.error({ error: vonageError || error.message, recipient: to });
reply.status(statusCode).send({ error: message, details: vonageError?.detail });
}
});Log detailed errors using Fastify's built-in Pino logger for production debugging. Return structured error responses with appropriate HTTP status codes and user-friendly messages without leaking sensitive internal details.
What are the deployment best practices for Vonage MMS Fastify applications?
Production Deployment Checklist:
- Use environment variables for all credentials via AWS Secrets Manager, HashiCorp Vault, or platform-specific secret management – never commit .env or private.key files.
- Enable HTTPS using a load balancer (AWS ALB, Nginx, Caddy) that terminates SSL/TLS.
- Implement rate limiting with
@fastify/rate-limitto prevent API abuse and protect your Vonage account. - Add authentication using API keys or JWT with
@fastify/jwtand@fastify/authto secure your/send-mmsendpoint. - Configure structured logging with Pino and forward logs to CloudWatch, Datadog, or Splunk for monitoring.
- Set restrictive file permissions on private.key (readable only by application user) or load key content from environment variables in containerized environments.
- Verify 10DLC registration is complete before deployment to ensure message delivery.
Docker Configuration Example:
# Dockerfile
FROM node:20-alpine
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install production dependencies only
RUN npm ci --only=production
# Copy application code
COPY src ./src
# Create non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
# Set ownership
RUN chown -R nodejs:nodejs /app
# Switch to non-root user
USER nodejs
# Expose port
EXPOSE 3000
# Start application
CMD ["node", "src/server.js"]Docker Compose with Secrets:
# docker-compose.yml
version: '3.8'
services:
mms-app:
build: .
ports:
- "3000:3000"
environment:
NODE_ENV: production
PORT: 3000
VONAGE_API_KEY: ${VONAGE_API_KEY}
VONAGE_API_SECRET: ${VONAGE_API_SECRET}
VONAGE_APPLICATION_ID: ${VONAGE_APPLICATION_ID}
VONAGE_APPLICATION_PRIVATE_KEY_CONTENT: ${VONAGE_APPLICATION_PRIVATE_KEY_CONTENT}
VONAGE_FROM_NUMBER: ${VONAGE_FROM_NUMBER}
secrets:
- vonage_private_key
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
secrets:
vonage_private_key:
file: ./private.keyAWS ECS Task Definition Example:
{
"family": "mms-app",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "256",
"memory": "512",
"containerDefinitions": [
{
"name": "mms-app",
"image": "your-ecr-repo/mms-app:latest",
"portMappings": [
{
"containerPort": 3000,
"protocol": "tcp"
}
],
"secrets": [
{
"name": "VONAGE_API_KEY",
"valueFrom": "arn:aws:secretsmanager:region:account:secret:vonage/api-key"
},
{
"name": "VONAGE_API_SECRET",
"valueFrom": "arn:aws:secretsmanager:region:account:secret:vonage/api-secret"
},
{
"name": "VONAGE_APPLICATION_ID",
"valueFrom": "arn:aws:secretsmanager:region:account:secret:vonage/app-id"
},
{
"name": "VONAGE_APPLICATION_PRIVATE_KEY_CONTENT",
"valueFrom": "arn:aws:secretsmanager:region:account:secret:vonage/private-key"
},
{
"name": "VONAGE_FROM_NUMBER",
"valueFrom": "arn:aws:secretsmanager:region:account:secret:vonage/from-number"
}
],
"environment": [
{
"name": "NODE_ENV",
"value": "production"
},
{
"name": "PORT",
"value": "3000"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/mms-app",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "ecs"
}
},
"healthCheck": {
"command": ["CMD-SHELL", "wget --quiet --tries=1 --spider http://localhost:3000/health || exit 1"],
"interval": 30,
"timeout": 5,
"retries": 3,
"startPeriod": 60
}
}
]
}For Kubernetes deployments, use ConfigMaps for non-sensitive config and Secrets for credentials, mounted as environment variables or volumes with appropriate RBAC policies.
Frequently Asked Questions
How to send MMS messages with Node.js and Fastify?
Use the Vonage Messages API with the Fastify framework and Node.js. This involves setting up a Fastify server, integrating the Vonage Messages API, and creating an endpoint to handle MMS sending requests. You'll need a Vonage account, API credentials, and an MMS-capable number.
What is the Vonage Messages API used for?
The Vonage Messages API is a unified platform for sending various types of messages, including SMS and MMS. It simplifies the process of integrating messaging functionality into applications and offers features for different communication channels.
Why use Fastify for sending MMS messages?
Fastify is a high-performance Node.js web framework known for its speed and ease of use. Its efficiency makes it well-suited for handling API requests, such as those for sending MMS messages, with minimal overhead.
When should I use the Vonage Messages API for sending multimedia?
Use the Vonage Messages API whenever your application needs to send multimedia content like images or videos through SMS. This can be useful for various purposes such as notifications, alerts, marketing campaigns, or user engagement functionalities.
Can I send MMS messages internationally with Vonage?
Vonage's MMS service through the API is primarily focused on US numbers sending to US recipients. For international MMS, consult Vonage's documentation and pricing for specific country support and regulations, as requirements vary.
How to set up a Fastify server for MMS?
Install Fastify, the Vonage Messages SDK (`@vonage/messages`), and dotenv. Create a server file, set up routes, and configure environment variables for your Vonage credentials. This establishes the core structure for MMS functionality within your Fastify application.
What is the role of the private key in Vonage MMS setup?
The private key, downloaded when creating a Vonage application, is essential for authenticating with the Vonage Messages API. It's used in conjunction with your API key, secret, and application ID for secure communication. Keep this key secure and out of version control.
How to handle errors when sending MMS with Vonage?
Implement `try...catch` blocks in your code to handle errors during the MMS sending process. The Vonage API often provides detailed error information that can be logged for debugging. Fastify's schema validation can prevent common request errors.
Why does the image URL need to be publicly accessible for Vonage MMS?
The Vonage servers need to directly access the image from the URL you provide to include it in the MMS message. URLs behind authentication or firewalls will prevent Vonage from retrieving the image, resulting in MMS sending failures.
What are the file size limits for MMS images with Vonage?
Vonage has limits on MMS image sizes, typically around 1-5MB, although this can vary based on carrier networks. Check the official documentation for the most up-to-date limits to ensure your images send successfully.
How to troubleshoot Vonage MMS authentication errors?
Double-check that your API key, secret, Application ID, and private key path are correctly configured. Ensure the private key file exists and the sending number is linked to your Vonage application in the dashboard. Verify the default SMS setting is set to "Messages API".
What are some security best practices for sending MMS with Fastify?
Use environment variables to store sensitive credentials, implement input validation to prevent attacks, use rate limiting to protect your API, and add authentication/authorization to secure your endpoints. Always run your application behind HTTPS in production environments.
How to secure the Vonage private key?
Store the `private.key` file securely, with restrictive permissions. In production, consider loading the key content from a secure secret store instead of directly from the file system, especially in containerized deployments, to minimize security risks.
What if my Vonage MMS isn't being delivered?
Check the Vonage message logs for details on the delivery status. Verify the recipient number is valid and reachable, and confirm the sender number is provisioned for MMS and correctly linked to your application. Double-check the image URL's accessibility.