code examples

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

Send MMS with Plivo in Node.js: Complete MMS API Integration Guide [2025]

Learn how to send MMS messages with Plivo API in Node.js. Complete guide with Express.js integration, multimedia messaging setup, validation, error handling, and production deployment.

Multimedia Messaging Service (MMS) transforms user communication by enabling you to send MMS messages with images, GIFs, videos, and audio alongside text. While standard SMS delivers text only, multimedia messaging creates engaging experiences that capture attention and drive action. MMS messages achieve 3–5× higher engagement rates than SMS, with click-through rates averaging 15–20% compared to 3–5% for text-only messages.

When to use MMS vs. SMS:

  • Use MMS for product launches, event invitations, visual confirmations, QR codes, branded content, or tutorial videos.
  • Use SMS for time-sensitive alerts, appointment reminders, one-time passwords, or when targeting international recipients where MMS support varies.

This guide shows you how to build production-ready Node.js MMS applications using the Plivo API. Master Plivo MMS integration with Express.js by creating secure API endpoints, implementing validation, handling errors gracefully, and deploying with confidence. Build a simple API endpoint that accepts recipient details and media information, then reliably sends MMS via Plivo.

Project Goals:

  • Set up a Node.js project using Express.js.
  • Integrate the Plivo Node.js SDK for sending MMS.
  • Build a secure API endpoint to trigger MMS sending.
  • Implement proper configuration management for API keys and settings.
  • Include input validation, error handling, and basic logging.
  • Provide instructions for testing and verification.
  • Discuss deployment considerations and potential caveats.

Technologies Used:

  • Node.js: JavaScript runtime for server-side development (version 18+ recommended).
  • Express.js: Minimal and flexible Node.js web application framework.
  • Plivo Node.js SDK: Simplifies interaction with the Plivo API.
  • dotenv: Manages environment variables for configuration.
  • express-validator: Middleware for request data validation.
  • Postman / curl: Tools for testing the API endpoint.

Prerequisites:

  • Node.js 18+ and npm (or yarn) installed.
  • A Plivo account (free trial account available).
  • Basic understanding of JavaScript, Node.js, APIs, and terminal commands.
  • A publicly accessible URL for your media file. Host files on AWS S3, Cloudinary, or use public URLs for testing. The URL must be accessible without authentication, support HTTPS (recommended), and serve the correct MIME type headers.

System Architecture:

mermaid
graph LR
    A[Client / User] -- HTTP POST Request --> B(Node.js / Express API);
    B -- Send MMS Request --> C(Plivo API);
    C -- Delivers MMS --> D(Recipient's Mobile Device);
    B -- API Response (Success/Error) --> A;

    subgraph 'Our Application'
        B
    end

    subgraph 'External Service'
        C
    end

This guide walks you through creating the "Node.js / Express API" component.

1. Setting Up the Project

Initialize your Node.js project and install the necessary dependencies.

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

    bash
    mkdir node-plivo-mms
    cd node-plivo-mms
  2. Initialize Node.js Project: This creates a package.json file to manage dependencies and project information.

    bash
    npm init -y
  3. Install Dependencies: Install express for the web server, plivo for the SDK, dotenv for environment variables, and express-validator for input validation.

    bash
    npm install express plivo dotenv express-validator
    • express: The web framework.
    • plivo: The official Plivo SDK.
    • dotenv: Loads environment variables from a .env file into process.env. Essential for keeping secrets out of code.
    • express-validator: Provides middleware for validating incoming request data, crucial for security and data integrity.
  4. Set Up Project Structure: Create the following basic structure within your node-plivo-mms directory:

    text
    node-plivo-mms/
    ├── src/
    │   ├── controllers/
    │   │   └── mmsController.js
    │   ├── middleware/
    │   │   └── validators.js
    │   ├── routes/
    │   │   └── mmsRoutes.js
    │   ├── services/
    │   │   └── plivoService.js
    │   └── app.js
    ├── .env
    ├── .gitignore
    └── package.json
    • src/: Contains the main application code.
    • controllers/: Handles incoming requests, validates data, and calls services.
    • middleware/: Contains custom middleware, like validators.
    • routes/: Defines API endpoints and maps them to controllers.
    • services/: Contains business logic, including interaction with external APIs like Plivo.
    • app.js: Sets up the Express application, middleware, and starts the server.
    • .env: Stores sensitive configuration like API keys.
    • .gitignore: Prevents sensitive files (like .env) and unnecessary files (like node_modules) from being committed to version control.
  5. Create .gitignore: Create a file named .gitignore in the project root and add the following:

    text
    # Dependencies
    node_modules/
    
    # Environment variables
    .env
    
    # Logs
    logs
    *.log
    
    # OS generated files
    .DS_Store
    Thumbs.db
  6. Set Up Environment Variables (.env): Create a file named .env in the project root. This file holds your Plivo credentials and configuration. Never commit this file to Git.

    dotenv
    # Plivo Credentials
    PLIVO_AUTH_ID=YOUR_PLIVO_AUTH_ID
    PLIVO_AUTH_TOKEN=YOUR_PLIVO_AUTH_TOKEN
    
    # Plivo MMS Enabled Number (must be purchased from Plivo and support MMS)
    PLIVO_SOURCE_NUMBER=YOUR_PLIVO_MMS_NUMBER
    
    # Server Configuration
    PORT=3000
    • PLIVO_AUTH_ID, PLIVO_AUTH_TOKEN: Your Plivo API credentials.
    • PLIVO_SOURCE_NUMBER: The Plivo phone number in E.164 format (e.g., +14155551212) that sends the MMS. It must be MMS-enabled. E.164 is the international telephone numbering standard that requires a + prefix, country code (1–3 digits), and subscriber number (up to 12 digits), with no spaces or special characters.
    • PORT: The port on which your Node.js application listens.

2. Plivo Account Setup for MMS

Before writing code, ensure your Plivo account is ready.

  1. Sign Up/Log In: Go to Plivo.com and sign up for an account or log in.

  2. Find Auth ID and Auth Token:

    • Log in to the Plivo console.
    • Your Auth ID and Auth Token are prominently displayed on the overview dashboard page.
    • Copy these values and paste them into your .env file for PLIVO_AUTH_ID and PLIVO_AUTH_TOKEN.
  3. Get an MMS-Enabled Phone Number:

    • Navigate to "Phone Numbers" > "Buy Numbers" in the Plivo console.
    • Search for numbers, ensuring you filter by "MMS" capability. Plivo currently supports MMS in the US and Canada.
    • Purchase a suitable number. MMS-enabled numbers typically cost $1–$3/month, similar to standard SMS numbers, with per-message charges of $0.02–$0.04 depending on the destination carrier.
    • Selection criteria: Choose local numbers (matching your audience's area code) for higher engagement and trust. Toll-free numbers work but may have limited MMS support on some carriers.
    • Go to "Phone Numbers" > "Your Numbers". Verify that the number you purchased has the MMS capability listed.
    • Copy this number (including the leading + and country code) and paste it into your .env file for PLIVO_SOURCE_NUMBER.
  4. Trial Account Limitation (Sandbox Numbers):

    • IMPORTANT: If you use a Plivo trial account, you can only send messages to phone numbers verified in your account. This is the most common reason for failures when starting.
    • Navigate to "Messaging" > "Sandbox Numbers".
    • Add and verify the phone number(s) you intend to use as recipients during testing. Failure to do this results in failed messages.
    • Common trial account issues:
      • Error 401: Your Auth ID or Auth Token is incorrect.
      • Error 403: The destination number isn't verified in sandbox mode.
      • Error 400: The source number doesn't support MMS or the media URL is inaccessible.

3. Implement Plivo MMS API Service in Node.js

Encapsulate the Plivo interaction within a dedicated service file.

Create src/services/plivoService.js:

javascript
// src/services/plivoService.js

const plivo = require('plivo');
require('dotenv').config(); // Ensure environment variables are loaded

// Initialize the Plivo client only once using environment variables
// Note: Client initialization captures env vars at the time of require/load.
// For tests involving changing credentials, ensure modules are reset.
let client;
try {
  client = new plivo.Client(
    process.env.PLIVO_AUTH_ID,
    process.env.PLIVO_AUTH_TOKEN
  );
} catch (error) {
  console.error("Failed to initialize Plivo client. Check AUTH_ID/AUTH_TOKEN.", error);
  // Depending on application needs, you might want to prevent startup
  // or handle this more gracefully than just logging.
}

/**
 * Sends an MMS message using Plivo.
 * Reads sensitive config directly inside the function for better testability
 * and to reflect potential runtime changes if not using singleton patterns.
 *
 * @param {string} destinationNumber - The recipient's phone number (E.164 format).
 * @param {string} text - The text content of the message.
 * @param {string} mediaUrl - The publicly accessible URL of the media file.
 * @returns {Promise<object>} - A promise that resolves with the Plivo API response.
 * @throws {Error} - Throws an error if sending fails or config is missing.
 */
const sendMms = async (destinationNumber, text, mediaUrl) => {
  const sourceNumber = process.env.PLIVO_SOURCE_NUMBER;
  const authId = process.env.PLIVO_AUTH_ID;
  const authToken = process.env.PLIVO_AUTH_TOKEN;

  // Check for essential configuration *inside* the function call
  if (!sourceNumber) {
    throw new Error('Plivo source number is not configured in .env');
  }
  if (!authId || !authToken) {
    throw new Error('Plivo Auth ID or Auth Token is missing in .env');
  }
  if (!client) {
      throw new Error('Plivo client is not initialized. Check credentials.');
  }

  // Log initiation attempt (consider replacing with a proper logger in production)
  console.log(`Attempting to send MMS to ${destinationNumber} from ${sourceNumber}`);

  try {
    const response = await client.messages.create(
      sourceNumber,      // src
      destinationNumber, // dst
      text,              // text
      {
        type: 'mms',
        media_urls: [mediaUrl], // Array of media URLs
        // You can also use media_ids if you upload media via the Plivo console:
        // media_ids: ['YOUR_UPLOADED_MEDIA_ID']
      },
      // Optional: Callback URL for delivery status updates
      // {
      //   url: 'YOUR_STATUS_CALLBACK_URL',
      //   method: 'POST'
      // }
    );

    // Log success (consider replacing with a proper logger in production)
    console.log('Plivo API Response:', response);
    return response;
  } catch (error) {
    // Log error (consider replacing with a proper logger in production)
    console.error('Error sending MMS via Plivo:', error);
    // Re-throw the error to be handled by the controller/middleware
    throw error;
  }
};

module.exports = {
  sendMms,
};

Use media_urls vs. media_ids:

  • media_urls: Reference externally hosted files (AWS S3, Cloudinary, etc.). Plivo fetches the media at send time. Use this for dynamic content or when you manage your own media storage.
  • media_ids: Reference files uploaded to Plivo's media library via console or API. Use this for frequently reused assets (logos, brand images) to reduce latency and avoid external URL failures.

Plivo API response structure:

javascript
{
  api_id: "abc123-def456-ghi789",
  message_uuid: ["msg-uuid-1234"],
  message: "message(s) queued"
}
  • message_uuid: Array of unique IDs for tracking delivery status.
  • api_id: Request identifier for Plivo support inquiries.

Common Plivo error codes:

  • 400: Invalid parameters (bad phone format, inaccessible media URL, non-MMS number).
  • 401: Authentication failed (check AUTH_ID/AUTH_TOKEN).
  • 402: Insufficient account balance.
  • 403: Number not verified (trial accounts) or blocked destination.
  • 500: Plivo server error (retry with exponential backoff).

Explanation:

  • Import plivo and load environment variables using dotenv.config().
  • The Plivo Client is initialized. It captures PLIVO_AUTH_ID and PLIVO_AUTH_TOKEN at load time.
  • The sendMms function takes the destination number, text, and media URL as arguments.
  • Checks for essential configuration (sourceNumber, PLIVO_AUTH_ID, PLIVO_AUTH_TOKEN) are performed inside the function to make testing environment variable changes easier.
  • client.messages.create is the core Plivo SDK method.
    • src: Your Plivo MMS number (PLIVO_SOURCE_NUMBER).
    • dst: The recipient's number.
    • text: The message body.
    • The fourth argument is an options object where you specify type: 'mms' and provide the media_urls as an array. Plivo fetches the media from this URL and attaches it.
    • The URL in media_urls must be publicly accessible.
    • An optional fifth argument can provide callback details for status updates (not implemented in this basic guide).
  • Use async/await for cleaner asynchronous code.
  • Note on Logging: console.log and console.error are used here for simplicity. In production, replace these with a robust logging library like Winston or Pino (discussed further in Deployment section).
  • Errors during the API call are caught, logged, and re-thrown for higher-level handling.

4. Building the API Layer (Routes and Controller)

Create the Express route and controller to expose MMS sending functionality via an API endpoint.

1. Create Route (src/routes/mmsRoutes.js):

javascript
// src/routes/mmsRoutes.js

const express = require('express');
const mmsController = require('../controllers/mmsController');
const { validateSendMms } = require('../middleware/validators'); // We'll create this next

const router = express.Router();

// Define the endpoint: POST /api/mms/send
// It uses validation middleware before hitting the controller
router.post('/send', validateSendMms, mmsController.sendMmsHandler);

module.exports = router;

Explanation:

  • Create an Express router instance.
  • Define a POST route at /send. The full path is /api/mms/send when mounted in app.js.
  • Include validateSendMms middleware to validate the request body before mmsController.sendMmsHandler executes.
  • Pass the request to the controller function.

2. Create Controller (src/controllers/mmsController.js):

javascript
// src/controllers/mmsController.js

const { validationResult } = require('express-validator');
const plivoService = require('../services/plivoService');

/**
 * Handles the API request to send an MMS.
 * Validates input, calls the Plivo service, and sends the response.
 */
const sendMmsHandler = async (req, res, next) => {
  // Check for validation errors
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    // If validation fails, return 400 Bad Request with error details
    return res.status(400).json({ errors: errors.array() });
  }

  // Extract validated data from request body
  const { destinationNumber, text, mediaUrl } = req.body;

  try {
    // Call the service function to send the MMS
    const result = await plivoService.sendMms(destinationNumber, text, mediaUrl);

    // Send success response back to the client
    res.status(200).json({
      message: 'MMS sending initiated successfully.',
      plivoResponse: result,
    });
  } catch (error) {
    // Pass errors to the central error handling middleware (created later)
    next(error);
  }
};

module.exports = {
  sendMmsHandler,
};

Explanation:

  • Import the validationResult function from express-validator and the plivoService.
  • The sendMmsHandler function first checks if validationResult(req) found any errors defined by validator middleware. If so, it immediately sends a 400 Bad Request response.
  • If validation passes, extract destinationNumber, text, and mediaUrl from the request body (req.body).
  • Call plivoService.sendMms within a try...catch block.
  • On success, send a 200 OK response including Plivo API's confirmation.
  • If plivoService.sendMms throws an error (either configuration error or Plivo API error), the catch block catches it and passes it to Express's error handling mechanism using next(error).

5. Implementing Input Validation

Validate input to ensure security and prevent errors. Use express-validator.

Create the directory src/middleware and inside it, create validators.js:

javascript
// src/middleware/validators.js

const { body } = require('express-validator');

const validateSendMms = [
  // Validate destinationNumber: must be in E.164 format
  // E.164 format is required because it provides unambiguous international phone number formatting,
  // ensuring carrier routing works correctly across all networks.
  // Consider using 'google-libphonenumber' for robust validation if needed.
  body('destinationNumber')
    .trim()
    .notEmpty().withMessage('Destination number is required.')
    .isString().withMessage('Destination number must be a string.')
    .matches(/^\+[1-9]\d{1,14}$/).withMessage('Destination number must be in E.164 format (e.g., +14155551212).'),

  // Validate text: must be a non-empty string
  // Optional: Add .isLength({ max: 1600 }) to enforce MMS text limits
  body('text')
    .trim()
    .notEmpty().withMessage('Text message is required.')
    .isString().withMessage('Text must be a string.'),

  // Validate mediaUrl: must be a valid URL
  // Note: This validates URL format only, not accessibility or content type
  body('mediaUrl')
    .trim()
    .notEmpty().withMessage('Media URL is required.')
    .isURL().withMessage('Media URL must be a valid URL.'),
];

module.exports = {
  validateSendMms,
};

Explanation:

  • Use the body function from express-validator to define validation rules for fields expected in the request body.
  • destinationNumber: Check it's not empty, is a string, and roughly matches the E.164 format (starts with +, followed by digits). For production, consider more robust phone number validation.
  • text: Check for non-emptiness and being a string.
  • mediaUrl: Check for non-emptiness and being a valid URL format using isURL().
  • .trim() removes leading/trailing whitespace.
  • .withMessage() provides custom error messages.
  • This validateSendMms array of middleware is exported and used in src/routes/mmsRoutes.js.

6. Setting Up the Express App and Error Handling

Tie everything together in src/app.js.

javascript
// src/app.js

const express = require('express');
const dotenv = require('dotenv');
const mmsRoutes = require('./routes/mmsRoutes');

// Load environment variables from .env file
dotenv.config();

// Create Express app instance
const app = express();

// Middleware to parse JSON request bodies
app.use(express.json());
// Middleware to parse URL-encoded request bodies (optional, but common)
app.use(express.urlencoded({ extended: true }));

// --- Routes ---
// Mount the MMS routes under the /api/mms path
app.use('/api/mms', mmsRoutes);

// --- Basic Health Check Route ---
app.get('/health', (req, res) => {
  res.status(200).json({ status: 'UP', timestamp: new Date().toISOString() });
});

// --- Central Error Handling Middleware ---
// This middleware catches errors passed via next(error)
// IMPORTANT: It must be defined *after* all other app.use() and routes calls
app.use((err, req, res, next) => {
  // Use a proper logger in production instead of console.error
  console.error('Unhandled Error:', err);

  // Determine status code: use error's status or default to 500
  const statusCode = err.status || err.statusCode || 500;

  // Send a generic error response
  // Avoid sending detailed internal error messages in production
  res.status(statusCode).json({
    message: 'An unexpected error occurred.',
    // Optionally include error details in development only
    error: process.env.NODE_ENV === 'development' ? err.message : undefined,
  });
});

// --- Start the Server ---
const PORT = process.env.PORT || 3000;

app.listen(PORT, () => {
  // Use a proper logger in production
  console.log(`Server running on port ${PORT}`);
  // Verify essential config on startup
  if (!process.env.PLIVO_AUTH_ID || !process.env.PLIVO_AUTH_TOKEN) {
      console.warn('WARNING: Plivo Auth ID or Token is missing in .env! Client may not initialize.');
  }
  if (!process.env.PLIVO_SOURCE_NUMBER) {
      console.warn('WARNING: Plivo source number is missing in .env! MMS sending will fail.');
  } else {
      console.log(`Configured to send MMS from: ${process.env.PLIVO_SOURCE_NUMBER}`);
  }
});

module.exports = app; // Export for potential testing

Explanation:

  • Load dotenv first to ensure environment variables are available.
  • Create the express app.
  • express.json(): Essential middleware to parse incoming JSON request bodies (like the one sent to /api/mms/send).
  • Mount mmsRoutes under the /api/mms prefix. The actual endpoint is POST /api/mms/send.
  • Health Check: A simple /health endpoint is added as a best practice for monitoring.
  • Central Error Handling: This is crucial. Any error passed via next(error) (like in the controller's catch block) is caught here.
    • It logs the error (emphasizing that a real logger should be used in production).
    • It sets an appropriate HTTP status code (defaulting to 500 Internal Server Error).
    • It sends a standardized JSON error response to the client, avoiding leaking sensitive stack traces in production.
  • The server starts listening on the configured PORT.
  • Startup logs check for the presence of essential Plivo configuration from .env.
  • Note on Logging: This file uses console.log and console.warn. Replace these with a dedicated logging library for production applications.

7. Security Considerations

While this guide covers basics, production systems require more robust security.

  • API Key Security: Your PLIVO_AUTH_ID and PLIVO_AUTH_TOKEN are highly sensitive.
    • NEVER commit them to version control. Use .env and .gitignore.
    • In production deployments, use secure secret management solutions provided by your cloud provider (e.g., AWS Secrets Manager, Google Secret Manager, Azure Key Vault) or tools like HashiCorp Vault instead of .env files.
  • Input Validation: Basic validation is implemented with express-validator. Ensure your validation rules are strict and cover all expected inputs to prevent injection attacks or unexpected behavior.
  • Rate Limiting: Protect your API endpoint (and your Plivo account balance) from abuse by implementing rate limiting. The express-rate-limit package is a popular choice.
    bash
    npm install express-rate-limit
    Add it to app.js before your routes:
    javascript
    // src/app.js
    const rateLimit = require('express-rate-limit');
    // ... other imports
    
    const limiter = rateLimit({
      windowMs: 15 * 60 * 1000, // 15 minutes
      max: 100, // Limit each IP to 100 requests per windowMs
      standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
      legacyHeaders: false, // Disable the `X-RateLimit-*` headers
      message: 'Too many requests from this IP, please try again after 15 minutes',
    });
    
    // Apply the rate limiting middleware to API calls
    // Apply *before* the specific routes you want to limit
    app.use('/api/mms', limiter); // Apply specifically to MMS routes
    
    // --- Routes ---
    app.use('/api/mms', mmsRoutes);
    // ... rest of app.js
  • Authentication/Authorization: This guide doesn't include authentication. In a real application, protect the /api/mms/send endpoint so only authorized users or systems can trigger MMS sending (e.g., using API keys, JWT tokens, OAuth).
  • HTTPS: Always run your application behind a reverse proxy (like Nginx or Caddy) configured with TLS/SSL certificates (e.g., from Let's Encrypt) to ensure communication is encrypted. Don't run Node.js directly exposed to the internet on port 80/443.
  • CORS Configuration: Add the cors package to control which domains can access your API:
    bash
    npm install cors
    Configure in app.js:
    javascript
    const cors = require('cors');
    app.use(cors({
      origin: process.env.ALLOWED_ORIGINS?.split(',') || '*',
      credentials: true
    }));
  • Security Headers: Use helmet to set secure HTTP headers:
    bash
    npm install helmet
    Add to app.js:
    javascript
    const helmet = require('helmet');
    app.use(helmet());
  • PII Handling: Phone numbers are Personally Identifiable Information (PII). Implement proper data handling:
    • Encrypt phone numbers at rest in your database.
    • Log only masked phone numbers (e.g., +1415555****).
    • Implement data retention policies (auto-delete old records).
    • Provide user opt-out mechanisms.
    • Ensure GDPR/CCPA compliance for data subject access requests.

8. Testing Your Plivo MMS Integration

Test to ensure your code works as expected and prevent regressions.

1. Unit Testing (Plivo Service): Unit test the service layer by mocking the Plivo client.

Install Jest:

bash
npm install --save-dev jest

Add a test script to package.json:

json
// package.json
{
  // ... other fields
  "scripts": {
    "start": "node src/app.js",
    "test": "jest"
  },
  // ...
}

Create a test file src/services/plivoService.test.js:

javascript
// src/services/plivoService.test.js

// Mock the plivo module *before* importing the service
const mockCreate = jest.fn();
jest.mock('plivo', () => {
  // Mock the constructor and the messages.create method
  return {
    Client: jest.fn().mockImplementation(() => {
      // This is the instance returned by new plivo.Client()
      return {
        messages: {
          create: mockCreate,
        },
      };
    }),
  };
});

// Mock dotenv - prevent it from actually reading .env during tests
jest.mock('dotenv', () => ({
  config: jest.fn(),
}));

// Store original process.env
const OLD_ENV = process.env;

describe('Plivo Service', () => {
  beforeEach(() => {
    // Reset modules before each test to clear cache and re-require with fresh env vars
    jest.resetModules();
    // Clear mock function calls history
    mockCreate.mockClear();
    // Restore process.env and set default test values
    process.env = {
      ...OLD_ENV, // Preserve original env vars
      PLIVO_AUTH_ID: 'test-auth-id',
      PLIVO_AUTH_TOKEN: 'test-auth-token',
      PLIVO_SOURCE_NUMBER: '+15551112222',
    };
  });

  afterAll(() => {
    // Restore original environment variables after all tests
    process.env = OLD_ENV;
  });

  it('should call plivo.messages.create with correct parameters', async () => {
    // Re-require the service *inside the test* after setting env vars and resetting modules
    const plivoService = require('./plivoService');
    const dest = '+15553334444';
    const text = 'Test message';
    const mediaUrl = 'http://example.com/image.jpg';
    const expectedPlivoResponse = { messageUuid: 'some-uuid' };

    // Configure the mock to return a resolved promise
    mockCreate.mockResolvedValue(expectedPlivoResponse);

    const result = await plivoService.sendMms(dest, text, mediaUrl);

    // Assert that plivo.messages.create was called once
    expect(mockCreate).toHaveBeenCalledTimes(1);

    // Assert it was called with the correct arguments
    expect(mockCreate).toHaveBeenCalledWith(
      process.env.PLIVO_SOURCE_NUMBER, // src
      dest,                            // dst
      text,                            // text
      { type: 'mms', media_urls: [mediaUrl] } // options
    );

    // Assert the function returned the expected response
    expect(result).toEqual(expectedPlivoResponse);
  });

  it('should throw an error if Plivo API fails', async () => {
    // Re-require the service
    const plivoService = require('./plivoService');
    const dest = '+15553334444';
    const text = 'Test message';
    const mediaUrl = 'http://example.com/image.jpg';
    const expectedError = new Error('Plivo API Error');

    // Configure the mock to return a rejected promise
    mockCreate.mockRejectedValue(expectedError);

    // Assert that calling sendMms throws an error
    await expect(plivoService.sendMms(dest, text, mediaUrl))
      .rejects.toThrow('Plivo API Error');

    // Ensure plivo.messages.create was still called
    expect(mockCreate).toHaveBeenCalledTimes(1);
  });

  it('should throw an error if source number is not configured', async () => {
    // Modify environment for this specific test
    delete process.env.PLIVO_SOURCE_NUMBER;
    // Re-require the service *after* modifying env and resetting modules (via beforeEach)
    const plivoService = require('./plivoService');

    await expect(plivoService.sendMms('+111', 'txt', 'url'))
      .rejects.toThrow('Plivo source number is not configured in .env');
    // Ensure Plivo API was not called
    expect(mockCreate).not.toHaveBeenCalled();
  });

  it('should throw an error if auth credentials are not configured', async () => {
    // Modify environment for this specific test
    delete process.env.PLIVO_AUTH_ID;
     // Re-require the service *after* modifying env and resetting modules (via beforeEach)
    const plivoService = require('./plivoService');

    await expect(plivoService.sendMms('+111', 'txt', 'url'))
        .rejects.toThrow('Plivo Auth ID or Auth Token is missing in .env');
     // Ensure Plivo API was not called
    expect(mockCreate).not.toHaveBeenCalled();
  });
});

Run tests: npm test

Explanation of Test Changes:

  • jest.resetModules() is added to beforeEach to ensure that plivoService (and its dependency on process.env) is freshly loaded for each test, reflecting any changes made to process.env.
  • The service is now required inside each test case to ensure the test uses the environment variables set for that specific test scope.
  • Added a test case for missing Auth ID/Token.

2. Integration Testing (API Endpoint): Test the full request-response cycle of your API endpoint.

Install Supertest:

bash
npm install --save-dev supertest

Create a test file src/routes/mmsRoutes.test.js:

javascript
// src/routes/mmsRoutes.test.js

const request = require('supertest');
const app = require('../app'); // Import your configured Express app
const plivoService = require('../services/plivoService'); // Import to mock

// Mock the service layer to avoid actual Plivo calls during integration tests
// Jest automatically hoist jest.mock calls to the top of the module
jest.mock('../services/plivoService');

describe('MMS API Endpoint (/api/mms/send)', () => {
  beforeEach(() => {
    // Reset mocks before each test
    // Ensure the mock implementation is cleared or reset if needed
    // For jest.fn(), mockClear() is usually sufficient.
    plivoService.sendMms.mockClear();
  });

  it('should send MMS successfully with valid input', async () => {
    const requestBody = {
      destinationNumber: '+14155551234',
      text: 'Hello via API',
      mediaUrl: 'https://media.giphy.com/media/26gscSULUcfKU7dHq/source.gif',
    };
    const mockPlivoResponse = { message_uuid: ['mock-uuid-123'] };

    // Configure the mock service function to resolve successfully
    plivoService.sendMms.mockResolvedValue(mockPlivoResponse);

    const response = await request(app)
      .post('/api/mms/send')
      .send(requestBody)
      .expect('Content-Type', /json/)
      .expect(200);

    // Check if the service was called correctly
    expect(plivoService.sendMms).toHaveBeenCalledTimes(1);
    expect(plivoService.sendMms).toHaveBeenCalledWith(
      requestBody.destinationNumber,
      requestBody.text,
      requestBody.mediaUrl
    );

    // Check the response body
    expect(response.body.message).toEqual('MMS sending initiated successfully.');
    expect(response.body.plivoResponse).toEqual(mockPlivoResponse);
  });

  it('should return 400 Bad Request for invalid input (missing field)', async () => {
    const invalidRequestBody = {
      // destinationNumber is missing
      text: 'Missing number',
      mediaUrl: 'https://example.com/image.png',
    };

    const response = await request(app)
      .post('/api/mms/send')
      .send(invalidRequestBody)
      .expect('Content-Type', /json/)
      .expect(400);

    // Check the error response structure
    expect(response.body.errors).toBeDefined();
    expect(response.body.errors.length).toBeGreaterThan(0);
    expect(response.body.errors[0].path).toEqual('destinationNumber'); // Check which field failed
    expect(response.body.errors[0].msg).toContain('required'); // Check error message

    // Ensure the service was NOT called
    expect(plivoService.sendMms).not.toHaveBeenCalled();
  });

   it('should return 400 Bad Request for invalid URL format', async () => {
    const invalidRequestBody = {
      destinationNumber: '+14155551234',
      text: 'Bad URL',
      mediaUrl: 'not-a-valid-url', // Invalid URL
    };

    const response = await request(app)
      .post('/api/mms/send')
      .send(invalidRequestBody)
      .expect('Content-Type', /json/)
      .expect(400);

    expect(response.body.errors).toBeDefined();
    expect(response.body.errors[0].path).toEqual('mediaUrl');
    expect(response.body.errors[0].msg).toContain('valid URL');

    expect(plivoService.sendMms).not.toHaveBeenCalled();
  });


  it('should return 500 Internal Server Error if service throws an error', async () => {
    const requestBody = {
      destinationNumber: '+14155551234',
      text: 'This will fail',
      mediaUrl: 'https://example.com/fail.gif',
    };
    const errorMessage = 'Plivo service failed';

    // Configure the mock service to reject
    plivoService.sendMms.mockRejectedValue(new Error(errorMessage));

    const response = await request(app)
      .post('/api/mms/send')
      .send(requestBody)
      .expect('Content-Type', /json/)
      .expect(500);

    // Check the generic error response
    expect(response.body.message).toEqual('An unexpected error occurred.');
     // Optionally check error message in development
     // Note: Requires setting NODE_ENV=development for the test runner
     // if (process.env.NODE_ENV === 'development') {
     //    expect(response.body.error).toEqual(errorMessage);
     // }


    // Ensure the service was called
    expect(plivoService.sendMms).toHaveBeenCalledTimes(1);
  });
});

Run tests: npm test

Best practices for testing:

  • Maintain 80%+ code coverage for critical paths (send logic, validation, error handling).
  • Mock external dependencies (Plivo SDK) to avoid real API calls and charges.
  • Test edge cases: missing fields, invalid formats, network failures, timeout scenarios.
  • Use separate test databases or mock data stores for integration tests.

9. Running the Application

  1. Ensure .env is correct: Double-check your PLIVO_AUTH_ID, PLIVO_AUTH_TOKEN, and PLIVO_SOURCE_NUMBER in the .env file.
  2. Start the server:
    bash
    node src/app.js
    You should see output indicating the server is running and the configured source number.
  3. Test with curl:
    bash
    curl -X POST http://localhost:3000/api/mms/send \
      -H "Content-Type: application/json" \
      -d '{
        "destinationNumber": "+14155551234",
        "text": "Hello from Plivo MMS!",
        "mediaUrl": "https://media.giphy.com/media/26gscSULUcfKU7dHq/source.gif"
      }'
    Expected success response:
    json
    {
      "message": "MMS sending initiated successfully.",
      "plivoResponse": {
        "api_id": "abc-123",
        "message_uuid": ["msg-uuid-456"],
        "message": "message(s) queued"
      }
    }
  4. Test with Postman:
    • Create a new POST request to http://localhost:3000/api/mms/send
    • Set Headers: Content-Type: application/json
    • Set Body (raw JSON):
      json
      {
        "destinationNumber": "+14155551234",
        "text": "Test MMS",
        "mediaUrl": "https://example.com/image.jpg"
      }
    • Click Send and verify the response.

Monitoring MMS delivery status:

  1. Check Plivo console logs (Messages > Logs) for real-time status updates.
  2. Implement webhook handlers to receive delivery callbacks:
    javascript
    // Add to src/routes/mmsRoutes.js
    router.post('/status', (req, res) => {
      const { Status, MessageUUID, ErrorCode } = req.body;
      console.log(`Message ${MessageUUID}: ${Status}`);
      if (ErrorCode) {
        console.error(`Error: ${ErrorCode}`);
      }
      res.sendStatus(200);
    });
  3. Configure webhook URL in Plivo console under Messaging settings.

Debugging failed sends:

  • Error 400: Check phone number format (must be E.164), verify media URL is accessible, confirm source number supports MMS.
  • Error 403: Add destination number to sandbox (trial accounts only).
  • Error 402: Add funds to your Plivo account.
  • Timeout errors: Increase request timeout, check network connectivity, verify Plivo API status page.

Frequently Asked Questions

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

Install the Plivo SDK (npm install plivo), authenticate with your Auth ID and Token, then call client.messages.create() with the from number (MMS-enabled), to number, text, and media_urls array. Plivo delivers the MMS to the recipient's device via your verified phone number.

What file formats does Plivo MMS support?

Plivo MMS supports images (JPEG, PNG, GIF), videos (MP4, 3GP), and audio (MP3, AMR). Maximum file size is 5MB per message. Host files on publicly accessible URLs (HTTPS recommended) and pass them in the media_urls array when sending. Some carriers restrict specific formats (e.g., AT&T may block certain animated GIFs), so test across carriers before production deployment.

How much does it cost to send MMS with Plivo?

Plivo MMS pricing varies by destination country and carrier. US/Canada MMS typically costs $0.02–$0.04 per message. Check your Plivo dashboard pricing page for current rates. MMS costs more than SMS due to multimedia content delivery and increased carrier processing. Plivo doesn't charge separately for media storage or bandwidth – all costs are per-message.

Do I need a special Plivo phone number for MMS?

Yes, your Plivo phone number must be MMS-enabled. Not all Plivo numbers support MMS by default. Purchase MMS-capable numbers from your Plivo console or verify existing numbers have MMS capability. SMS-only numbers reject MMS requests with error responses.

How do I handle Plivo MMS delivery callbacks?

Configure a webhook URL in your Plivo console under Messaging settings. Plivo sends POST requests to this URL with delivery status updates. Parse the Status, MessageUUID, and ErrorCode fields to track delivered, failed, or queued messages in your application.

Example webhook implementation:

javascript
router.post('/webhook/mms-status', (req, res) => {
  const { Status, MessageUUID, ErrorCode, From, To } = req.body;

  // Update your database
  updateMessageStatus(MessageUUID, Status, ErrorCode);

  // Log for monitoring
  console.log(`MMS ${MessageUUID} from ${From} to ${To}: ${Status}`);

  // Always respond with 200 to acknowledge receipt
  res.sendStatus(200);
});

Can I send MMS to international numbers with Plivo?

Yes, but MMS availability varies by country and carrier. Many countries support MMS, but some regions have limited carrier support or higher costs. Verify international MMS support in Plivo's coverage documentation before implementing. Consider SMS fallback for unsupported destinations.

What is the maximum size for Plivo MMS messages?

Plivo enforces a 5MB limit per MMS message across all media files combined. If you include multiple images or videos, their total size cannot exceed 5MB. Compress or resize media files before sending to stay within limits and reduce delivery costs.

Best practices for media optimization:

  • Resize images to 640×480px or 1024×768px (most carriers downsample larger images).
  • Use JPEG with 70–80% quality for photos.
  • Compress videos to <2MB using H.264 codec.
  • Use GIF only when animation is essential (static images are more reliable).
  • Test media URLs before sending (verify 200 response, correct MIME type, HTTPS).

How do I test Plivo MMS without sending real messages?

Use Plivo's trial account with verified phone numbers (add recipients in Sandbox Numbers). Test with small image URLs (under 500KB) to validate integration. Check Plivo's message logs in the console for delivery status. Use tools like Postman or curl to test your API endpoint locally before production deployment.

10. Production Deployment

Environment setup:

  1. Replace console.log with Winston or Pino for structured logging.
  2. Use environment-specific configurations (.env.production, .env.staging).
  3. Store secrets in AWS Secrets Manager, Google Secret Manager, or Azure Key Vault.
  4. Enable HTTPS with TLS certificates (Let's Encrypt, AWS Certificate Manager).
  5. Configure reverse proxy (Nginx, Caddy) with rate limiting and request buffering.

Monitoring and observability:

  • Track MMS delivery rates, error rates, and latency with Datadog, New Relic, or Prometheus.
  • Set up alerts for failed sends, API errors, or rate limit violations.
  • Monitor Plivo account balance to prevent service interruptions.
  • Log masked phone numbers only (never full numbers in plaintext).

Scaling considerations:

  • Use Redis for rate limiting across multiple server instances.
  • Implement job queues (Bull, BullMQ) for high-volume MMS sending.
  • Add retry logic with exponential backoff for transient failures.
  • Cache frequently used media files in CDN (CloudFront, Cloudflare).

Deployment checklist:

  • All tests pass (npm test)
  • Environment variables configured in production
  • Rate limiting enabled
  • CORS configured for allowed origins
  • Security headers set (Helmet)
  • Logging library configured
  • Error tracking enabled (Sentry, Rollbar)
  • Health check endpoint verified
  • Webhook endpoints secured with signature verification
  • Database backup and recovery tested
  • Load testing completed (simulate 100+ requests/second)

Common production issues:

  • High latency: Cache Plivo client initialization, use connection pooling, optimize media URL resolution.
  • Memory leaks: Monitor Node.js heap usage, upgrade to Node.js 20+ for better garbage collection.
  • Rate limit hits: Implement distributed rate limiting, use separate Plivo accounts for different services.
  • Failed deliveries: Implement retry queues, track carrier-specific failure patterns, add SMS fallback.

Frequently Asked Questions

How to send MMS messages with Node.js?

Use the Plivo Node.js SDK and Express.js to create an API endpoint. This endpoint receives recipient details and media URLs, then uses the Plivo API to send the MMS message. The project requires setting up a Node.js project and installing necessary dependencies like Express.js, Plivo's Node.js SDK, and dotenv.

What is the Plivo Node.js SDK used for?

The Plivo Node.js SDK simplifies interaction with the Plivo API. It provides convenient functions for sending SMS and MMS messages, making calls, and other Plivo functionalities, directly within your Node.js application. It handles the low-level API communication details, making your code cleaner and easier to manage. It's crucial for sending MMS messages as described in the guide.

Why does MMS provide a richer user experience than SMS?

MMS supports multimedia content like images, GIFs, videos, and audio, along with text. This makes MMS more engaging and versatile compared to SMS, which is limited to text-only messages. MMS enables richer communication, making it suitable for various applications like sharing product demos or sending personalized greetings.

When should I use MMS instead of SMS in my application?

Consider MMS when you need to send more than just text. If your message involves visuals, audio, or short videos, MMS is the preferred choice. For example, send order confirmations with product images or promotional messages with engaging GIFs using MMS instead of plain text SMS.

Can I use a free trial Plivo account for testing MMS?

Yes, a free trial Plivo account is sufficient to test MMS functionality. However, remember you can only send test MMS messages to numbers verified within your Plivo sandbox environment during the trial period. This important step avoids common delivery issues when starting.

How to set up Plivo credentials for my Node.js app?

After signing up for a Plivo account, locate your Auth ID and Auth Token on the Plivo console dashboard. Store these securely in a `.env` file in your project root directory, and never commit this `.env` file to version control. The dotenv package will load these values into process.env at runtime.

What is the purpose of express-validator in the project?

Express-validator is used for request data validation. It ensures that incoming data to your API endpoint meets specific criteria, like required fields and data types, enhancing security and preventing errors caused by bad input. This adds a layer of protection and ensures data integrity.

How to verify recipients when using a Plivo trial account?

Within the Plivo console, navigate to 'Messaging' > 'Sandbox Numbers'. Add the phone numbers you will use as recipients during testing and follow the steps to verify them. This verification is mandatory for sending MMS messages with a trial account.

What is the best way to secure Plivo API credentials in production?

Use dedicated secret management services such as AWS Secrets Manager, Google Secret Manager, Azure Key Vault, or tools like HashiCorp Vault. Avoid storing credentials in `.env` files for production deployments, as these present security risks.

How to test the Plivo MMS service without sending actual messages?

Implement unit tests for the `plivoService.js` file. Mock the Plivo client object using Jest's mocking capabilities, which allows testing your service's logic and interaction with the Plivo SDK without making real API calls or incurring costs.

What is the role of the central error handling middleware?

Central error handling middleware catches errors passed using `next(error)` and provides a consistent way to handle unexpected issues. It logs errors, sets an appropriate HTTP status code (like 500 Internal Server Error), and sends a standardized error response to the client, preventing information leakage and simplifying debugging.

What are some security best practices for production Node.js APIs?

Implement rate limiting to prevent abuse, enforce HTTPS using a reverse proxy with TLS/SSL certificates, add strong input validation, and secure sensitive configuration like API keys with dedicated secret management services. These measures are crucial for a production-ready application.

How can I prevent exceeding my Plivo account balance due to excessive API calls?

Implement rate limiting using a library like `express-rate-limit`. This middleware limits the number of requests from a particular IP address within a timeframe, preventing accidental or malicious overuse of the Plivo API which can lead to unexpected charges.

How to handle different environments like development and production?

Use environment variables and tools like dotenv for local development. For production, use dedicated secret management solutions for sensitive configuration and consider more robust error handling and logging practices. Always avoid exposing sensitive data directly in your code.