code examples

Sent logo
Sent TeamMay 3, 2025 / code examples / Article

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-mms and /send-sms endpoints
  • 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 .env file, 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-sdk and node --version

System Architecture:

Your messaging system follows this request flow:

  1. Client request: Client application (cURL, Postman, or your app) sends HTTP POST to /send-mms or /send-sms
  2. Express API receives: Server validates request and authenticates using Vonage SDK
  3. Vonage SDK calls: vonage.messages.send() method contacts Vonage Messages API
  4. Vonage processes: Vonage API sends MMS or SMS to recipient's mobile carrier
  5. Recipient receives: Message delivered to recipient's phone
  6. 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/status endpoint
  • 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:

bash
mkdir vonage-mms-sender
cd vonage-mms-sender

1.2 Initialize Node.js Project

Create package.json to manage dependencies:

bash
npm init -y

This generates a default package.json with project metadata.

1.3 Install Dependencies

Install Express (web framework), Vonage SDK (API client), and dotenv (environment variables):

bash
npm install express @vonage/server-sdk dotenv

Package 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 .env file, keeping sensitive credentials secure.

1.4 Create Project Files

Create core application files:

bash
# 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 File

1.5 Configure .gitignore

Prevent sensitive files from entering version control. Add these lines to .gitignore:

Code
# 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
*.key

Security note: Never commit .env or private.key files. These contain credentials that authenticate your Vonage account.

Project Structure:

Your project contains these files:

FilePurpose
index.jsMain Express application code (server, routes, Vonage client)
package.jsonProject metadata and dependency list
package-lock.jsonLocked dependency versions for consistent installs
.envSensitive credentials (API keys, secrets) – never commit
.gitignoreFiles to exclude from Git version control
node_modules/Installed npm packages (auto-generated, ignored by Git)
private.keyVonage 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

  1. Log in to Vonage API Dashboard
  2. Note your main account credentials (Overview page):
    • API Key: 8-character alphanumeric string
    • API Secret: 16-character secret key
  3. 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:

  1. Navigate to Messaging > 10DLC
  2. Click Register Brand:
    • Enter business name, EIN/Tax ID, business address
    • Describe your company (50-200 characters)
    • Submit for approval
  3. Click Create Campaign:
    • Describe use case (e.g., "Order notifications", "Appointment reminders")
    • Select message samples
    • Estimate monthly volume
  4. Wait for approval (1-3 business days typically)
  5. 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

  1. Navigate to Numbers > Buy numbers
  2. Select country: United States (or Canada for Canadian MMS)
  3. Filter capabilities:
    • SMS (required)
    • MMS (required)
  4. Choose number type:
    • Long code (10-digit): Requires 10DLC registration, supports MMS
    • Toll-free (1-8XX): Requires verification, supports MMS, no 10DLC needed
  5. Purchase number
  6. Copy number in E.164 format (e.g., +12015550123)
  7. Pricing: $1-2/month rental + per-message charges ($0.0075/SMS, $0.05/MMS typically)

Step 4: Create Vonage Application

  1. Navigate to Applications > Create a new application
  2. Enter application name (e.g., "Node MMS Sender")
  3. Click Generate public and private key:
    • Downloads private.key file to your computer
    • Populates public key field automatically
    • Store private.key securely – never commit to Git
  4. Enable Messages capability (toggle or checkbox)
  5. 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
  6. Click Generate new application
  7. Copy Application ID (UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
  1. On application details page, scroll to Linked numbers
  2. Click Link button next to your purchased number
  3. Confirm linking – this associates number with Messages API application
  1. Navigate to Settings > API settings
  2. Scroll to SMS settings
  3. Set Default SMS Setting to Messages API (not SMS API v2)
  4. 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.key file
  • 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.

Move the downloaded private.key file to your project root directory:

bash
# Move private.key to project directory
mv ~/Downloads/private.key ./private.key

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

Code
# 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_here

Variable Explanations:

VariableRequired?Purpose
VONAGE_API_KEYOptionalMain account credential (8 characters)
VONAGE_API_SECRETOptionalMain account secret (16 characters)
VONAGE_APPLICATION_IDRequiredApplication UUID for Messages API
VONAGE_PRIVATE_KEY_PATHRequiredPath to JWT private key file
VONAGE_NUMBERRequiredSender ID (your Vonage number in E.164 format)
APP_PORTOptionalExpress server port (default: 3000)
MY_SECRET_API_KEYOptionalYour 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:

javascript
// 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:

  1. require('dotenv').config();: Loads variables from the .env file into process.env.
  2. express(): Creates an Express application instance.
  3. app.use(express.json()), app.use(express.urlencoded(...)): Middleware to parse incoming JSON and URL-encoded request payloads, making them available as req.body.
  4. app.get('/'): A simple route to confirm the server is running.
  5. app.listen(): Starts the server and makes it listen for connections on the specified port.
  6. Basic error handling middleware is added as a safety net.

You can run this basic server now:

bash
node index.js

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

javascript
// 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:

javascript
// 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:

  1. Vonage Client: The vonage instance is initialized using credentials from .env. A check is added to ensure the private key file exists. Error handling is included for initialization failure.
  2. Async/Await: The route handlers are async functions to allow using await for the asynchronous vonage.messages.send call.
  3. 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 like joi or express-validator).
  4. 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 the url (must be publicly accessible) and an optional caption for 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".
  5. Response: On success, it returns a 200 status with the message_uuid provided 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.
  6. Error Handling: The try...catch block 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.

  1. 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 3000

    ngrok will provide a forwarding URL (e.g., https://abcd-1234.ngrok.io). Copy the https version.

  2. 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.
  3. Add Webhook Endpoint in index.js: Add this route handler before the app.listen call:

    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 POST requests 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 OK status 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:

  1. Input Validation: Implement thorough validation for all inputs (to number format, imageUrl validity and source, text length and content). Libraries like joi or express-validator are 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)."" });
    }
  2. 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' });
        }
    });
  3. 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.
    bash
    npm install express-rate-limit
    javascript
    // 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);
  4. Webhook Security: As mentioned, verify incoming webhooks using Vonage's signing mechanisms (JWT).
  5. Secure Key Management: Never hardcode credentials. Use environment variables and secure storage mechanisms provided by your deployment platform. Ensure the private.key file 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.

  1. Consistent Error Responses: Standardize your error response format (e.g., { success: false, error: ""Error message"", code: ""ERROR_CODE"" }).
  2. Detailed Logging: Use a dedicated logging library like Winston for production instead of console.log. Configure log levels (info, warn, error) and output destinations (file, console, external service).
    bash
    npm install winston
    javascript
    // 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 });
  3. 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.data object.
  4. 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.

  1. Start Your Server:

    bash
    node index.js
  2. 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.

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

  3. Send Test SMS (using cURL):

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

  4. 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 .env with incorrect Vonage credentials to trigger authentication errors.
    • Check console logs for detailed error messages.
  5. Verify Webhooks (if configured):

    • Check the console where node index.js is 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).

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 TypeHTTP StatusCauseSolution
invalid-params400Missing/invalid required fieldsCheck to, from, imageUrl fields; verify E.164 format
unauthorized401Invalid Application ID or private keyVerify VONAGE_APPLICATION_ID and private.key file path
unsupported-channel400MMS not supported for destinationDestination may be landline, international, or VoIP; use SMS
media-invalid400Image URL inaccessible or invalid formatEnsure image URL returns 200 OK with image content-type
media-too-large400Image exceeds size limitCompress image to <1 MB; check carrier-specific limits
throttled429Rate limit exceededImplement exponential backoff; check 10DLC throughput limits
insufficient-balance402Account has insufficient creditAdd credit to Vonage account (Dashboard > Billing)
spam-detected400Message flagged as spamReview message content; ensure opt-in consent; check 10DLC campaign

Debugging Steps:

  1. Check Vonage Dashboard Logs:

    • Navigate to Dashboard > Messaging > Logs
    • View detailed delivery status, error codes, timestamps
    • Filter by message_uuid for specific messages
  2. Verify Number Capabilities:

    • Dashboard > Numbers > Your Numbers
    • Confirm SMS and MMS checkboxes are enabled
    • Verify number is linked to your Application
  3. Test Image URL Accessibility:

    bash
    curl -I https://your-image-url.com/image.jpg
    # Should return: HTTP/1.1 200 OK
    # Content-Type: image/jpeg
  4. Check 10DLC Status (US Long Codes):

    • Dashboard > Messaging > 10DLC
    • Verify brand status: "Approved"
    • Verify campaign status: "Active"
    • Processing time: 1-3 business days typically
  5. 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`);
  6. 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.