code examples

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

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/messages SDK (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 .env file, keeping sensitive credentials out of source code.

Prerequisites:

  • Node.js and npm (or yarn): Installed on your development machine. Check with node -v and npm -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.

  1. Create Project Directory: Open your terminal and create a new directory for your project, then navigate into it.

    bash
    mkdir fastify-vonage-mms
    cd fastify-vonage-mms
  2. Initialize Node.js Project: Create a package.json file to manage dependencies and project metadata.

    bash
    npm init -y
  3. Install Dependencies: Install Fastify for the web server, @vonage/messages for the Vonage API interaction, and dotenv for environment variables.

    bash
    npm install fastify @vonage/messages dotenv

    Recommended Version Pinning: For production applications, pin major versions to ensure stability and security:

    PackageRecommended VersionNotes
    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 audit after installation to check for known security vulnerabilities.

  4. Set Up Project Structure: Create a basic structure for clarity.

    bash
    mkdir src
    touch src/server.js
    touch src/vonageClient.js
    touch .env
    touch .gitignore
    • src/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.
  5. Configure .gitignore: Add node_modules and .env to your .gitignore file to prevent committing them.

    Code
    node_modules/
    .env
    private.key
  6. Set Up Environment Variables (.env): Open the .env file 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=3000

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

  1. Log in to Vonage: Access your Vonage API Dashboard.

  2. Note API Key & Secret: Find your API key and API secret at the top of the dashboard. Copy these into your .env file.

  3. 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 .env file for VONAGE_FROM_NUMBER.

    Pricing: US 10DLC numbers cost $1.00/month. Toll-Free numbers cost $5.00–$15.00/month depending on availability.

  4. Register for 10DLC (Required for US MMS):

    Critical Step: Before sending MMS to the United States, complete 10DLC registration. This involves:

    StepActionProcessing TimeCost
    1Register your 10DLC brand (declare your company)1–3 business days$4.00 (one-time)
    2Brand verification by The Campaign Registry (TCR)1–5 business daysIncluded in brand fee
    3Register a 10DLC campaign (describe your messaging use case)1–3 business days$15.00/month (recurring)
    4Link your US number to your 10DLC campaignImmediateIncluded

    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.
  5. 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.key file.
    • Crucially: Save the private.key file directly into the root directory of your fastify-vonage-mms project (the same level as your package.json). Ensure the path in your .env file (VONAGE_APPLICATION_PRIVATE_KEY_PATH=./private.key) matches this location. Also ensure private.key is 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/inbound and https://example.com/webhooks/status. If you later want to receive delivery receipts, update these with real endpoints accessible by Vonage.
    • 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 .env file for VONAGE_APPLICATION_ID.
  6. 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.
  7. 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.

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

  1. dotenv.config(): Loads the variables from your .env file into process.env.
  2. Environment Variable Validation: Checks if critical variables are set, exiting if not.
  3. Private Key Path Resolution & Check: Uses path.resolve to get the absolute path and fs.existsSync to verify the private key file exists at the specified location before proceeding.
  4. @vonage/messages Initialization: Creates an instance of the Messages client. Crucially, it uses the combination of apiKey, apiSecret, applicationId, and privateKey. This JWT-based authentication is required for the Messages API v1 used for MMS.
  5. sendMms Function:
    • Takes to, from, imageUrl, and an optional caption as 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 MMSImage object defining the recipient, sender, image URL, and caption.
    • Calls messagesClient.send() with the payload.
    • Uses async/await with a try…catch block 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 in error.response.data.
  6. module.exports: Exports the sendMms function 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.

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

  1. require('dotenv').config(): Loads environment variables.
  2. fastify({ logger: true }): Initializes Fastify and enables its built-in Pino logger.
  3. require('./vonageClient'): Imports the sendMms function.
  4. Request Validation Schema (sendMmsSchema):
    • Defines the expected JSON structure for the POST /send-mms request body using Fastify's JSON Schema support.
    • Requires to and imageUrl.
    • Validates to and optional from against an E.164 regex (^\\+[1-9]\\d{1,14}$). Adds descriptions.
    • Checks imageUrl for basic URI format. Adds description.
    • Makes caption an optional string with a default value and description.
    • Adds additionalProperties: false to 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.
  5. POST /send-mms Route:
    • Uses async (request, reply) for asynchronous handling.
    • Includes the { schema: sendMmsSchema } option to enable automatic validation.
    • Extracts validated to, imageUrl, and caption from request.body.
    • Determines the from number: uses the one from the request body if provided, otherwise defaults to VONAGE_FROM_NUMBER from the .env file. Includes a check to ensure a from number is available (in case .env is missing the variable and it's not in the request).
    • Calls await sendMms(…) inside a try…catch block.
    • Success: Sends a 200 OK response with a success message and the message_uuid received from Vonage.
    • Error: Logs the error using fastify.log.error and sends a 500 Internal Server Error response with a structured error message including the specific detail from the caught error.
  6. GET /health Route: A simple health check endpoint, useful for load balancers or monitoring systems.
  7. start Function:
    • Defines an asynchronous function to start the server.
    • Listens on the port specified by the PORT environment variable (defaults to 3000).
    • Listens on 0.0.0.0 to be accessible from outside the container/machine if needed (common for deployment).
    • Includes error handling for server startup failures.
  8. start(): Calls the function to start the server.

5. Running and Testing the Application

  1. Ensure .env is correct: Double-check all Vonage credentials, App ID, Private Key Path, and numbers in your .env file. Make sure private.key exists at the specified path.

  2. Start the Server: Open your terminal in the project root directory (fastify-vonage-mms) and run:

    bash
    node src/server.js

    You 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.json for 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 dev for development with auto-reload (Node.js v18.11+ required), npm start for quick testing, or npm run prod for production with optimizations.

  3. Test with curl or Postman: Open another terminal window and use curl (or configure Postman) to send a POST request to your running server.

    Replace:

    • YOUR_RECIPIENT_NUMBER with the actual phone number (E.164 format) you want to send the MMS to (you can use the DEFAULT_TO_NUMBER from .env if set).
    • Optionally change the imageUrl and caption.
    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"
    }'
  4. Check Results:

    • Terminal (curl): For successful requests, you should receive a JSON response like:
      json
      {
        "message": "MMS sending initiated successfully.",
        "recipient": "YOUR_RECIPIENT_NUMBER",
        "sender": "YOUR_VONAGE_FROM_NUMBER",
        "messageId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
      }
      For invalid requests (schema validation failure), you'll get a 400 error with details:
      json
      {
        "statusCode": 400,
        "error": "Bad Request",
        "message": "body must have required property 'imageUrl'" // Example
      }
      Or a 500 error if the Vonage API call fails:
      json
      {
        "error": "Failed to send MMS.",
        "details": "Specific error message from Vonage or validation"
      }
    • Terminal (Server Logs): Check the logs where node src/server.js is running. You'll see Fastify's request logs (including request IDs) and the logs from vonageClient.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.

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.js Errors: The try…catch block catches errors during the messagesClient.send() call. It logs detailed errors (especially Vonage-specific error codes/messages found in error.response.data) to the console/server logs.
  • Fastify Route Errors: The route's try…catch catches errors from sendMms, logs them using fastify.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:

javascript
// 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.type or error.response.data.title from Vonage errors in the route handler's catch block 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 info and above in production, debug or trace in 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 .env locally and secure environment variable management in production (e.g., AWS Secrets Manager, HashiCorp Vault, platform-specific environment variables). Always add .env and private.key to .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-provided caption text 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.

    bash
    npm install @fastify/rate-limit
    javascript
    // 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:

    bash
    npm install @fastify/cors
    javascript
    // 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-mms endpoint 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_KEY or X-API-Key: YOUR_SECRET_KEY). Validate this key on the server using a Fastify hook (preHandler or onRequest).
    • JWT: Implement user authentication (e.g., with @fastify/jwt and @fastify/auth) and verify JWTs to authorize requests based on user identity or roles.
  • 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.key file. 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 .env to 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 imageUrl must 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 using curl first.

    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_ID in your .env file.
    • Verify VONAGE_APPLICATION_PRIVATE_KEY_PATH points correctly to the private.key file, 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 the VONAGE_APPLICATION_ID in the Vonage dashboard.
    • Verify API Settings > Default SMS Setting is set to Messages API in the Vonage dashboard.
  • Number Provisioning/Permission Errors (e.g., 403 Forbidden, "Non White-listed Destination"):

    • Ensure the VONAGE_FROM_NUMBER is 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.
  • 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 TypeRate Limit
    Free/Trial1 request/second
    Paid10 requests/second
    EnterpriseCustom (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-limit plugin 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:

bash
# 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.jpg

Presigned URL Example (temporary access):

javascript
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 request

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

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

  1. Use environment variables for all credentials via AWS Secrets Manager, HashiCorp Vault, or platform-specific secret management – never commit .env or private.key files.
  2. Enable HTTPS using a load balancer (AWS ALB, Nginx, Caddy) that terminates SSL/TLS.
  3. Implement rate limiting with @fastify/rate-limit to prevent API abuse and protect your Vonage account.
  4. Add authentication using API keys or JWT with @fastify/jwt and @fastify/auth to secure your /send-mms endpoint.
  5. Configure structured logging with Pino and forward logs to CloudWatch, Datadog, or Splunk for monitoring.
  6. Set restrictive file permissions on private.key (readable only by application user) or load key content from environment variables in containerized environments.
  7. Verify 10DLC registration is complete before deployment to ensure message delivery.

Docker Configuration Example:

dockerfile
# 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:

yaml
# 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.key

AWS ECS Task Definition Example:

json
{
  "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.