code examples
code examples
Send MMS with Vonage Messages API, Node.js & Express: Complete Guide
Build a Node.js Express API to send MMS and SMS messages using Vonage Messages API. Includes authentication, webhook handling, error management, and production deployment guidance.
Send MMS with Vonage Messages API, Node.js & Express
Build a Node.js Express API that sends MMS and SMS messages through Vonage Messages API. This guide covers project setup, authentication with JWT-based credentials, webhook handling for delivery receipts, error management, and production deployment strategies.
Create a functional Express server with two endpoints: /send-mms for multimedia messages with images and /send-sms for text messages. You'll learn to handle Vonage's authentication, implement webhook security, manage errors, and deploy a production-ready messaging service.
Project Overview and Goals
Goal: Create a Node.js Express API that programmatically sends MMS messages (with images) and SMS messages using Vonage Messages API.
Problem Solved: Build a backend messaging service that enables applications to send rich media notifications without managing carrier complexities directly. Send MMS where supported (US/Canada) and fall back to SMS for other regions.
What You'll Build:
- Express server with
/send-mmsand/send-smsendpoints - JWT-based Vonage authentication with public/private key pairs
- Webhook endpoint for delivery receipt tracking
- Input validation for E.164 phone formats and image URLs
- Production-ready error handling and logging
- Rate limiting and authentication middleware
Critical MMS Limitations (October 2025):
⚠️ Geographic Restrictions:
- MMS sending supported: US and Canada only (when sending from US/Canada long codes or toll-free numbers)
- MMS receiving: US/Canada numbers can receive MMS from most global carriers
- International MMS: Not supported via Vonage Messages API for long codes/TFNs
- SMS fallback: International recipients should use SMS channel instead
⚠️ Technical Constraints:
- File size limit: 5 MB maximum per MMS message (carrier-dependent, some limit to 1-2 MB)
- Supported formats: JPEG, PNG, GIF (most carriers); limited MP4 video support
- Image dimensions: Recommended max 1920×1920 pixels (carriers may resize)
- Caption length: 1000 characters maximum (combined with image)
- Number types: Long codes and toll-free numbers only (short codes require separate configuration)
⚠️ Carrier Compatibility:
- US carriers: AT&T, T-Mobile, Verizon support MMS with varying size limits
- MVNO carriers: Cricket, Metro, Boost support MMS (may have stricter limits)
- Landlines: Cannot receive MMS (Vonage API returns error)
- VoIP numbers: Limited MMS support (carrier-dependent)
Regulatory Requirements:
- 10DLC registration required for US long code messaging (A2P traffic)
- Toll-free verification required for toll-free numbers (3-5 business days)
- Content compliance: TCPA, CAN-SPAM, CTIA guidelines apply
- Opt-out mechanisms: Must include STOP/UNSUBSCRIBE functionality
For international messaging or broader channel support, consider Vonage's WhatsApp or Viber channels via the same Messages API.
Technologies Used:
- Node.js (v18+): JavaScript runtime environment for building server-side applications. Node.js 18 or later required for optimal Vonage SDK compatibility (October 2025).
- Express.js (v4.18+): Minimal and flexible Node.js web application framework for creating API endpoints.
- Vonage Messages API: Unified communications API from Vonage (formerly Nexmo) supporting SMS, MMS, WhatsApp, Viber, and Facebook Messenger channels.
@vonage/server-sdk(v3.12+): Official Vonage Server SDK for Node.js. As of October 2025, version 3.x is current with modern ES module support and improved TypeScript definitions.dotenv(v16.0+): Module to load environment variables from a.envfile, keeping sensitive credentials secure.- ngrok (Optional): Tool to expose local servers to the internet for webhook testing. Required only if implementing delivery receipt webhooks (Section 6).
Important Version Notes:
- Node.js 18+ required: The Vonage SDK v3.x uses modern JavaScript features requiring Node.js 18 or later
- @vonage/server-sdk v3.12+ recommended: Earlier versions (v2.x) use deprecated authentication methods
- Check current versions:
npm list @vonage/server-sdkandnode --version
System Architecture:
Your messaging system follows this request flow:
- Client request: Client application (cURL, Postman, or your app) sends HTTP POST to
/send-mmsor/send-sms - Express API receives: Server validates request and authenticates using Vonage SDK
- Vonage SDK calls:
vonage.messages.send()method contacts Vonage Messages API - Vonage processes: Vonage API sends MMS or SMS to recipient's mobile carrier
- Recipient receives: Message delivered to recipient's phone
- Webhook callback (optional): Vonage sends delivery status to your webhook endpoint
Webhook Flow (Optional):
- Vonage Messages API sends status updates to your configured URL
- ngrok (for local testing) forwards public URL to your local server
- Express server receives status at
/webhooks/statusendpoint - Your code processes delivery confirmation, failures, or other status events
Expected Outcome:
After completing this guide, you'll have:
✅ Running Express server listening on port 3000 (configurable)
✅ POST /send-mms endpoint accepting recipient, image URL, and caption
✅ POST /send-sms endpoint accepting recipient and text message
✅ Secure credential storage using environment variables
✅ Basic error handling with detailed logging
✅ (Optional) Webhook endpoint for delivery receipt tracking
Success Criteria:
- Send test MMS to US/Canada number with image display
- Send test SMS with message delivery confirmation
- View delivery status in Vonage Dashboard logs
- Receive webhook callbacks (if implemented)
Prerequisites:
- Node.js 18+ and npm (or yarn): Installed on your development machine. Node.js 18.0.0 or later required for Vonage SDK v3.x compatibility. Check version:
node --version. Download Node.js - Vonage API Account: Sign up for free if you don't have one. Vonage Signup (formerly Nexmo)
- Vonage Phone Number: A US-based virtual number capable of sending SMS and MMS, purchased through your Vonage account. Note that MMS is primarily a US/Canada feature for this type of sending.
- 10DLC Registration (US Long Codes): If using a standard 10-digit long code for Application-to-Person (A2P) messaging in the US, you must complete 10DLC brand and campaign registration through the Vonage Dashboard. Toll-free numbers require separate verification (3-5 business days). Learn more: Vonage 10DLC Registration
- Basic understanding of JavaScript and REST APIs.
- (Optional) ngrok: For testing webhook functionality locally. Download ngrok
1. Setting up the Project
Initialize your Node.js project and install the necessary dependencies.
1.1 Create Project Directory
Open your terminal and create a new directory:
mkdir vonage-mms-sender
cd vonage-mms-sender1.2 Initialize Node.js Project
Create package.json to manage dependencies:
npm init -yThis generates a default package.json with project metadata.
1.3 Install Dependencies
Install Express (web framework), Vonage SDK (API client), and dotenv (environment variables):
npm install express @vonage/server-sdk dotenvPackage purposes:
- express (v4.18+): HTTP server and routing
- @vonage/server-sdk (v3.12+): Vonage API client with JWT authentication
- dotenv (v16.0+): Loads credentials from
.envfile, keeping sensitive credentials secure.
1.4 Create Project Files
Create core application files:
# Linux/macOS
touch index.js .env .gitignore
# Windows Command Prompt
type nul > index.js
type nul > .env
type nul > .gitignore
# Windows PowerShell
New-Item index.js -ItemType File
New-Item .env -ItemType File
New-Item .gitignore -ItemType File1.5 Configure .gitignore
Prevent sensitive files from entering version control. Add these lines to .gitignore:
# Dependencies
node_modules/
# Environment variables (contains API keys)
.env
# Logs
*.log
npm-debug.log*
# OS files
.DS_Store
Thumbs.db
# Vonage private key (never commit)
private.key
*.keySecurity note: Never commit .env or private.key files. These contain credentials that authenticate your Vonage account.
Project Structure:
Your project contains these files:
| File | Purpose |
|---|---|
index.js | Main Express application code (server, routes, Vonage client) |
package.json | Project metadata and dependency list |
package-lock.json | Locked dependency versions for consistent installs |
.env | Sensitive credentials (API keys, secrets) – never commit |
.gitignore | Files to exclude from Git version control |
node_modules/ | Installed npm packages (auto-generated, ignored by Git) |
private.key | Vonage private key for JWT authentication (downloaded later) |
Key principle: Keep credentials in .env and .gitignore to prevent accidental exposure.
2. Vonage Account and Application Setup
Configure your Vonage account and create a Vonage Application for Messages API authentication. The Messages API uses JWT authentication with public/private key pairs.
Dashboard Navigation Note: As of October 2025, Vonage's dashboard layout may differ slightly. Look for "Communications APIs" or "Messages API" sections if menu items have moved.
Step 1: Access Dashboard and Locate Credentials
- Log in to Vonage API Dashboard
- Note your main account credentials (Overview page):
- API Key: 8-character alphanumeric string
- API Secret: 16-character secret key
- Store these for reference (primarily needed for account-level operations)
Step 2: Complete 10DLC Registration (US Long Codes Only)
Required for US Application-to-Person (A2P) messaging:
- Navigate to Messaging > 10DLC
- Click Register Brand:
- Enter business name, EIN/Tax ID, business address
- Describe your company (50-200 characters)
- Submit for approval
- Click Create Campaign:
- Describe use case (e.g., "Order notifications", "Appointment reminders")
- Select message samples
- Estimate monthly volume
- Wait for approval (1-3 business days typically)
- Costs: ~$4 brand registration + ~$15 campaign registration (one-time, carrier fees)
Toll-free alternative: Skip 10DLC by using toll-free numbers (1-8XX format). Requires separate verification via Vonage Support (3-5 business days).
Important: Unregistered long codes experience filtered messages, reduced throughput (1 msg/sec vs 60+), and potential number suspension.
Step 3: Purchase US/Canada Number
- Navigate to Numbers > Buy numbers
- Select country: United States (or Canada for Canadian MMS)
- Filter capabilities:
- ☑ SMS (required)
- ☑ MMS (required)
- Choose number type:
- Long code (10-digit): Requires 10DLC registration, supports MMS
- Toll-free (1-8XX): Requires verification, supports MMS, no 10DLC needed
- Purchase number
- Copy number in E.164 format (e.g.,
+12015550123) - Pricing: $1-2/month rental + per-message charges ($0.0075/SMS, $0.05/MMS typically)
Step 4: Create Vonage Application
- Navigate to Applications > Create a new application
- Enter application name (e.g., "Node MMS Sender")
- Click Generate public and private key:
- Downloads
private.keyfile to your computer - Populates public key field automatically
- Store private.key securely – never commit to Git
- Downloads
- Enable Messages capability (toggle or checkbox)
- Configure webhook URLs:
- Inbound URL:
https://example.com/webhooks/inbound(placeholder for now) - Status URL:
https://example.com/webhooks/status(delivery receipts) - HTTP Method: POST (both URLs)
- Update later with ngrok URL for local testing
- Inbound URL:
- Click Generate new application
- Copy Application ID (UUID format:
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
Step 5: Link Number to Application
- On application details page, scroll to Linked numbers
- Click Link button next to your purchased number
- Confirm linking – this associates number with Messages API application
Step 6: Set Default SMS API (Recommended)
- Navigate to Settings > API settings
- Scroll to SMS settings
- Set Default SMS Setting to Messages API (not SMS API v2)
- Click Save changes
This ensures Vonage SDK uses the modern Messages API by default.
Credentials Summary:
Store these values – you'll use them in .env configuration:
- ✅ VONAGE_NUMBER: Your purchased number (E.164 format:
+12015550123) - ✅ VONAGE_APPLICATION_ID: Application UUID
- ✅ VONAGE_PRIVATE_KEY_PATH: Path to
private.keyfile - ✅ VONAGE_API_KEY: 8-character API key (optional for Messages API)
- ✅ VONAGE_API_SECRET: 16-character secret (optional for Messages API)
- ✅ 10DLC Status: Registered and approved (if using US long code)
3. Environment Configuration
Store credentials securely using environment variables in a .env file.
Step 1: Move Private Key to Project (Optional but Recommended)
Move the downloaded private.key file to your project root directory:
# Move private.key to project directory
mv ~/Downloads/private.key ./private.keyThis file is already listed in .gitignore to prevent accidental commits.
Step 2: Configure .env File
Open .env and add these variables with your actual credentials:
# Vonage Main Account Credentials
# Found on Dashboard > Overview page
VONAGE_API_KEY=YOUR_8_CHAR_API_KEY
VONAGE_API_SECRET=YOUR_16_CHAR_API_SECRET
# Vonage Application Credentials
# Found in Application settings
VONAGE_APPLICATION_ID=YOUR_APPLICATION_UUID
# Private Key File Path
# Path to private.key file (relative to project root)
VONAGE_PRIVATE_KEY_PATH=./private.key
# Your Vonage Phone Number
# US/Canada number in E.164 format (include +)
VONAGE_NUMBER=+12015550123
# Express Server Port
APP_PORT=3000
# Optional: API Authentication (Section 7)
# Create your own secret key for endpoint protection
# MY_SECRET_API_KEY=your_chosen_secret_key_hereVariable Explanations:
| Variable | Required? | Purpose |
|---|---|---|
VONAGE_API_KEY | Optional | Main account credential (8 characters) |
VONAGE_API_SECRET | Optional | Main account secret (16 characters) |
VONAGE_APPLICATION_ID | Required | Application UUID for Messages API |
VONAGE_PRIVATE_KEY_PATH | Required | Path to JWT private key file |
VONAGE_NUMBER | Required | Sender ID (your Vonage number in E.164 format) |
APP_PORT | Optional | Express server port (default: 3000) |
MY_SECRET_API_KEY | Optional | Your custom API key for endpoint authentication |
Security Requirements:
⚠️ Never commit .env to Git – it contains authentication credentials
⚠️ Never commit private.key to Git – it authenticates all API requests
⚠️ Use strong secrets – generate with openssl rand -hex 32 for production
⚠️ Restrict file permissions – run chmod 600 private.key on Unix systems
⚠️ Production deployment – use platform environment variables (Heroku, AWS, Vercel) instead of .env files
E.164 Format Requirement:
Phone numbers must use E.164 international format:
- Format:
+[country code][number] - US example:
+12015550123(country code 1, area code 201, number 5550123) - Canada example:
+14165550123(country code 1, area code 416, number 5550123) - No spaces, dashes, or parentheses
4. Implement Express Server
Set up the basic Express server structure.
Edit your index.js file:
// index.js
require('dotenv').config(); // Load environment variables from .env file
const express = require('express');
// --- Basic Server Setup ---
const app = express();
const port = process.env.APP_PORT || 3000; // Use port from .env or default to 3000
// --- Middleware ---
// Enable parsing of JSON request bodies
app.use(express.json());
// Enable parsing of URL-encoded request bodies
app.use(express.urlencoded({ extended: true }));
// --- Basic Route ---
app.get('/', (req, res) => {
res.send('Vonage MMS/SMS Sender API is running!');
});
// --- Placeholder for API Endpoints (We will add these next) ---
// app.post('/send-mms', ...);
// app.post('/send-sms', ...);
// app.post('/webhooks/status', ...); // Optional for DLRs
// --- Start Server ---
app.listen(port, () => {
// Log server start with current date
const startDate = new Date().toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });
console.log(`Server listening at http://localhost:${port} on ${startDate}`);
});
// --- Basic Error Handling (Example - enhance as needed) ---
app.use((err, req, res, next) => {
console.error(""Unhandled Error:"", err.stack);
res.status(500).send('Something broke!');
});
// Export app for potential testing frameworks
module.exports = app;Explanation:
require('dotenv').config();: Loads variables from the.envfile intoprocess.env.express(): Creates an Express application instance.app.use(express.json()),app.use(express.urlencoded(...)): Middleware to parse incoming JSON and URL-encoded request payloads, making them available asreq.body.app.get('/'): A simple route to confirm the server is running.app.listen(): Starts the server and makes it listen for connections on the specified port.- Basic error handling middleware is added as a safety net.
You can run this basic server now:
node index.jsYou should see the ""Server listening..."" message in your console. Open http://localhost:3000 in your browser to see the welcome message. Press Ctrl+C to stop the server.
5. Implement Sending Logic (MMS & SMS)
Now, let's integrate the Vonage SDK and create the API endpoints for sending messages.
First, initialize the Vonage client near the top of index.js, after loading dotenv:
// index.js
require('dotenv').config();
const express = require('express');
const { Vonage } = require('@vonage/server-sdk'); // Import Vonage
const fs = require('fs'); // Import Node.js file system module
// --- Vonage Client Initialization ---
let vonage;
try {
// Ensure the private key path exists before initializing
const privateKeyPath = process.env.VONAGE_PRIVATE_KEY_PATH;
if (!privateKeyPath || !fs.existsSync(privateKeyPath)) {
throw new Error(`Private key file not found at path: ${privateKeyPath}. Please check your VONAGE_PRIVATE_KEY_PATH environment variable.`);
}
vonage = new Vonage({
apiKey: process.env.VONAGE_API_KEY, // Good practice to include, though Messages uses App ID/Key primarily
apiSecret: process.env.VONAGE_API_SECRET,
applicationId: process.env.VONAGE_APPLICATION_ID,
privateKey: privateKeyPath // Provide the path here
}, {
// Optional: Custom logger, user agent, etc.
// logger: console // Example: Use console for logging SDK actions
});
console.log(""Vonage client initialized successfully."");
} catch (error) {
console.error(""FATAL: Failed to initialize Vonage client:"", error.message);
// Optionally exit if Vonage client is critical and cannot be initialized
// process.exit(1);
}
// --- Basic Server Setup ---
// ... (rest of the server setup code from Section 4) ...Now, add the API endpoints below the basic / route and before the app.listen call:
// index.js
// ... (Vonage client initialization and basic server setup) ...
// --- API Endpoints ---
/**
* @route POST /send-mms
* @description Sends an MMS message with an image.
* @access Public (Add auth middleware in production)
* @requestBody { "to": "E.164_PHONE_NUMBER", "imageUrl": "PUBLIC_IMAGE_URL", "caption": "Optional text caption" }
* @response 200 { "success": true, "message_uuid": "VONAGE_MESSAGE_UUID" }
* @response 400 { "success": false, "error": "Description of validation error" }
* @response 500 { "success": false, "error": "Description of sending error" }
*/
app.post('/send-mms', async (req, res) => {
if (!vonage) {
return res.status(500).json({ success: false, error: "Vonage client not initialized." });
}
const { to, imageUrl, caption } = req.body;
const from = process.env.VONAGE_NUMBER;
// Basic Input Validation
if (!to || !imageUrl) {
return res.status(400).json({ success: false, error: "Missing required fields: 'to' and 'imageUrl'" });
}
// Validate E.164 format
const e164Pattern = /^\+?1[2-9]\d{9}$/; // US/Canada format
if (!e164Pattern.test(to.replace(/[\s\-()]/g, ''))) {
return res.status(400).json({
success: false,
error: "Invalid 'to' phone number. MMS requires US/Canada number in E.164 format (e.g., +12015550123)"
});
}
// Validate image URL format and accessibility
if (!imageUrl.match(/^https?:\/\/.+\.(jpg|jpeg|png|gif)(\?.*)?$/i)) {
return res.status(400).json({
success: false,
error: "Invalid imageUrl. Must be publicly accessible HTTP/HTTPS URL ending in .jpg, .jpeg, .png, or .gif"
});
}
console.log(`Attempting to send MMS to: ${to} from: ${from} with image: ${imageUrl}`);
try {
const resp = await vonage.messages.send({
message_type: "image",
image: {
url: imageUrl,
caption: caption || "" // Use provided caption or empty string
},
to: to,
from: from,
channel: "mms"
});
console.log("MMS Sent Successfully:", resp);
res.status(200).json({ success: true, message_uuid: resp.message_uuid });
} catch (error) {
console.error("Error sending MMS:", error?.response?.data || error.message); // Log detailed error if available
// Provide helpful error messages based on common issues
let errorMessage = "Failed to send MMS.";
// Handle specific Vonage API error codes (October 2025 Messages API)
if (error?.response?.data?.type) {
const errorType = error.response.data.type;
switch (errorType) {
case 'https://developer.vonage.com/api-errors/messages#invalid-params':
errorMessage = "Invalid MMS parameters. Check image URL accessibility and phone number format.";
break;
case 'https://developer.vonage.com/api-errors/messages#unsupported-channel':
errorMessage = "MMS not supported for this destination. Try SMS channel or check number capabilities.";
break;
case 'https://developer.vonage.com/api-errors/messages#throttled':
errorMessage = "Rate limit exceeded. Slow down request frequency.";
break;
default:
if (error.response.data.title) {
errorMessage = `Failed to send MMS: ${error.response.data.title} - ${error.response.data.detail || ''}`;
}
}
} else if (error.message) {
errorMessage = `Failed to send MMS: ${error.message}`;
}
res.status(error?.response?.status || 500).json({ success: false, error: errorMessage });
}
});
/**
* @route POST /send-sms
* @description Sends a standard SMS text message.
* @access Public (Add auth middleware in production)
* @requestBody { ""to"": ""E.164_PHONE_NUMBER"", ""text"": ""Your message content"" }
* @response 200 { ""success"": true, ""message_uuid"": ""VONAGE_MESSAGE_UUID"" }
* @response 400 { ""success"": false, ""error"": ""Description of validation error"" }
* @response 500 { ""success"": false, ""error"": ""Description of sending error"" }
*/
app.post('/send-sms', async (req, res) => {
if (!vonage) {
return res.status(500).json({ success: false, error: ""Vonage client not initialized."" });
}
const { to, text } = req.body;
const from = process.env.VONAGE_NUMBER;
// Basic Input Validation
if (!to || !text) {
return res.status(400).json({ success: false, error: ""Missing required fields: 'to' and 'text'"" });
}
// Add more robust validation
console.log(`Attempting to send SMS to: ${to} from: ${from} with text: ""${text}""`);
try {
const resp = await vonage.messages.send({
message_type: ""text"",
text: text,
to: to,
from: from,
channel: ""sms""
});
console.log(""SMS Sent Successfully:"", resp);
res.status(200).json({ success: true, message_uuid: resp.message_uuid });
} catch (error) {
console.error(""Error sending SMS:"", error?.response?.data || error.message);
let errorMessage = ""Failed to send SMS."";
if (error?.response?.data?.title) {
errorMessage = `Failed to send SMS: ${error.response.data.title} - ${error.response.data.detail || ''}`;
} else if (error.message) {
errorMessage = `Failed to send SMS: ${error.message}`;
}
res.status(error?.response?.status || 500).json({ success: false, error: errorMessage });
}
});
// --- Start Server ---
// ... (app.listen call) ...
// --- Basic Error Handling ---
// ... (app.use error handler) ...MMS Image Requirements and Best Practices (October 2025):
Supported Image Formats:
- JPEG/JPG: Recommended for photos (best compression, universal support)
- PNG: Supported for graphics with transparency (larger file sizes)
- GIF: Supported for simple animations (size limits apply per frame)
- Animated GIF: Limited support (some carriers strip animation, show first frame only)
- HEIC/HEIF: Not supported (iOS native format requires conversion to JPEG)
- WebP: Not widely supported by carriers (convert to JPEG/PNG)
Size and Dimension Limits:
- File size: 5 MB maximum (Vonage API limit)
- Carrier limits: Most US carriers limit to 1-2 MB (Verizon: 1 MB, AT&T: 2 MB, T-Mobile: 1 MB)
- Recommended size: Keep under 1 MB for universal carrier compatibility
- Dimensions: Maximum 1920×1920 pixels recommended
- Aspect ratio: Carriers may resize/crop images; use standard ratios (1:1, 4:3, 16:9)
Image URL Requirements:
- Public accessibility: URL must be publicly accessible (no authentication required)
- HTTPS recommended: Some carriers require HTTPS for image URLs
- Direct image URL: Must return image content-type header (not HTML page with image)
- Response time: Vonage fetches image when sending; slow URLs may timeout (5-second limit)
- CDN best practice: Use CDN-hosted images for reliability and speed
Caption Guidelines:
- Maximum length: 1000 characters (combined text + image)
- Carrier variations: Some carriers display caption separately, others inline
- Encoding: UTF-8 supported (emoji and international characters work)
- Fallback: If MMS fails, caption-only SMS may be sent automatically
Common MMS Failures and Solutions:
- Image too large: Resize/compress to <1 MB before sending
- URL inaccessible: Ensure image URL returns 200 OK and image content-type
- Unsupported format: Convert HEIC/WebP to JPEG before uploading
- Landline destination: MMS not supported; use SMS fallback
- International destination: MMS limited to US/Canada; use WhatsApp or SMS
Explanation:
- Vonage Client: The
vonageinstance is initialized using credentials from.env. A check is added to ensure the private key file exists. Error handling is included for initialization failure. - Async/Await: The route handlers are
asyncfunctions to allow usingawaitfor the asynchronousvonage.messages.sendcall. - Input Validation: Basic checks ensure required fields (
to,imageUrl/text) are present in the request body (req.body). Production applications need more robust validation (e.g., using libraries likejoiorexpress-validator). vonage.messages.send(): This is the core function from the Vonage SDK used to send messages via the Messages API.message_type: Set to"image"for MMS or"text"for SMS.image: An object containing theurl(must be publicly accessible) and an optionalcaptionfor MMS. Supported image types are typically JPEG, PNG, GIF. Check Vonage documentation for specifics and size limits.text: The message content for SMS.to: The recipient's phone number in E.164 format (e.g.,+14155550101).from: Your Vonage virtual number (VONAGE_NUMBER).channel: Explicitly set to"mms"or"sms".
- Response: On success, it returns a 200 status with the
message_uuidprovided by Vonage. On failure, it logs the error and returns an appropriate status code (400 for bad input, 500 or Vonage's error status for sending issues) with an error message. - Error Handling: The
try...catchblock handles errors during the API call. It attempts to log detailed error information from Vonage (error.response.data) if available.
6. Handling Webhooks (Optional - Delivery Receipts)
To confirm message delivery status, Vonage sends information to the ""Status URL"" you configured in your Vonage Application. Let's set up ngrok and a basic webhook handler.
-
Start ngrok: If you haven't already, download and set up ngrok. Run it to expose your local server's port (e.g., 3000).
bash./ngrok http 3000 # Or on Windows: ngrok.exe http 3000ngrok will provide a forwarding URL (e.g.,
https://abcd-1234.ngrok.io). Copy thehttpsversion. -
Update Vonage Application Status URL:
- Go back to your application settings in the Vonage Dashboard.
- Edit the Messages capability.
- Paste your ngrok forwarding URL into the Status URL field, appending a path like
/webhooks/status. Example:https://abcd-1234.ngrok.io/webhooks/status. - Set the HTTP Method for the Status URL to
POST. - Save the changes.
-
Add Webhook Endpoint in
index.js: Add this route handler before theapp.listencall:javascript// index.js // ... (other routes) ... /** * @route POST /webhooks/status * @description Receives message status updates (DLRs) from Vonage. * @access Public (Verify requests in production using JWT or signatures) * @requestBody Vonage Status Webhook Payload * @response 200 OK */ app.post('/webhooks/status', (req, res) => { console.log(""--- Status Webhook Received ---""); console.log(""Timestamp:"", new Date().toISOString()); console.log(""Body:"", JSON.stringify(req.body, null, 2)); // Log the entire status update // Add logic here to process the status (e.g., update database) // Example: Log key info if (req.body.message_uuid && req.body.status) { console.log(`Status for ${req.body.message_uuid}: ${req.body.status}`); } // Vonage expects a 200 OK response to acknowledge receipt res.status(200).end(); }); // --- Start Server --- // ... (app.listen call) ...
Explanation:
- This endpoint listens for
POSTrequests at the path you configured in Vonage (/webhooks/status). - It logs the received status update payload. The payload structure contains details like
message_uuid,status(delivered,failed,rejected, etc.),timestamp, and potentially error codes. Refer to the Vonage Messages API documentation for the exact payload structure. - Crucially, it sends back a
200 OKstatus immediately. If Vonage doesn't receive a 200 OK, it will retry sending the webhook, leading to duplicate processing. - Security: In production, you must secure this webhook endpoint. Vonage provides mechanisms like Signed Webhooks (using JWT) to verify that incoming requests genuinely originated from Vonage. Implementing webhook signature verification is beyond this basic guide but essential for production.
Restart your server (node index.js) after adding the webhook. Now, when you send messages, status updates should be logged to your console via ngrok.
7. Security Considerations
While this guide focuses on basic functionality, production applications require stronger security:
- Input Validation: Implement thorough validation for all inputs (
tonumber format,imageUrlvalidity and source,textlength and content). Libraries likejoiorexpress-validatorare recommended.javascript// Example using basic regex for E.164 (adapt as needed) const e164Pattern = /^\+[1-9]\d{1,14}$/; if (!e164Pattern.test(to)) { return res.status(400).json({ success: false, error: ""Invalid 'to' phone number format. Use E.164 (e.g., +12125551234)."" }); } - Authentication/Authorization: Protect your API endpoints. Implement API keys, JWT tokens, OAuth, or other mechanisms to ensure only authorized clients can send messages.
javascript
// Example: Simple API Key Check (Replace with robust auth) app.use((req, res, next) => { const apiKey = req.headers['x-api-key']; // Make sure MY_SECRET_API_KEY is set in your .env file if (apiKey && process.env.MY_SECRET_API_KEY && apiKey === process.env.MY_SECRET_API_KEY) { next(); } else { // Avoid logging unauthorized attempts unless debugging, could be noisy console.warn('Unauthorized API attempt detected.'); res.status(401).json({ success: false, error: 'Unauthorized' }); } }); - Rate Limiting: Prevent abuse by limiting the number of requests a client can make within a certain time window. Use middleware like
express-rate-limit.bashnpm install express-rate-limitjavascript// In index.js const rateLimit = require('express-rate-limit'); const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // Limit each IP to 100 requests per windowMs message: 'Too many requests from this IP, please try again after 15 minutes' }); // Apply to all requests // app.use(limiter); // Or apply specifically to sending routes app.use('/send-mms', limiter); app.use('/send-sms', limiter); - Webhook Security: As mentioned, verify incoming webhooks using Vonage's signing mechanisms (JWT).
- Secure Key Management: Never hardcode credentials. Use environment variables and secure storage mechanisms provided by your deployment platform. Ensure the
private.keyfile has restricted permissions on the server. Consider loading the key content directly into an environment variable instead of relying on a file path in production.
8. Error Handling and Logging
Robust error handling and logging are vital for debugging and monitoring.
- Consistent Error Responses: Standardize your error response format (e.g.,
{ success: false, error: ""Error message"", code: ""ERROR_CODE"" }). - Detailed Logging: Use a dedicated logging library like
Winstonfor production instead ofconsole.log. Configure log levels (info, warn, error) and output destinations (file, console, external service).bashnpm install winstonjavascript// Example: Basic Winston setup const winston = require('winston'); const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', // Control level via environment format: winston.format.combine( winston.format.timestamp(), winston.format.json() // Use JSON format for structured logging ), transports: [ new winston.transports.Console({ format: winston.format.combine( winston.format.colorize(), // Add color for console readability winston.format.simple() ) }) // Add file transport for production (recommended) // new winston.transports.File({ filename: 'error.log', level: 'error' }), // new winston.transports.File({ filename: 'combined.log' }), ], }); // Replace console.log/error with logger.info, logger.warn, logger.error // Example: logger.info(`Server listening at http://localhost:${port}`); // Example: logger.error(""Error sending MMS:"", { message: error.message, response: error?.response?.data }); - Specific Vonage Errors: Handle specific error codes or messages returned by the Vonage API for more granular retry logic or user feedback (e.g., invalid number format, insufficient funds, carrier violations). Check the
error.response.dataobject. - Retry Mechanisms: For transient network issues or temporary Vonage service problems, implement retry logic (e.g., using libraries like
async-retry) with exponential backoff for sending requests. Be cautious not to retry indefinitely or for non-recoverable errors.
9. Testing and Verification
Test your endpoints thoroughly.
-
Start Your Server:
bashnode index.js -
Send Test MMS (using cURL): Replace placeholders with your test recipient number (must be whitelisted if your Vonage account is in trial mode) and a valid public image URL.
bashcurl -X POST http://localhost:3000/send-mms \ -H ""Content-Type: application/json"" \ -H ""x-api-key: YOUR_CHOSEN_SECRET_API_KEY"" \ -d '{ ""to"": ""+1YOUR_TEST_RECIPIENT_NUMBER"", ""imageUrl"": ""https://placekitten.com/200/300"", ""caption"": ""Hello from Node/Vonage!"" }'(Note: Add the
-H ""x-api-key: ...""header if you implemented the example security middleware from Section 7)Expected Success Response:
json{ ""success"": true, ""message_uuid"": ""xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"" }Check the recipient's phone for the MMS message. Check your console logs for success messages.
-
Send Test SMS (using cURL):
bashcurl -X POST http://localhost:3000/send-sms \ -H ""Content-Type: application/json"" \ -H ""x-api-key: YOUR_CHOSEN_SECRET_API_KEY"" \ -d '{ ""to"": ""+1YOUR_TEST_RECIPIENT_NUMBER"", ""text"": ""This is a test SMS from Node/Vonage."" }'(Note: Add the
-H ""x-api-key: ...""header if you implemented the example security middleware from Section 7)Expected Success Response:
json{ ""success"": true, ""message_uuid"": ""yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"" }Check the recipient's phone for the SMS message. Check your console logs.
-
Test Error Cases:
- Send requests with missing fields (
to,imageUrl,text). - Send requests with invalid phone number formats.
- Send requests with invalid image URLs (for MMS).
- Send requests without the API key (if implemented) or with an incorrect one.
- Temporarily modify
.envwith incorrect Vonage credentials to trigger authentication errors. - Check console logs for detailed error messages.
- Send requests with missing fields (
-
Verify Webhooks (if configured):
- Check the console where
node index.jsis running for ""Status Webhook Received"" logs after sending messages. - You can also inspect requests in the ngrok web interface (usually
http://127.0.0.1:4040).
- Check the console where
10. Troubleshooting and Caveats
MMS Geographic and Technical Limitations:
- MMS US/Canada Only: Vonage MMS sending via long codes/Toll-Free Numbers is supported for sending to US and Canadian numbers from US/Canada numbers as of October 2025. For international messaging, use SMS channel or consider Vonage's WhatsApp/Viber channels via the same Messages API.
- Carrier-Specific Limits: Each US carrier enforces different MMS size limits:
- Verizon: 1 MB maximum (600 KB recommended)
- AT&T: 2 MB maximum (1 MB recommended)
- T-Mobile: 1 MB maximum (600 KB recommended)
- Sprint (now T-Mobile): 1 MB maximum
- 10DLC Registration Required (US Long Codes): As of October 2025, all US long code A2P messaging requires completed 10DLC brand and campaign registration. Unregistered long codes will experience:
- Filtered/blocked messages
- Reduced throughput (1 message/second vs 60+ for registered)
- Higher costs per message
- Potential number suspension Complete registration at: Vonage Dashboard > Messaging > 10DLC
Trial Account Limitations:
- Verified Numbers Only: If your Vonage account is new/in trial mode, you can typically only send messages to phone numbers you have verified and added to your account's whitelist (Vonage Dashboard > Settings > Verified Numbers).
- Credit Limits: Trial accounts may have sending limits or require a minimum credit balance. Check Dashboard > Billing.
- No 10DLC Registration: Trial accounts cannot complete 10DLC registration until upgraded to paid status.
Common Error Codes (Vonage Messages API - October 2025):
| Error Type | HTTP Status | Cause | Solution |
|---|---|---|---|
invalid-params | 400 | Missing/invalid required fields | Check to, from, imageUrl fields; verify E.164 format |
unauthorized | 401 | Invalid Application ID or private key | Verify VONAGE_APPLICATION_ID and private.key file path |
unsupported-channel | 400 | MMS not supported for destination | Destination may be landline, international, or VoIP; use SMS |
media-invalid | 400 | Image URL inaccessible or invalid format | Ensure image URL returns 200 OK with image content-type |
media-too-large | 400 | Image exceeds size limit | Compress image to <1 MB; check carrier-specific limits |
throttled | 429 | Rate limit exceeded | Implement exponential backoff; check 10DLC throughput limits |
insufficient-balance | 402 | Account has insufficient credit | Add credit to Vonage account (Dashboard > Billing) |
spam-detected | 400 | Message flagged as spam | Review message content; ensure opt-in consent; check 10DLC campaign |
Debugging Steps:
-
Check Vonage Dashboard Logs:
- Navigate to Dashboard > Messaging > Logs
- View detailed delivery status, error codes, timestamps
- Filter by message_uuid for specific messages
-
Verify Number Capabilities:
- Dashboard > Numbers > Your Numbers
- Confirm SMS and MMS checkboxes are enabled
- Verify number is linked to your Application
-
Test Image URL Accessibility:
bashcurl -I https://your-image-url.com/image.jpg # Should return: HTTP/1.1 200 OK # Content-Type: image/jpeg -
Check 10DLC Status (US Long Codes):
- Dashboard > Messaging > 10DLC
- Verify brand status: "Approved"
- Verify campaign status: "Active"
- Processing time: 1-3 business days typically
-
Validate E.164 Format:
javascript// US/Canada E.164 validation const isValidE164 = /^\+1[2-9]\d{9}$/.test(phoneNumber); console.log(`Number ${phoneNumber} is ${isValidE164 ? 'valid' : 'invalid'} E.164 format`); -
Enable SDK Debug Logging:
javascript// Add to Vonage client initialization vonage = new Vonage({ // ... credentials ... }, { logger: console, // Logs all SDK HTTP requests/responses debug: true // Additional debug output });
Production Deployment Checklist:
- 10DLC registration completed and approved (US long codes)
- Toll-free verification completed (if using toll-free)
- Webhook URLs configured with production domain (not ngrok)
- Rate limiting implemented (express-rate-limit)
- Error monitoring configured (Sentry, DataDog, etc.)
- Image hosting on reliable CDN with HTTPS
- Input validation library added (joi, express-validator)
- Authentication middleware protecting endpoints
- Environment variables set on hosting platform
- Private key stored securely (not in repository)
- Logging configured with Winston or similar
- Retry logic implemented for transient failures
- Opt-out/STOP mechanism implemented (TCPA compliance)
- Message templates reviewed for CTIA compliance
Frequently Asked Questions
How to send MMS messages with Node.js and Express
Use the Vonage Messages API with the @vonage/server-sdk. Set up an Express route that takes the recipient's number, image URL, and an optional caption, then use vonage.messages.send() to send the MMS message.
What is the Vonage Messages API?
The Vonage Messages API is a unified platform that lets you send various types of messages like SMS, MMS, WhatsApp, and more through a single API. It simplifies communication across multiple channels.
Why does Vonage MMS primarily work in the US?
MMS functionality through long codes and toll-free numbers is largely a US/Canada-centric feature due to carrier and market dynamics in those regions. SMS, however, offers much broader global reach through the Vonage API.
When should I use the Vonage Server SDK?
Using the Vonage Server SDK simplifies the process of sending MMS messages, which is primarily supported in the US market, and text SMS messages, allowing you to add robust messaging features to your applications seamlessly. Use it whenever you want to integrate Vonage APIs into your Node.js projects.
Can I send MMS messages internationally with Vonage?
MMS sending with long codes/toll-free numbers via Vonage is primarily for US/Canada. Consult the Vonage documentation for the latest information on international MMS support and any applicable restrictions or alternative solutions.
How to set up a Node.js server to send MMS
Create a Node.js project, install Express, the Vonage Server SDK, and dotenv. Configure your Vonage account and application, then set up API routes in your server to handle sending requests and webhooks. Ensure secure storage of your API credentials.
What is the purpose of ngrok in Vonage MMS sending?
ngrok creates a public URL that tunnels to your local server, enabling Vonage to send webhooks (delivery receipts) to your application during development. This is mainly for testing purposes while not in a deployed environment.
Why use dotenv in a Vonage project?
Dotenv loads environment variables from a .env file, keeping your sensitive Vonage API keys and secrets out of your codebase. This is essential for security and best practice.
How to handle Vonage delivery receipts (DLRs)
Set up a webhook endpoint (e.g. /webhooks/status) in your Express app. Configure this URL in your Vonage application settings. Vonage will send POST requests to this endpoint with message status updates. Ensure you respond with a 200 OK status.
What is the role of the private.key file with the Vonage API?
The private.key file is essential for authenticating your application with the Vonage Messages API. It works in conjunction with your Application ID, ensuring secure communication. Store this file securely and never commit it to version control.
How to install the Vonage Server SDK for Node.js
Open your terminal and use npm: 'npm install @vonage/server-sdk'. This will install the necessary library for interacting with the Vonage API.
What is the format for phone numbers with the Vonage API?
Use the E.164 format for phone numbers, such as +14155552671. Ensure the plus sign and country code are included.
How to secure a Vonage MMS application
Implement robust input validation, use authentication/authorization middleware, set up rate limiting, and secure your webhook endpoint. Never expose your API keys or private key in your codebase.