code examples

Sent logo
Sent TeamMar 8, 2026 / code examples / Plivo

How to Send MMS with Plivo, Node.js & Fastify: Complete 2025 Guide

Learn how to send MMS messages using Plivo API, Fastify, and Node.js. Complete tutorial with code examples, authentication, rate limiting, and error handling for multimedia messaging.

Send MMS with Plivo, Node.js & Fastify: Complete Tutorial

Learn how to build a production-ready service using Fastify and Node.js to send Multimedia Messaging Service (MMS) messages via the Plivo API. This comprehensive tutorial covers project setup, authentication, sending logic, error handling, rate limiting, and testing.

By the end of this guide, you'll have a robust Fastify application that sends MMS messages with media attachments (images, audio, video) using Plivo's reliable messaging infrastructure.

MMS vs SMS: When to Use Multimedia Messaging

MMS (Multimedia Messaging Service) allows you to send rich media content including images, audio files, videos, and longer text (up to 1600 characters), while SMS is limited to 160 characters of text only. Use MMS when you need to:

  • Send visual content like product images, QR codes, or promotional graphics
  • Share audio messages or video clips
  • Deliver richer, more engaging marketing campaigns
  • Provide visual instructions or tutorials

Note that MMS messages typically cost more than SMS and require cellular data connection on the recipient's device. Plivo supports MMS exclusively within the US and Canada.

Technologies Used:

  • Node.js: The JavaScript runtime environment.
  • Fastify: A high-performance, low-overhead web framework for Node.js. Chosen for its speed, extensive plugin ecosystem, and developer-friendly features like built-in validation.
  • Plivo Node.js SDK: The official library for interacting with the Plivo API, simplifying MMS sending.
  • dotenv: A module to load environment variables from a .env file, keeping sensitive credentials out of the codebase.
  • pino-pretty (Optional): A development dependency to make Fastify's logs more human-readable during development.
<!-- EXPAND: Could benefit from version compatibility matrix (Type: Enhancement) -->

Prerequisites:

  • Node.js and npm (or yarn): Ensure you have Node.js 22.x LTS installed (Active LTS until October 2025, Maintenance LTS until April 2027). Node.js 22 is the recommended version for production applications as of January 2025. Download from Node.js Downloads. Source: Node.js Release Schedule
  • Plivo Account: You need an active Plivo account. (Sign up for Plivo)
  • Plivo Auth ID and Auth Token: Found on your Plivo console dashboard after logging in.
  • Plivo Phone Number: An MMS-enabled Plivo phone number (available for US & Canada only). Plivo supports MMS messaging exclusively within the US and Canada using local long code, toll-free, or short code numbers. You can purchase one from the Plivo console under "Phone Numbers" > "Buy Numbers". Ensure the number has MMS capabilities enabled. Source: Plivo MMS Support
  • Publicly Accessible Media URL: The URL of the image or media file you want to send. Supported formats include JPEG, GIF, PNG (images), MP3 (audio), and MP4 (video). Crucially, Plivo's servers must be able to publicly fetch the media from this URL. For Canadian carriers, keep MMS attachments smaller than 1 MB to ensure broad compatibility across all networks. For testing, services like Postimages or public GitHub repository links can work. Source: Plivo MMS Country Support
  • (Optional) Verified Destination Number: If using a Plivo trial account, you can only send messages to numbers verified in your Plivo console under "Messaging" > "Sandbox Numbers".
<!-- GAP: Missing cost breakdown and trial account limitations (Type: Critical, Priority: High) --> <!-- DEPTH: No information about MMS pricing or account balance requirements (Priority: High) -->

System Architecture:

(A diagram illustrating the flow from HTTP Client -> Fastify App -> Plivo API -> End User would typically be included here.)

This guide focuses on the "Fastify Application" component and its interaction with the Plivo API for sending MMS.

<!-- EXPAND: Missing actual architecture diagram or flowchart (Type: Enhancement, Priority: Medium) --> <!-- GAP: No explanation of webhook flow for delivery status (Type: Substantive) -->

Setting Up Your Plivo MMS Project

Let's initialize the project, install dependencies, and set up the basic structure.

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

    bash
    mkdir fastify-plivo-mms
    cd fastify-plivo-mms
  2. Initialize Node.js Project: Initialize a package.json file. The -y flag accepts default settings.

    bash
    npm init -y
    # or
    yarn init -y
  3. Install Dependencies: Install Fastify, the Plivo SDK, and dotenv.

    bash
    npm install fastify plivo dotenv
    # or
    yarn add fastify plivo dotenv
<!-- DEPTH: No explanation of why these specific packages or alternatives (Priority: Low) -->
  1. Install Development Dependencies (Optional): Install pino-pretty for better log readability during development.

    bash
    npm install --save-dev pino-pretty
    # or
    yarn add --dev pino-pretty
  2. Create Project Structure: Set up a basic directory structure for better organization.

    bash
    mkdir src
    mkdir src/routes
    touch src/app.js
    touch src/routes/mms.js
    touch .env
    touch .env.example
    touch .gitignore
    • src/: Contains the main application source code.
    • src/routes/: Will hold route definitions.
    • src/app.js: The main Fastify application entry point.
    • src/routes/mms.js: The route handler specifically for MMS sending.
    • .env: Stores sensitive environment variables (API keys, etc.). Never commit this file.
    • .env.example: An example file showing required environment variables (safe to commit).
    • .gitignore: Specifies files and directories that Git should ignore.
<!-- EXPAND: Could add testing directory structure and config files (Type: Enhancement) -->
  1. Configure .gitignore: Add node_modules and .env to your .gitignore file to prevent committing them to version control.

    text
    # .gitignore
    
    node_modules
    .env
    *.log
  2. Configure .env.example: Add the necessary environment variable placeholders to .env.example.

    dotenv
    # .env.example
    
    # Plivo API Credentials
    PLIVO_AUTH_ID=YOUR_PLIVO_AUTH_ID
    PLIVO_AUTH_TOKEN=YOUR_PLIVO_AUTH_TOKEN
    
    # Plivo MMS-enabled phone number (in E.164 format, e.g., +14151234567)
    PLIVO_SENDER_NUMBER=YOUR_PLIVO_MMS_NUMBER
    
    # Server Configuration
    HOST=0.0.0.0
    PORT=3000
  3. Configure .env: Copy .env.example to .env and fill in your actual Plivo credentials and phone number.

    bash
    cp .env.example .env

    Now, edit the .env file with your real values obtained from the Plivo console.

    dotenv
    # .env
    
    # Plivo API Credentials
    PLIVO_AUTH_ID=MXXXXXXXXXXXXXXXXXXD
    PLIVO_AUTH_TOKEN=NXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXZA
    # Plivo MMS-enabled phone number (in E.164 format, e.g., +14151234567)
    PLIVO_SENDER_NUMBER=+14151112222
    # Server Configuration
    HOST=0.0.0.0
    PORT=3000

    Why .env? Storing sensitive information like API keys directly in code is a major security risk. .env files allow you to keep configuration separate from the codebase, making it easier to manage different environments (development, production) and preventing accidental exposure.

<!-- DEPTH: Missing best practices for secret rotation and key management (Priority: Medium) -->

Implementing the Fastify Server for MMS

Let's set up the basic Fastify server and configure it to use the Plivo client.

  1. Edit src/app.js: This file will initialize Fastify, load environment variables, instantiate the Plivo client, register routes, and start the server.

    javascript
    // src/app.js
    
    'use strict'
    
    const Fastify = require('fastify');
    const plivo = require('plivo');
    const mmsRoutes = require('./routes/mms');
    require('dotenv').config(); // Load .env variables into process.env
    
    // Basic validation for essential environment variables
    const requiredEnv = ['PLIVO_AUTH_ID', 'PLIVO_AUTH_TOKEN', 'PLIVO_SENDER_NUMBER', 'PORT', 'HOST'];
    const missingEnv = requiredEnv.filter(envVar => !process.env[envVar]);
    
    if (missingEnv.length > 0) {
        console.error(`Error: Missing required environment variables: ${missingEnv.join(', ')}`);
        console.error('Please ensure they are set in your .env file or environment.');
        process.exit(1); // Exit if essential config is missing
    }
    
    // Configure Fastify logger for development
    const loggerConfig = process.env.NODE_ENV === 'development' ? {
        transport: {
            target: 'pino-pretty',
            options: {
                translateTime: 'HH:MM:ss Z',
                ignore: 'pid,hostname',
            },
        },
    } : true; // Use default JSON logger in production
    
    // Initialize Fastify
    const app = Fastify({
        logger: loggerConfig
    });
    
    // --- Plivo Client Initialization ---
    // Why here? Instantiate the client once and make it available
    // throughout the application via Fastify's decoration feature.
    try {
        const plivoClient = new plivo.Client(
            process.env.PLIVO_AUTH_ID,
            process.env.PLIVO_AUTH_TOKEN
        );
        // Decorate the Fastify instance with the Plivo client
        // Makes `app.plivo` accessible in route handlers
        app.decorate('plivo', plivoClient);
        app.log.info('Plivo client initialized successfully.');
    } catch (error) {
        app.log.error('Failed to initialize Plivo client:', error);
        process.exit(1); // Cannot proceed without a working client
    }
    
    // Decorate Fastify with the sender number for easy access
    app.decorate('plivoSenderNumber', process.env.PLIVO_SENDER_NUMBER);
    
    // --- Register Routes ---
    // Prefix all routes defined in mms.js with /api/mms
    app.register(mmsRoutes, { prefix: '/api/mms' });
    
    // --- Health Check Route ---
    // Good practice for monitoring and load balancers
    app.get('/health', async (request, reply) => {
        return { status: 'ok', timestamp: new Date().toISOString() };
    });
    
    // --- Start Server ---
    const start = async () => {
        try {
            const port = parseInt(process.env.PORT, 10);
            const host = process.env.HOST;
            await app.listen({ port: port, host: host });
            // Logger already announces the address
        } catch (err) {
            app.log.error(err);
            process.exit(1);
        }
    };
    
    start();
    • Environment Variables: dotenv.config() loads variables from .env. We add basic checks to ensure critical variables are present.
    • Logger: We configure pino-pretty for development for easier reading and standard JSON logging for production (better for log aggregation tools).
    • Plivo Client: The plivo.Client is instantiated using credentials from process.env.
    • Fastify Decorators (app.decorate): This is a key Fastify pattern. We "decorate" the Fastify app instance with the plivoClient and plivoSenderNumber. This makes them easily accessible within route handlers via request.server.plivo or reply.server.plivo (and similarly for plivoSenderNumber), avoiding the need to pass them around manually or re-initialize them.
    • Route Registration: We register the routes defined in src/routes/mms.js under the /api/mms prefix.
    • Health Check: A simple /health endpoint is added, which is standard practice for monitoring.
    • Server Start: The app.listen function starts the server on the configured host and port.
<!-- DEPTH: Missing graceful shutdown handling and signal management (Priority: High) --> <!-- GAP: No error recovery strategies or circuit breaker pattern (Type: Substantive) -->

Building the MMS Sending API Endpoint

Now, let's implement the route that will handle incoming requests to send an MMS.

<!-- EXPAND: Could add request/response examples with different scenarios (Type: Enhancement) -->
  1. Edit src/routes/mms.js: This file defines the API endpoint for sending MMS messages.

    javascript
    // src/routes/mms.js
    
    'use strict';
    
    const plivo = require('plivo'); // Required for Plivo specific error types if needed
    
    // Define the JSON schema for the request body
    // Why schema? Fastify uses this for automatic validation.
    // It's faster than manual checks and ensures data consistency.
    const sendMmsSchema = {
        body: {
            type: 'object',
            required: ['to', 'media_urls', 'text'], // Define mandatory fields
            properties: {
                to: {
                    type: 'string',
                    // E.164 format: + followed by 1-3 digit country code + subscriber number (max 15 digits total)
                    pattern: '^\\+[1-9]\\d{1,14}$',
                    description: 'Destination phone number in E.164 format (e.g., +14151234567). Must start with + followed by country code and subscriber number. Maximum 15 digits total per ITU-T E.164 specification.' // ITU-T specification reference
                },
                media_urls: {
                    type: 'array',
                    minItems: 1, // Must have at least one media URL
                    maxItems: 10, // Plivo supports up to 10 media URLs per MMS
                    items: {
                        type: 'string',
                        format: 'url', // Basic URL format validation
                        description: 'Publicly accessible URL of the media file. Supported formats: JPEG, GIF, PNG (images), MP3 (audio), MP4 (video). Keep files under 1 MB for Canadian carriers.'
                    }
                },
                text: {
                    type: 'string',
                    minLength: 1, // Text cannot be empty
                    description: 'The text content of the MMS message'
                }
                // Optional: Add other Plivo parameters here if needed (e.g., url, method for status callbacks)
            },
            additionalProperties: false // Disallow properties not defined in the schema
        },
        response: { // Define expected success response structure
            200: {
                type: 'object',
                properties: {
                    message: { type: 'string' },
                    // Use camelCase for consistency with Node.js conventions and SDK response
                    messageUuid: { type: 'array', items: { type: 'string' } },
                    apiId: { type: 'string' }
                }
            },
            // Define potential error responses (Fastify handles validation errors automatically -> 400)
            500: {
                type: 'object',
                properties: {
                    statusCode: { type: 'number' },
                    error: { type: 'string' },
                    message: { type: 'string' }
                }
            },
             429: { // For rate limiting
                type: 'object',
                properties: {
                    statusCode: { type: 'number' },
                    error: { type: 'string' },
                    message: { type: 'string' }
                }
            }
            // Add other specific error status codes Plivo might return if needed (e.g., 400, 401, 402)
        }
    };
    
    async function mmsRoutes(fastify, options) {
        // Access the decorated Plivo client and sender number
        const plivoClient = fastify.plivo;
        const senderNumber = fastify.plivoSenderNumber;
    
        fastify.post('/send', { schema: sendMmsSchema }, async (request, reply) => {
            const { to, media_urls, text } = request.body; // Data is already validated by Fastify
    
            request.log.info(`Attempting to send MMS to ${to} from ${senderNumber}`);
    
            try {
                const params = {
                    src: senderNumber,
                    dst: to,         // Plivo uses 'dst' for destination
                    text: text,
                    media_urls: media_urls,
                    type: 'mms'      // Explicitly set type to MMS
                    // Optional: Configure status callbacks
                    // url: 'https://your-callback-url.com/plivo-status', // Your webhook endpoint
                    // method: 'POST'
                };
    
                const response = await plivoClient.messages.create(params);
    
                request.log.info({ msg: 'MMS sent successfully', response: response }, `MMS UUID(s): ${response.messageUuid.join(', ')}`);
    
                // Send successful response back to the client (using camelCase keys)
                return reply.code(200).send({
                    message: response.message,          // Success message from Plivo
                    messageUuid: response.messageUuid,  // Plivo's message identifier(s)
                    apiId: response.apiId               // Plivo's API request identifier
                });
    
            } catch (error) {
                request.log.error({ err: error }, `Failed to send MMS to ${to}`);
    
                // Handle potential Plivo-specific errors or general errors
                let statusCode = 500;
                let errorMessage = 'An unexpected error occurred while sending the MMS.';
    
                if (error instanceof plivo.PlivoResponseError) {
                    // Plivo API returned an error response
                    statusCode = error.statusCode || 500; // Use Plivo's status if available
                    errorMessage = `Plivo API Error (${error.statusCode}): ${error.message || 'Unknown Plivo error'}`;
                    // Common Plivo errors include 400 (e.g., invalid 'dst', number capability issues), 401 (auth), 402 (insufficient balance).
                    // You might want more specific handling based on Plivo error codes here.
                } else if (error.name === 'ValidationError') {
                    // Although Fastify handles schema validation, keep this for potential future manual checks
                    statusCode = 400;
                    errorMessage = `Validation Error: ${error.message}`;
                }
                // Add more specific error handling if needed (e.g., network errors)
    
                // Send error response
                return reply.code(statusCode).send({
                    statusCode: statusCode,
                    error: error.name || 'Internal Server Error',
                    message: errorMessage
                });
            }
        });
    }
    
    module.exports = mmsRoutes;
    • Schema Validation: We define a sendMmsSchema using JSON Schema. Fastify automatically validates incoming request bodies against this schema before the handler function runs. If validation fails, Fastify sends a 400 Bad Request response automatically. This simplifies the handler logic significantly. We specify required fields (to, media_urls, text), types, and formats (like E.164 pattern and URL format). additionalProperties: false prevents extra, unexpected fields. The to field pattern was corrected, and its description enhanced. Added maxItems: 10 to media_urls based on Plivo limits.
    • Route Definition: fastify.post('/send', { schema: sendMmsSchema }, ...) defines a POST endpoint at /api/mms/send. The schema is attached directly to the route options.
    • Accessing Decorators: fastify.plivo and fastify.plivoSenderNumber provide the initialized Plivo client and sender number.
    • Plivo API Call: We construct the params object required by plivoClient.messages.create. Note that Plivo uses dst for the destination number. We explicitly set type: 'mms'.
    • async/await: The route handler is an async function, allowing us to use await for the Plivo API call, making the asynchronous code cleaner.
    • Success Response: On success, we log the result and return a 200 OK response with details from the Plivo API. The response keys (messageUuid, apiId) are now consistently camelCase, matching the schema and common Node.js practice.
    • Error Handling: The try...catch block handles errors during the API call. We log the error and attempt to provide a meaningful status code and message back to the client. We check if the error is a specific PlivoResponseError to potentially extract more details, adding a comment about common error codes. The response schema was updated to use camelCase for consistency.
<!-- GAP: Missing retry logic for transient failures (Type: Substantive, Priority: High) --> <!-- DEPTH: No explanation of idempotency or duplicate request handling (Priority: Medium) --> <!-- EXPAND: Could add detailed error code mapping table (Type: Enhancement) -->

Adding Rate Limiting for Security

To prevent abuse and protect your Plivo account balance, implementing rate limiting is crucial.

<!-- DEPTH: Missing explanation of DDoS protection and additional security layers (Priority: Medium) -->
  1. Install Rate Limiter Plugin:

    bash
    npm install @fastify/rate-limit
    # or
    yarn add @fastify/rate-limit
  2. Register and Configure in src/app.js: Modify src/app.js to include the rate limiter.

    javascript
    // src/app.js
    // ... other imports
    const rateLimit = require('@fastify/rate-limit');
    // ... after Fastify initialization
    
    // --- Rate Limiting ---
    // Why? Prevents abuse and controls costs. Apply globally or per-route.
    // Register *before* your main application routes
    app.register(rateLimit, {
        max: 100, // Max requests per time window per IP (adjust as needed)
        timeWindow: '1 minute', // Time window duration
        // Optional: Add custom key generator, redis store for distributed systems, etc.
        // keyGenerator: function (req) { /* ... */ },
        // redis: new Redis({ host: '127.0.0.1' }),
        errorResponseBuilder: function (req, context) {
            return {
                statusCode: 429,
                error: 'Too Many Requests',
                message: `Rate limit exceeded. Please try again after ${context.after}.`,
                retryAfter: context.ttl, // Time in milliseconds until reset
            }
        },
        enableDraftSpec: true, // Recommended: Set standard RateLimit-* headers
    });
    
    // --- Plivo Client Initialization --- // Moved *after* rate limit registration
    try {
        const plivoClient = new plivo.Client(
            process.env.PLIVO_AUTH_ID,
            process.env.PLIVO_AUTH_TOKEN
        );
        // Decorate the Fastify instance with the Plivo client
        // Makes `app.plivo` accessible in route handlers
        app.decorate('plivo', plivoClient);
        app.log.info('Plivo client initialized successfully.');
    } catch (error) {
        app.log.error('Failed to initialize Plivo client:', error);
        process.exit(1); // Cannot proceed without a working client
    }
    
    // Decorate Fastify with the sender number for easy access
    app.decorate('plivoSenderNumber', process.env.PLIVO_SENDER_NUMBER);
    
    // --- Register Routes ---
    // Prefix all routes defined in mms.js with /api/mms
    app.register(mmsRoutes, { prefix: '/api/mms' });
    
    // --- Health Check Route ---
    // Good practice for monitoring and load balancers
    app.get('/health', async (request, reply) => {
        return { status: 'ok', timestamp: new Date().toISOString() };
    });
    
    // --- Start Server ---
    const start = async () => {
        try {
            const port = parseInt(process.env.PORT, 10);
            const host = process.env.HOST;
            await app.listen({ port: port, host: host });
            // Logger already announces the address
        } catch (err) {
            app.log.error(err);
            process.exit(1);
        }
    };
    
    start();
    • Registration: We use app.register to add the @fastify/rate-limit plugin. It's important to register this before the routes you want to limit.
    • Configuration:
      • max: Sets the maximum number of requests allowed within the timeWindow. Adjust this based on expected traffic and Plivo's own rate limits.
      • timeWindow: Defines the duration over which requests are counted (e.g., '1 minute', '1 hour', 60000 ms).
      • errorResponseBuilder: Customizes the 429 Too Many Requests response sent when the limit is hit.
      • enableDraftSpec: Adds standard RateLimit-* headers to responses, which is good practice.
    • Scope: By registering it here before other routes, it applies globally by default (based on IP address). You can configure it more granularly per-route if needed.
<!-- GAP: Missing Redis configuration for distributed rate limiting (Type: Substantive, Priority: Medium) --> <!-- DEPTH: No guidance on tuning rate limits based on account tier (Priority: High) -->

Testing Your MMS Service

Now, let's run the server and test the MMS sending endpoint.

<!-- EXPAND: Could add automated testing examples with Jest or Mocha (Type: Enhancement) -->
  1. Add Run Scripts to package.json:

    json
    // package.json snippet
    {
      // ... other fields like name, version, main, etc.
      "scripts": {
        "start": "node src/app.js",
        "dev": "NODE_ENV=development node src/app.js | pino-pretty"
      }
      // ... dependencies, etc.
    }
    • start: Runs the application in production mode (default logger).
    • dev: Runs in development mode, setting NODE_ENV and piping logs through pino-pretty.
  2. Run the Server (Development Mode):

    bash
    npm run dev
    # or
    yarn dev

    You should see output indicating the server is running, similar to:

    [HH:MM:SS Z] INFO: Plivo client initialized successfully. [HH:MM:SS Z] INFO: Server listening at http://0.0.0.0:3000
  3. Test with curl: Open another terminal window. Replace placeholders with your verified destination number (for trial accounts) or any US/Canada number (for paid accounts), and a publicly accessible media URL.

    Success Case:

    bash
    curl -X POST http://localhost:3000/api/mms/send \
    -H "Content-Type: application/json" \
    -d '{
      "to": "+1DESTINATION_NUMBER",
      "media_urls": ["https://upload.wikimedia.org/wikipedia/commons/thumb/a/a4/Plivo_logo.png/600px-Plivo_logo.png"],
      "text": "Hello from Fastify and Plivo!"
    }'

    Expected Success Response (JSON - using camelCase):

    json
    {
      "message": "message(s) queued",
      "messageUuid": ["xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"],
      "apiId": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
    }

    You should also see corresponding logs in the server terminal and receive the MMS on the destination phone shortly.

<!-- DEPTH: No explanation of typical delivery times or latency expectations (Priority: Medium) --> **Validation Error Case (Missing `text`):** ```bash curl -X POST http://localhost:3000/api/mms/send \ -H "Content-Type: application/json" \ -d '{ "to": "+1DESTINATION_NUMBER", "media_urls": ["https://example.com/image.png"] }' ``` *Expected Error Response (400 Bad Request - Handled by Fastify):* ```json { "statusCode": 400, "error": "Bad Request", "message": "body should have required property 'text'" } ``` **Plivo API Error Case (e.g., Invalid Auth Token in `.env`):** *(Simulate by temporarily putting an incorrect token in `.env` and restarting the server)* ```bash # Send the valid request again after messing up the token curl -X POST http://localhost:3000/api/mms/send \ -H "Content-Type: application/json" \ -d '{ "to": "+1DESTINATION_NUMBER", "media_urls": ["https://upload.wikimedia.org/wikipedia/commons/thumb/a/a4/Plivo_logo.png/600px-Plivo_logo.png"], "text": "Testing error" }' ``` *Expected Error Response (Handled by our `catch` block):* ```json { "statusCode": 401, "error": "PlivoResponseError", "message": "Plivo API Error (401): Authentication credentials were not provided or are invalid." } ``` <!-- EXPAND: Could add Postman collection or OpenAPI spec for testing (Type: Enhancement) -->

Troubleshooting Common MMS Issues

  • Authentication Errors (401): Double-check PLIVO_AUTH_ID and PLIVO_AUTH_TOKEN in your .env file. Ensure they are copied correctly from the Plivo console.
  • Invalid src or dst Number (e.g., 400 Bad Request from Plivo):
    • Ensure PLIVO_SENDER_NUMBER is an MMS-enabled number you own on Plivo.
    • Ensure both src and dst numbers are in E.164 format (e.g., +14151234567). The to field validator checks the format (^\\+[1-9]\\d{1,14}$).
    • If using a trial account, the dst number must be verified in the Sandbox Numbers section of the Plivo console.
  • Invalid Media URL: The media_urls must point to publicly accessible URLs. Plivo's servers need to fetch the media from these URLs. Test the URL in your browser first. Supported file types include JPEG, GIF, PNG (images), MP3 (audio), and MP4 (video). For Canadian carriers, keep MMS attachments smaller than 1 MB to ensure broad compatibility across all networks.
  • Rate Limits (429): If you send too many requests too quickly, you'll hit the rate limit configured in Fastify (max: 100, timeWindow: '1 minute' by default in this guide) or Plivo's own API limits. Plivo enforces a default limit of 300 API requests per 5 seconds for non-call APIs, and supports up to 100 concurrent MMS requests. When limits are exceeded, Plivo responds with HTTP 429 Too Many Requests. The error response indicates when you can retry. Source: Plivo Rate Limits and Plivo MMS Account Rate Limits
  • MMS Not Supported: Plivo primarily supports sending MMS between US and Canadian numbers. Sending to other countries may fail or be converted to SMS with a link. Check Plivo's documentation for current country support.
  • Other Plivo API Errors (4xx/5xx): Beyond authentication (401), you might encounter errors like 400 (Bad Request - often due to invalid numbers, lack of MMS capability on the source number, or malformed requests), 402 (Payment Required - insufficient account balance), or Plivo-side server errors (5xx). The error message provided by the API is usually informative. Check the Plivo API documentation for specific error code meanings.
  • Network Issues: Ensure your server has outbound internet connectivity to reach the Plivo API (api.plivo.com). Firewalls or network configuration could block requests.
  • Message Queued vs. Delivered: A successful API response (200 OK, message: "message(s) queued") only means Plivo accepted the request. It doesn't guarantee delivery to the handset. For delivery confirmation, you need to implement webhook handlers for Plivo's status callbacks (using the url and method parameters in the messages.create call). This is a more advanced topic.
  • Large Media Files: Very large media files might take longer to process or could potentially time out. Consider optimizing media file sizes. Check Plivo's limits on file size if you encounter issues.
<!-- GAP: Missing specific timeout values and timeout handling strategies (Type: Critical, Priority: High) --> <!-- DEPTH: No troubleshooting flowchart or decision tree (Priority: Medium) --> <!-- EXPAND: Could add logging best practices for debugging production issues (Type: Enhancement) -->

Production Deployment Best Practices

  • Environment Variables: In production, never commit your .env file. Use your hosting provider's mechanism for setting environment variables (e.g., Heroku Config Vars, AWS Systems Manager Parameter Store, Docker environment variables).
  • Process Management: Use a process manager like pm2 or run your application within a container (Docker) to handle restarts, clustering (for multi-core utilization), and logging.
    • Example with pm2: npm install -g pm2, then pm2 start src/app.js --name fastify-plivo-mms
  • HTTPS: Always run your production application behind a reverse proxy (like Nginx or Caddy) that handles HTTPS termination. Fastify itself can handle HTTPS, but offloading it is common practice.
  • Logging: Configure production logging to output structured JSON (Fastify's default) and forward these logs to a centralized logging service (e.g., Better Stack, Datadog, ELK stack) for analysis and monitoring.
  • Monitoring: Set up external uptime monitoring (e.g., Better Stack Uptime, UptimeRobot) to ping your /health endpoint. Monitor application performance (APM tools) and error rates.
<!-- GAP: Missing Docker/Kubernetes deployment examples (Type: Substantive, Priority: Medium) --> <!-- DEPTH: No guidance on horizontal scaling or load balancing (Priority: High) --> <!-- EXPAND: Could add infrastructure as code examples (Terraform/CloudFormation) (Type: Enhancement) --> <!-- GAP: Missing backup/disaster recovery considerations (Type: Critical, Priority: Medium) -->

Plivo MMS Frequently Asked Questions

How do I send MMS with Plivo and Node.js?

To send MMS with Plivo and Node.js, install the Plivo SDK (npm install plivo), initialize a Plivo client with your Auth ID and Auth Token, and call client.messages.create() with parameters including src (your Plivo number), dst (recipient in E.164 format), text, media_urls (array of public media URLs), and type: 'mms'. Plivo supports MMS only within the US and Canada.

What file formats does Plivo MMS support?

Plivo MMS supports JPEG, GIF, and PNG for images, MP3 for audio, and MP4 for video. Media files must be hosted on publicly accessible URLs that Plivo servers can fetch. For Canadian carriers, keep MMS attachments smaller than 1 MB to ensure broad compatibility across all networks. You can include up to 10 media URLs per MMS message.

<!-- DEPTH: Missing specific file size limits per format (Priority: Medium) -->

What is E.164 phone number format?

E.164 is the international phone number format standardized by ITU-T. It consists of a plus sign (+) followed by a country code (1-3 digits) and subscriber number, with a maximum of 15 digits total. Example: +14151234567 for a US number. Both source and destination numbers must use E.164 format for Plivo API calls.

Why use Fastify for MMS messaging?

Fastify is a high-performance, low-overhead Node.js web framework ideal for MMS messaging services. It provides built-in JSON schema validation, automatic request/response serialization, extensive plugin ecosystem (including @fastify/rate-limit), and superior performance compared to Express. Fastify's decorator pattern simplifies sharing the Plivo client across routes.

<!-- EXPAND: Could add performance benchmarks vs Express (Type: Enhancement) -->

What are Plivo API rate limits?

Plivo enforces a default limit of 300 API requests per 5 seconds for non-call APIs and supports up to 100 concurrent MMS requests. When limits are exceeded, Plivo responds with HTTP 429 Too Many Requests. Implement rate limiting in your Fastify application using @fastify/rate-limit to control costs and prevent abuse.

Can I send MMS to countries outside US and Canada with Plivo?

No. Plivo supports MMS messaging exclusively within the US and Canada. Attempts to send MMS to other countries may fail or be converted to SMS with a media link. You can only send and receive MMS using US or Canadian Plivo phone numbers (local long code, toll-free, or short code).

How do I handle Plivo MMS delivery status?

Plivo's messages.create() returns a 200 OK response when messages are queued, not delivered. For delivery confirmation, implement webhook handlers for Plivo's status callbacks by adding url and method parameters to your API call. Plivo will POST delivery status updates (delivered, failed, undelivered) to your webhook endpoint.

<!-- GAP: Missing webhook implementation example (Type: Substantive, Priority: High) --> <!-- DEPTH: No explanation of webhook security/verification (Priority: High) -->

What Node.js version should I use for Plivo MMS?

Use Node.js 22.x LTS for production applications. Node.js 22 is in Active LTS until October 2025 and Maintenance LTS until April 2027. This version provides full support for modern JavaScript features, security updates, and optimal performance for Fastify and Plivo SDK integration.

Now that you've built a Fastify MMS service with Plivo, consider exploring these related topics:

Wrapping Up

You have now successfully built a Fastify application capable of sending MMS messages using the Plivo Node.js SDK. We covered project setup, secure credential management, API route implementation with validation, error handling, rate limiting, and essential testing procedures.

This provides a solid foundation for integrating MMS capabilities into your Node.js applications. From here, you could expand the service to include status webhooks for delivery tracking, build a frontend interface, or integrate it into a larger communication workflow.

<!-- EXPAND: Could add next steps roadmap with advanced features (Type: Enhancement) -->