How to Send MMS with Plivo, Node.js, and Express: Complete API Guide
Learn how to send MMS messages using Plivo Node.js SDK and Express. Step-by-step tutorial with code examples for building a production-ready multimedia messaging API with images, videos, and audio.
Learn how to send MMS messages with Plivo using Node.js and Express in this comprehensive guide. Multimedia Messaging Service (MMS) lets you enrich user communication by sending images, videos, and audio files alongside text messages.
This tutorial shows you how to build a production-ready MMS API using the Plivo Node.js SDK and Express framework. You'll start with a basic script and progressively build toward a robust API endpoint capable of handling MMS requests – covering setup, authentication, error handling, security, and deployment best practices.
Project Overview and Goals
What You're Building:
Create a Node.js application that programmatically sends MMS messages via Plivo's API. This involves:
A standalone script for sending a single MMS message
An Express.js API endpoint (POST /send-mms) that accepts recipient details, text, and a media URL to send an MMS
Problem Solved:
Integrate rich media messaging capabilities into your applications – move beyond simple text (SMS) to create engaging user experiences for notifications, alerts, marketing, or customer support.
Technologies Used:
Node.js – JavaScript runtime environment for building scalable network applications
Express.js – Minimal and flexible Node.js web application framework for creating API endpoints
Plivo – Cloud communications platform providing APIs for voice, SMS, and MMS. You'll use their Node.js SDK (version 4.75.1) as of January 2025.
dotenv – Module to load environment variables from a .env file, keeping sensitive credentials out of source code
System Architecture:
The basic flow for sending an MMS via your API endpoint looks like this:
Text
[Client Application (e.g., Frontend, Mobile App, Backend Service)]
|
| Makes HTTP POST request to /send-mms
| (with destination number, text, media URL)
V
[Node.js/Express API Server] --(Uses Plivo SDK)--> [Plivo API Platform]
| |
| 1. Receives request | 2. Processes request, validates
| 2. Validates input | 3. Sends MMS via carrier networks
| 3. Uses Plivo SDK to call Plivo's API |
| 4. Returns response (success/failure) |
V V
[Client Application] [Recipient's Mobile Device]
| |
| Receives API response | Receives MMS message
Prerequisites:
Node.js and npm (or yarn) – Install on your system. Verify with node -v and npm -v. Compatible with Node.js versions 5.5 and higher per the Plivo SDK documentation. Learn more about E.164 phone number formatting for international compliance.
Plivo Account – Sign up for a free Plivo trial account if you don't have one.
Trial Account Limitations – If using a Plivo trial account, you can only send MMS to phone numbers verified in your Plivo console under Phone Numbers > Sandbox Numbers. Trial accounts require a minimum $25 recharge to upgrade and remove sandbox restrictions. Alphanumeric sender IDs are not supported on trial accounts.
Expected Outcome:
You'll have a functional Node.js application capable of sending MMS messages programmatically – both via a direct script and through a secure, testable API endpoint. You'll understand the key configuration steps, error handling, and best practices for using Plivo's MMS API.
Setting Up Your Node.js MMS Project
Initialize your Node.js project and install the necessary dependencies.
Create Project Directory:
Open your terminal and create a new directory for your project:
Bash
mkdir plivo-mms-sender
cd plivo-mms-sender
Initialize Node.js Project:
Create a package.json file to manage your project's dependencies and scripts:
Bash
npm init -y
Install Dependencies:
Install the Plivo Node.js SDK, Express framework, and dotenv for managing environment variables:
Bash
npm install plivo express dotenv
plivo – Official Plivo library for interacting with their API
express – Web server framework for building your API endpoint
dotenv – Loads environment variables from a .env file
Create Project Structure:
Set up the basic file structure:
Text
plivo-mms-sender/
├── node_modules/
├── .env # Stores environment variables (API keys, phone number) – DO NOT COMMIT
├── .gitignore # Specifies intentionally untracked files that Git should ignore
├── send-mms.js # Standalone script for sending MMS
├── app.js # Express application with the API endpoint
├── package.json
└── package-lock.json
Configure Environment Variables (.env):
Create a file named .env in your project root. This file holds your sensitive Plivo credentials and phone number. Never commit this file to version control.
Replace YOUR_PLIVO_AUTH_ID and YOUR_PLIVO_AUTH_TOKEN with credentials from your Plivo Console dashboard
Replace YOUR_MMS_ENABLED_PLIVO_NUMBER with your E.164 formatted Plivo phone number (e.g., +14155551212)
Configure .gitignore:
Create a file named .gitignore in your project root. Add node_modules and .env to prevent committing them to your Git repository:
Text
# .gitignore
node_modules
.env
Why .gitignore? Security and efficiency – node_modules can be large and easily reinstalled using npm install. .env contains sensitive API keys that should never be exposed in your codebase history.
Your project environment is ready for implementation.
Implementing MMS Sending with Plivo SDK
Create a simple script to send one MMS message directly using the Plivo SDK. This helps verify your credentials and Plivo number setup.
Create the Sending Script (send-mms.js):
Create the file send-mms.js and add the following code:
JavaScript
// send-mms.jsrequire('dotenv').config(); // Load environment variables from .env fileconst plivo = require('plivo');
// Ensure environment variables are loadedconst authId = process.env.PLIVO_AUTH_ID;
const authToken = process.env.PLIVO_AUTH_TOKEN;
const senderNumber = process.env.PLIVO_SENDER_NUMBER;
if (!authId || !authToken || !senderNumber) {
console.error('Error: Plivo credentials or sender number not found in .env file.');
process.exit(1); // Exit if credentials are missing
}
// --- Configuration ---// IMPORTANT: Replace with the recipient's phone number in E.164 format.// If using a Plivo trial account, this MUST be a number verified// in your Plivo Console under Phone Numbers > Sandbox Numbers.const recipientNumber = '+14155551234'; // Example - CHANGE THIS to a valid recipient number// Replace with the URL of the media file you want to send (must be publicly accessible)const mediaUrl = 'https://media.giphy.com/media/26gscSULUcfKU7dHq/source.gif'; // Example GIF - CHANGE THIS if needed// The text content of your messageconst messageText = 'Hello from Node.js with Plivo MMS!';
// --- End Configuration ---// Create a Plivo client instanceconst client = new plivo.Client(authId, authToken);
console.log(`Attempting to send MMS from ${senderNumber} to ${recipientNumber}...`);
// Use the client to send the message
client.messages.create({
src: senderNumber, // Your Plivo phone number (must be MMS enabled)dst: recipientNumber, // The destination phone numbertext: messageText, // The text part of the messagetype: 'mms', // Specify message type as MMSmedia_urls: [mediaUrl] // Array of publicly accessible URLs for media files// You can also use media_ids if you've uploaded media to Plivo:// media_ids: ['YOUR_UPLOADED_MEDIA_ID']
})
.then(function(message_created) {
console.log('MMS Sent Successfully!');
console.log('Message UUID:', message_created.messageUuid[0]); // Plivo returns UUIDs in an arrayconsole.log('API Response:', message_created);
})
.catch(function(error) {
console.error('Error sending MMS:', error);
});
Explanation:
require('dotenv').config(); loads the variables from your .env file into process.env
Credential Check: Basic validation ensures the script doesn't run without necessary credentials
Configuration: Clearly defined variables for recipient number, media URL, and text. Change recipientNumber to a valid target number before running. If using a trial account, ensure it's a verified sandbox number.
new plivo.Client() initializes the connection to the Plivo API using your Auth ID and Token
client.messages.create() is the core function call:
src: Your MMS-enabled Plivo number (from .env)
dst: The recipient's number
text: The message body
type: 'mms': Crucial for sending MMS. Without this, it defaults to SMS.
media_urls: An array containing one or more publicly accessible URLs pointing to the media files (images, GIFs, videos, audio). Plivo fetches the media from these URLs. Ensure the URLs are valid and accessible.
.then() handles the successful API response from Plivo, typically containing message UUIDs and status information
.catch() catches and logs any errors during the API call (e.g., invalid credentials, invalid number format, inaccessible media URL)
Run the Script:
Execute the script from your terminal:
Bash
node send-mms.js
Verification:
Check your terminal for the "MMS Sent Successfully!" message and the response details from Plivo
Check the recipient's phone – they should receive the MMS message shortly
Check the Plivo Console under Logs > Messaging Logs for the status of the sent message
If you encounter errors, check the console output and refer to the "Troubleshooting" section below. Common issues include incorrect credentials, non-verified sandbox numbers (for trial accounts), invalid recipient number format (use E.164: +1…), or inaccessible media_urls.
Building an Express API Endpoint for MMS
While the script is useful for testing, a more practical approach is to create an API endpoint that other services or frontends can call to send MMS messages.
Create the Express App (app.js):
Create the file app.js and add the following code:
JavaScript
// app.jsrequire('dotenv').config();
const express = require('express');
const plivo = require('plivo');
const app = express();
const port = process.env.PORT || 3000; // Use environment variable for port or default to 3000// Middleware to parse JSON bodies
app.use(express.json());
// Middleware to parse URL-encoded bodies (often used by HTML forms)
app.use(express.urlencoded({ extended: true }));
// --- Plivo Configuration ---const authId = process.env.PLIVO_AUTH_ID;
const authToken = process.env.PLIVO_AUTH_TOKEN;
const senderNumber = process.env.PLIVO_SENDER_NUMBER;
if (!authId || !authToken || !senderNumber) {
console.error('CRITICAL ERROR: Plivo credentials or sender number not found in .env file. API cannot start.');
process.exit(1); // Prevent server start if config is missing
}
const plivoClient = new plivo.Client(authId, authToken);
// --- End Plivo Configuration ---// --- API Endpoint: POST /send-mms ---
app.post('/send-mms', async (req, res) => {
const { recipientNumber, messageText, mediaUrl } = req.body;
// --- Basic Input Validation ---if (!recipientNumber || !messageText || !mediaUrl) {
return res.status(400).json({
error: 'Missing required fields: recipientNumber, messageText, mediaUrl',
});
}
// Basic E.164 format check (improves reliability but not foolproof)// Consider libraries like 'libphonenumber-js' for robust validation in production.if (!/^\+[1-9]\d{1,14}$/.test(recipientNumber)) {
return res.status(400).json({
error: 'Invalid recipientNumber format. Use E.164 format (e.g., +14155551212).',
});
}
// Basic URL validationtry {
newURL(mediaUrl);
} catch (_) {
return res.status(400).json({ error: 'Invalid mediaUrl format.' });
}
// --- End Basic Input Validation ---console.log(`API Request: Send MMS from ${senderNumber} to ${recipientNumber}`);
try {
// This implementation expects a single mediaUrl from the request body// and passes it as a single-element array to Plivo.const response = await plivoClient.messages.create({
src: senderNumber,
dst: recipientNumber,
text: messageText,
type: 'mms',
media_urls: [mediaUrl], // Expecting a single URL for simplicity here
});
console.log('Plivo API Success:', response);
res.status(200).json({
message: 'MMS sent successfully.',
messageUuid: response.messageUuid[0], // Extract the UUIDplivoResponse: response,
});
} catch (error) {
console.error('Plivo API Error:', error);
// Provide more specific feedback if possiblelet statusCode = 500;
let errorMessage = 'Failed to send MMS due to an internal server error.';
if (error.statusCode) {
// Use Plivo's status code if available (e.g., 400 Bad Request, 401 Unauthorized)
statusCode = error.statusCode;
errorMessage = error.message || 'Plivo API request failed.';
}
res.status(statusCode).json({
error: errorMessage,
details: error.message, // Include Plivo's error message if helpful
});
}
});
// --- Health Check Endpoint ---
app.get('/health', (req, res) => {
res.status(200).json({ status: 'UP', timestamp: newDate().toISOString() });
});
// --- Start Server ---
app.listen(port, () => {
console.log(`MMS Sender API listening on port ${port}`);
console.log(`Plivo Sender Number: ${senderNumber}`);
if (process.env.NODE_ENV !== 'production') {
console.log(`\nTest endpoint locally using curl (replace placeholders!):\n`);
console.log(`curl -X POST http://localhost:${port}/send-mms -H "Content-Type: application/json" -d '{"recipientNumber": "+1...", "messageText": "API Test", "mediaUrl": "https://..."}'\n`);
}
});
Explanation:
Dependencies: Imports express and plivo, loads .env
Middleware:express.json() and express.urlencoded() are essential for parsing incoming request bodies (JSON and form data)
Plivo Client: The client is initialized once when the app starts
/send-mms Endpoint (POST):
Defines a route that listens for POST requests at the /send-mms path
Extracts recipientNumber, messageText, and mediaUrl from the request body (req.body)
Input Validation: Performs basic checks: presence of required fields, basic E.164 format for the number, and basic URL validity. The E.164 regex (^\+[1-9]\d{1,14}$) is a basic check; while helpful, it might allow technically invalid numbers (e.g., incorrect length for a country). For production, use a dedicated library like libphonenumber-js for robust phone number validation.
mediaUrl Handling: This implementation expects a single mediaUrl field in the request body. It then wraps this URL in an array ([mediaUrl]) as required by the Plivo API's media_urls parameter. Modify the API to accept an array of URLs directly if needed.
Async/Await: Uses async/await for cleaner handling of the asynchronous Plivo API call
Plivo Call: Calls plivoClient.messages.create within a try…catch block
Success Response (200): If the Plivo API call succeeds (doesn't throw an error), it sends back a JSON response confirming initiation and includes the message UUID
Error Response (400/500): If validation fails (400) or the Plivo API call throws an error (usually 500, but uses Plivo's status code if available), it logs the error and sends back a JSON error response
/health Endpoint (GET): A simple endpoint used for monitoring to check if the service is running
app.listen(): Starts the Express server on the specified port and provides a sample curl command for local testing
Run the API Server:
Execute the app from your terminal:
Bash
node app.js
You should see the message "MMS Sender API listening on port 3000" and the sample curl command.
Test the API Endpoint:
Use curl (or tools like Postman or Insomnia) to send a POST request to your running server. Open a new terminal window (leave the server running in the first one).
IMPORTANT: Replace the placeholder values below (+14155551234 and the mediaUrl) with a valid recipient number (remember the sandbox verification requirement if using a Plivo trial account) and a working, publicly accessible media URL.
Bash
curl -X POST http://localhost:3000/send-mms \
-H "Content-Type: application/json" \
-d '{
"recipientNumber": "+14155551234",
"messageText": "Testing MMS from my API!",
"mediaUrl": "https://media.giphy.com/media/3o7TKS6AWgP3FpO2LS/giphy.gif"
}'
Verification:
API Response: You should receive a JSON response in your terminal like:
JSON
{"message":"MMS sent successfully.","messageUuid":"some-uuid-string-from-plivo","plivoResponse":{ ... detailed Plivo response ...}}
Server Logs: Check the terminal where node app.js is running for log messages
Recipient Phone: The MMS should arrive on the recipient's device
Plivo Console: Check the Messaging Logs
Error Testing: Try sending requests with missing fields, invalid numbers, or invalid URLs to see the error responses (e.g., 400 Bad Request)
Plivo API Authentication and Configuration
You've already used the Plivo credentials. Here's where to find them and how they work.
Auth ID and Auth Token:
Purpose: These act like your username and password for the Plivo API. They authenticate your requests.
How to Obtain: Log in to your Plivo Console. They are prominently displayed on the main Dashboard/Overview page.
Secure Storage: Store these in environment variables (.env locally, secure configuration management in production) and never hardcode them directly into your source code. Use dotenv locally and your deployment platform's environment variable system in production.
MMS-Enabled Plivo Number (src):
Purpose: This is the phone number from which your MMS messages will appear to be sent. It must be capable of sending MMS messages in the US and Canada.
How to Obtain/Verify:
Go to Phone Numbers > Your Numbers in the Plivo Console
Check the "Capabilities" column for your existing numbers. Look for "MMS"
If you need a new number, go to Phone Numbers > Buy Numbers. Filter by "MMS" capability and select a US or Canadian number (Long Code or Toll-Free).
URLs should be stable and remain accessible during message processing
Alternative (media_ids): Upload media directly to Plivo via their console (Messaging > MMS Media Upload) or API. Uploaded media gets a media_id. You can then use media_ids: ['YOUR_MEDIA_ID'] instead of media_urls. This can be useful for frequently used media or if you don't have a public hosting location. The same size limits apply (2 MB per media, 5 MB total).
Messaging > Logs: Check the status, content, and any errors for sent/received messages. Indispensable for debugging.
Phone Numbers > Your Numbers / Buy Numbers: Manage your Plivo numbers. Verify MMS capability is enabled.
Phone Numbers > Sandbox Numbers: Verify recipient numbers for testing during a trial (required for trial accounts)
Messaging > MMS Media Upload: Manage pre-uploaded media files
MMS Error Handling and Retry Logic
Robust applications need solid error handling and logging.
Error Handling Strategy:
API Level: Use try…catch blocks around asynchronous operations like Plivo API calls
Validation: Catch invalid inputs early (as shown in app.js) and return clear 4xx error responses
Plivo Errors: The Plivo SDK throws errors that often contain useful information, including Plivo-specific error messages and sometimes HTTP status codes. Catch these errors and log them. Return appropriate HTTP status codes (5xx for server/Plivo issues, 4xx for client errors) and informative JSON error messages to the API caller.
Network Issues: Network problems between your server and Plivo can occur. Ensure your catch blocks handle generic network errors too.
Logging:
Basic Logging:console.log and console.error are sufficient for simple cases and development
Production Logging: For production, use a dedicated logging library like winston or pino. These offer:
Log Levels: (debug, info, warn, error) – Filter logs based on severity
Structured Logging: Output logs in JSON format for easier parsing by log management systems (like Datadog, Splunk, ELK stack)
Transports: Send logs to files, databases, or external logging services
What to Log:
Incoming API requests (excluding sensitive data)
Key parameters used in Plivo calls (sender, recipient, media URL type)
Successful Plivo API responses (especially message UUIDs)
Detailed error information when Plivo calls fail (stack traces, error messages, Plivo status codes)
Validation failures
Example using console (adapt for libraries):
JavaScript
// Inside the /send-mms routeconsole.info(`[INFO] Received request to send MMS to ${recipientNumber}`);
// ... validation ...try {
// ... plivo call ...const response = await plivoClient.messages.create({ /* ... params ... */ });
console.info(`[SUCCESS] Plivo API call successful for ${recipientNumber}. UUID: ${response.messageUuid[0]}`);
res.status(200).json(/* ... */);
} catch (error) {
console.error(`[ERROR] Plivo API call failed for ${recipientNumber}. Status: ${error.statusCode || 'N/A'}, Message: ${error.message}`, error); // Log full error object too// Determine statusCode based on errorconst statusCode = error.statusCode && error.statusCode >= 400 && error.statusCode < 500 ? error.statusCode : 500;
res.status(statusCode).json(/* ... */);
}
Retry Mechanisms:
Why: Transient network issues or temporary Plivo outages might cause API calls to fail. Retrying can improve reliability.
Strategy: Implement exponential backoff. If a request fails, wait a short period (e.g., 1 second), retry. If it fails again, wait longer (e.g., 2 seconds), retry. Increase the delay exponentially up to a maximum number of retries.
Implementation: Libraries like async-retry or p-retry can simplify this. Be cautious about retrying errors that are clearly not transient (e.g., 401 Unauthorized, 400 Bad Request due to invalid number format). Only retry on likely temporary issues (e.g., 5xx errors, network timeouts).
Conceptual example (without a library):
JavaScript
asyncfunctionsendMmsWithRetry(params, retries = 3, delay = 1000) {
try {
returnawait plivoClient.messages.create(params);
} catch (error) {
// Only retry on specific error types (e.g., network or 5xx)// Example condition: retry on network errors or Plivo server errors (5xx)const shouldRetry = !error.statusCode || error.statusCode >= 500 || (error.code && ['ECONNRESET', 'ETIMEDOUT', 'ENETUNREACH'].includes(error.code));
if (retries > 0 && shouldRetry) {
console.warn(`[RETRY] Plivo call failed, retrying in ${delay}ms... (${retries} retries left)`);
awaitnewPromise(resolve =>setTimeout(resolve, delay));
// Exponential backoff: double the delay for the next retryreturnsendMmsWithRetry(params, retries - 1, delay * 2);
} else {
console.error(`[FAIL] Plivo call failed after retries or due to non-retryable error.`);
throw error; // Re-throw the error if retries exhausted or not retryable
}
}
}
// In your API route:// try {// const response = await sendMmsWithRetry({ /* Plivo params */ });// // Handle success// } catch (error) {// // Handle final failure// }
Tracking MMS Status with a Database
While your simple API doesn't require a database, a production system often benefits from one for:
Tracking Message Status: Store the messageUuid returned by Plivo and later update its status (sent, delivered, failed) using Plivo's message status callbacks (webhooks)
Auditing and History: Keep a log of all MMS messages sent, including recipient, content (or reference), timestamp, and status
User Data: Associate MMS messages with specific users in your application
Rate Limiting/Analytics: Store usage data
If adding a database (e.g., PostgreSQL with Prisma):
Schema Design (Conceptual schema.prisma):
Prisma
// schema.prisma
datasource db {
provider = "postgresql" // or mysql, sqlite, sqlserver, mongodb
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model MmsMessage {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
plivoMessageUuid String? @unique // Store the UUID returned by Plivo
senderNumber String // Your Plivo number used
recipientNumber String // The destination number
messageText String?
mediaUrl String?
mediaId String? // If using Plivo media upload
status String @default("initiated") // e.g., initiated, queued, sent, delivered, failed, undelivered
plivoStatus String? // Store the status from Plivo callback
errorCode String? // Store Plivo error code on failure
// Optional: Link to a User model if applicable
// userId String?
// user User? @relation(fields: [userId], references: [id])
@@index([recipientNumber])
@@index([plivoMessageUuid])
@@index([status])
}
// model User { ... } // If needed
Data Access: Use an ORM like Prisma or Sequelize to interact with the database, providing type safety and simplifying queries. Your API endpoint would first save a record with status: 'initiated' and then update it based on the Plivo API response or subsequent status callbacks.
Migrations: Use the ORM's migration tools (e.g., prisma migrate dev, sequelize db:migrate) to manage schema changes safely.
This section is conceptual. Implementing a full database layer is beyond the scope of this specific MMS sending guide but is a common next step for production systems.
Security Best Practices for MMS APIs
Secure your API endpoint.
Input Validation and Sanitization:
Validation: You added basic checks. Use robust libraries (joi, express-validator, libphonenumber-js) to enforce strict rules on recipientNumber (valid E.164 format), messageText (length limits, character types), and mediaUrl (valid URL format, perhaps restricting domains).
Sanitization: While less critical for API parameters passed directly to Plivo, if you were embedding user input into messageText that might be displayed elsewhere, use libraries like dompurify (for HTML) or ensure proper escaping to prevent cross-site scripting (XSS). Plivo handles the SMS/MMS encoding.
Authentication/Authorization:
Your current API is open. In production, protect it.
API Keys: Issue unique API keys to trusted clients. Clients include the key in a header (e.g., Authorization: Bearer YOUR_API_KEY or X-API-Key: YOUR_API_KEY). Your Express app verifies the key against a stored list/database before processing the request.
JWT/OAuth: For user-centric applications, use standard authentication flows like JSON Web Tokens or OAuth.
Rate Limiting:
Prevent abuse and control costs by limiting how many requests a client can make in a given time window.
Use middleware like express-rate-limit. Configure it based on IP address or API key.
// app.jsconst rateLimit = require('express-rate-limit');
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutesmax: 100, // Limit each IP to 100 requests per windowMsmessage: 'Too many requests from this IP, please try again after 15 minutes',
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headerslegacyHeaders: false, // Disable the `X-RateLimit-*` headers
});
// Apply the rate limiting middleware to API routes// Apply it before your main route handler for /send-mms
app.use('/send-mms', apiLimiter);
// ... rest of your app.js, including the app.post('/send-mms', ...) route ...
HTTPS: Always use HTTPS in production to encrypt data in transit. Deployment platforms like Heroku or using a reverse proxy like Nginx typically handle TLS/SSL termination.
Environment Variables: Keep sensitive data (API keys, Plivo Auth tokens) out of your code using environment variables. Ensure your .env file is in .gitignore. For production, do not deploy the .env file. Instead, use your hosting platform's built-in environment variable configuration (e.g., Heroku Config Vars, AWS Parameter Store, Azure App Configuration) or a dedicated secrets management service (e.g., AWS Secrets Manager, HashiCorp Vault). For related SMS implementations, check out our guides on basic SMS sending with Plivo and two-way messaging.
Frequently Asked Questions
What is the maximum file size for MMS attachments with Plivo?
Can I send MMS messages internationally with Plivo?
No. Plivo MMS is only available for US and Canadian phone numbers. You need a US or Canadian Plivo number to send MMS, and recipients must also be in the US or Canada. For international messaging, consider using SMS or other messaging channels like WhatsApp.
How do I send MMS from a Plivo trial account?
Trial accounts can only send MMS to verified sandbox numbers. Add recipient numbers in the Plivo Console under Phone Numbers > Sandbox Numbers. Trial accounts cannot use alphanumeric sender IDs and require a minimum $25 recharge to upgrade and remove restrictions.
Implement webhook endpoints to receive Plivo delivery status callbacks. Configure the callback URL in your Plivo Console under message settings or use the url parameter when sending messages via the Send Message API. Your Express app should handle POST requests containing status updates (queued, sent, delivered, failed) and store them in your database using the message UUID.
Can I send the same media file to multiple recipients?
Yes. Upload media to Plivo using Messaging > MMS Media Upload in the console or via the Upload Media API. You'll receive a media_id that you can reuse across multiple messages without re-uploading. This approach is more efficient than using media_urls for frequently sent media.
What's the difference between media_urls and media_ids?
media_urls are publicly accessible URLs that Plivo fetches when sending the MMS. media_ids refer to media files you've pre-uploaded to Plivo's servers. Use media_ids for frequently used media or when you don't have public hosting. Both methods support the same file size limits (2 MB per file, 5 MB total).
How do I test MMS sending locally in development?
Run your Express server locally with node app.js, then use curl or Postman to send POST requests to http://localhost:3000/send-mms. Ensure your Plivo credentials are in .env and recipient numbers are verified in sandbox (for trial accounts). Check the Plivo Console logs for detailed delivery information.
Use the Plivo Node.js SDK and the client.messages.create() method with type: 'mms' and media_urls set to an array of media URLs. This allows you to send multimedia messages like images and GIFs along with text from your Node application.
The Plivo Node.js SDK is a library that simplifies interaction with the Plivo communications platform, allowing developers to send SMS, MMS, and make voice calls programmatically within Node.js applications. The SDK handles authentication, API calls, and responses.
Plivo requires MMS-enabled numbers because standard SMS numbers often lack the capability to handle multimedia content. MMS-enabled numbers are specifically provisioned to support sending and receiving images, videos, and audio files.
Use media_ids when you've pre-uploaded media to Plivo's servers, either through the console or API. This is suitable for frequently used media or if you don't have readily available public URLs. Use media_urls if you have files hosted online and accessible via public links.
No, with a Plivo trial account, you can only send MMS to phone numbers verified as sandbox numbers in your Plivo console. This restriction is in place for testing and prevents misuse during the trial period.
Store your Plivo Auth ID, Auth Token, and sender number in a .env file in your project's root directory. Then use the dotenv library in your Node.js code to load these variables into process.env.
A client application makes a request to your Node.js/Express server, which uses the Plivo SDK to interact with the Plivo API. Plivo then processes the request, validates it, and sends the MMS via carrier networks to the recipient's mobile device.
Define a POST route (e.g., /send-mms) in your Express app. This endpoint receives the recipient number, message text, and media URL from the request body, validates them, uses the Plivo SDK to send the MMS, and returns a response to the client.
The .gitignore file prevents sensitive data (like your .env file containing Plivo credentials) and large directories (like node_modules) from being committed to version control, enhancing security and repository efficiency.
Input validation prevents issues such as invalid phone numbers, incorrect URLs, or oversized messages from causing errors or security vulnerabilities in your MMS sending application. Basic validation is shown in the example, but production systems should use libraries for stricter validation.
Use try...catch blocks to handle errors during the Plivo API call. Log detailed error information, including Plivo's error messages and status codes, for debugging. Return appropriate HTTP status codes and informative error messages to the client.
Retry mechanisms, using exponential backoff, handle transient network issues or temporary service disruptions by resending failed MMS requests after increasing delays. This improves the reliability of your application.
Use middleware like express-rate-limit to restrict the number of MMS requests a client can make within a specific timeframe, based on IP or API key. This prevents abuse and manages costs.
Secure your API with authentication (API keys, JWT), input validation, and rate limiting. Always use HTTPS in production. Store sensitive data (like API keys) in environment variables and never hardcode them.
Learn how to send MMS messages with Plivo using Node.js and Express in this comprehensive guide. Multimedia Messaging Service (MMS) lets you enrich user communication by sending images, videos, and audio files alongside text messages.
This tutorial shows you how to build a production-ready MMS API using the Plivo Node.js SDK and Express framework. You'll start with a basic script and progressively build toward a robust API endpoint capable of handling MMS requests – covering setup, authentication, error handling, security, and deployment best practices.
Project Overview and Goals
What You're Building:
Create a Node.js application that programmatically sends MMS messages via Plivo's API. This involves:
POST /send-mms) that accepts recipient details, text, and a media URL to send an MMSProblem Solved:
Integrate rich media messaging capabilities into your applications – move beyond simple text (SMS) to create engaging user experiences for notifications, alerts, marketing, or customer support.
Technologies Used:
dotenv– Module to load environment variables from a.envfile, keeping sensitive credentials out of source codeSystem Architecture:
The basic flow for sending an MMS via your API endpoint looks like this:
Prerequisites:
node -vandnpm -v. Compatible with Node.js versions 5.5 and higher per the Plivo SDK documentation. Learn more about E.164 phone number formatting for international compliance.Expected Outcome:
You'll have a functional Node.js application capable of sending MMS messages programmatically – both via a direct script and through a secure, testable API endpoint. You'll understand the key configuration steps, error handling, and best practices for using Plivo's MMS API.
Setting Up Your Node.js MMS Project
Initialize your Node.js project and install the necessary dependencies.
Create Project Directory: Open your terminal and create a new directory for your project:
mkdir plivo-mms-sender cd plivo-mms-senderInitialize Node.js Project: Create a
package.jsonfile to manage your project's dependencies and scripts:Install Dependencies: Install the Plivo Node.js SDK, Express framework, and
dotenvfor managing environment variables:plivo– Official Plivo library for interacting with their APIexpress– Web server framework for building your API endpointdotenv– Loads environment variables from a.envfileCreate Project Structure: Set up the basic file structure:
Configure Environment Variables (
.env): Create a file named.envin your project root. This file holds your sensitive Plivo credentials and phone number. Never commit this file to version control.YOUR_PLIVO_AUTH_IDandYOUR_PLIVO_AUTH_TOKENwith credentials from your Plivo Console dashboardYOUR_MMS_ENABLED_PLIVO_NUMBERwith your E.164 formatted Plivo phone number (e.g.,+14155551212)Configure
.gitignore: Create a file named.gitignorein your project root. Addnode_modulesand.envto prevent committing them to your Git repository:Why
.gitignore? Security and efficiency –node_modulescan be large and easily reinstalled usingnpm install..envcontains sensitive API keys that should never be exposed in your codebase history.Your project environment is ready for implementation.
Implementing MMS Sending with Plivo SDK
Create a simple script to send one MMS message directly using the Plivo SDK. This helps verify your credentials and Plivo number setup.
Create the Sending Script (
send-mms.js): Create the filesend-mms.jsand add the following code:// send-mms.js require('dotenv').config(); // Load environment variables from .env file const plivo = require('plivo'); // Ensure environment variables are loaded const authId = process.env.PLIVO_AUTH_ID; const authToken = process.env.PLIVO_AUTH_TOKEN; const senderNumber = process.env.PLIVO_SENDER_NUMBER; if (!authId || !authToken || !senderNumber) { console.error('Error: Plivo credentials or sender number not found in .env file.'); process.exit(1); // Exit if credentials are missing } // --- Configuration --- // IMPORTANT: Replace with the recipient's phone number in E.164 format. // If using a Plivo trial account, this MUST be a number verified // in your Plivo Console under Phone Numbers > Sandbox Numbers. const recipientNumber = '+14155551234'; // Example - CHANGE THIS to a valid recipient number // Replace with the URL of the media file you want to send (must be publicly accessible) const mediaUrl = 'https://media.giphy.com/media/26gscSULUcfKU7dHq/source.gif'; // Example GIF - CHANGE THIS if needed // The text content of your message const messageText = 'Hello from Node.js with Plivo MMS!'; // --- End Configuration --- // Create a Plivo client instance const client = new plivo.Client(authId, authToken); console.log(`Attempting to send MMS from ${senderNumber} to ${recipientNumber}...`); // Use the client to send the message client.messages.create({ src: senderNumber, // Your Plivo phone number (must be MMS enabled) dst: recipientNumber, // The destination phone number text: messageText, // The text part of the message type: 'mms', // Specify message type as MMS media_urls: [mediaUrl] // Array of publicly accessible URLs for media files // You can also use media_ids if you've uploaded media to Plivo: // media_ids: ['YOUR_UPLOADED_MEDIA_ID'] }) .then(function(message_created) { console.log('MMS Sent Successfully!'); console.log('Message UUID:', message_created.messageUuid[0]); // Plivo returns UUIDs in an array console.log('API Response:', message_created); }) .catch(function(error) { console.error('Error sending MMS:', error); });Explanation:
require('dotenv').config();loads the variables from your.envfile intoprocess.envrecipientNumberto a valid target number before running. If using a trial account, ensure it's a verified sandbox number.new plivo.Client()initializes the connection to the Plivo API using your Auth ID and Tokenclient.messages.create()is the core function call:src: Your MMS-enabled Plivo number (from.env)dst: The recipient's numbertext: The message bodytype: 'mms': Crucial for sending MMS. Without this, it defaults to SMS.media_urls: An array containing one or more publicly accessible URLs pointing to the media files (images, GIFs, videos, audio). Plivo fetches the media from these URLs. Ensure the URLs are valid and accessible..then()handles the successful API response from Plivo, typically containing message UUIDs and status information.catch()catches and logs any errors during the API call (e.g., invalid credentials, invalid number format, inaccessible media URL)Run the Script: Execute the script from your terminal:
Verification:
+1…), or inaccessiblemedia_urls.Building an Express API Endpoint for MMS
While the script is useful for testing, a more practical approach is to create an API endpoint that other services or frontends can call to send MMS messages.
Create the Express App (
app.js): Create the fileapp.jsand add the following code:// app.js require('dotenv').config(); const express = require('express'); const plivo = require('plivo'); const app = express(); const port = process.env.PORT || 3000; // Use environment variable for port or default to 3000 // Middleware to parse JSON bodies app.use(express.json()); // Middleware to parse URL-encoded bodies (often used by HTML forms) app.use(express.urlencoded({ extended: true })); // --- Plivo Configuration --- const authId = process.env.PLIVO_AUTH_ID; const authToken = process.env.PLIVO_AUTH_TOKEN; const senderNumber = process.env.PLIVO_SENDER_NUMBER; if (!authId || !authToken || !senderNumber) { console.error('CRITICAL ERROR: Plivo credentials or sender number not found in .env file. API cannot start.'); process.exit(1); // Prevent server start if config is missing } const plivoClient = new plivo.Client(authId, authToken); // --- End Plivo Configuration --- // --- API Endpoint: POST /send-mms --- app.post('/send-mms', async (req, res) => { const { recipientNumber, messageText, mediaUrl } = req.body; // --- Basic Input Validation --- if (!recipientNumber || !messageText || !mediaUrl) { return res.status(400).json({ error: 'Missing required fields: recipientNumber, messageText, mediaUrl', }); } // Basic E.164 format check (improves reliability but not foolproof) // Consider libraries like 'libphonenumber-js' for robust validation in production. if (!/^\+[1-9]\d{1,14}$/.test(recipientNumber)) { return res.status(400).json({ error: 'Invalid recipientNumber format. Use E.164 format (e.g., +14155551212).', }); } // Basic URL validation try { new URL(mediaUrl); } catch (_) { return res.status(400).json({ error: 'Invalid mediaUrl format.' }); } // --- End Basic Input Validation --- console.log(`API Request: Send MMS from ${senderNumber} to ${recipientNumber}`); try { // This implementation expects a single mediaUrl from the request body // and passes it as a single-element array to Plivo. const response = await plivoClient.messages.create({ src: senderNumber, dst: recipientNumber, text: messageText, type: 'mms', media_urls: [mediaUrl], // Expecting a single URL for simplicity here }); console.log('Plivo API Success:', response); res.status(200).json({ message: 'MMS sent successfully.', messageUuid: response.messageUuid[0], // Extract the UUID plivoResponse: response, }); } catch (error) { console.error('Plivo API Error:', error); // Provide more specific feedback if possible let statusCode = 500; let errorMessage = 'Failed to send MMS due to an internal server error.'; if (error.statusCode) { // Use Plivo's status code if available (e.g., 400 Bad Request, 401 Unauthorized) statusCode = error.statusCode; errorMessage = error.message || 'Plivo API request failed.'; } res.status(statusCode).json({ error: errorMessage, details: error.message, // Include Plivo's error message if helpful }); } }); // --- Health Check Endpoint --- app.get('/health', (req, res) => { res.status(200).json({ status: 'UP', timestamp: new Date().toISOString() }); }); // --- Start Server --- app.listen(port, () => { console.log(`MMS Sender API listening on port ${port}`); console.log(`Plivo Sender Number: ${senderNumber}`); if (process.env.NODE_ENV !== 'production') { console.log(`\nTest endpoint locally using curl (replace placeholders!):\n`); console.log(`curl -X POST http://localhost:${port}/send-mms -H "Content-Type: application/json" -d '{"recipientNumber": "+1...", "messageText": "API Test", "mediaUrl": "https://..."}'\n`); } });Explanation:
expressandplivo, loads.envexpress.json()andexpress.urlencoded()are essential for parsing incoming request bodies (JSON and form data)/send-mmsEndpoint (POST):POSTrequests at the/send-mmspathrecipientNumber,messageText, andmediaUrlfrom the request body (req.body)^\+[1-9]\d{1,14}$) is a basic check; while helpful, it might allow technically invalid numbers (e.g., incorrect length for a country). For production, use a dedicated library likelibphonenumber-jsfor robust phone number validation.mediaUrlHandling: This implementation expects a singlemediaUrlfield in the request body. It then wraps this URL in an array ([mediaUrl]) as required by the Plivo API'smedia_urlsparameter. Modify the API to accept an array of URLs directly if needed.async/awaitfor cleaner handling of the asynchronous Plivo API callplivoClient.messages.createwithin atry…catchblock/healthEndpoint (GET): A simple endpoint used for monitoring to check if the service is runningapp.listen(): Starts the Express server on the specified port and provides a samplecurlcommand for local testingRun the API Server: Execute the app from your terminal:
You should see the message "MMS Sender API listening on port 3000" and the sample
curlcommand.Test the API Endpoint: Use
curl(or tools like Postman or Insomnia) to send a POST request to your running server. Open a new terminal window (leave the server running in the first one).IMPORTANT: Replace the placeholder values below (
+14155551234and themediaUrl) with a valid recipient number (remember the sandbox verification requirement if using a Plivo trial account) and a working, publicly accessible media URL.curl -X POST http://localhost:3000/send-mms \ -H "Content-Type: application/json" \ -d '{ "recipientNumber": "+14155551234", "messageText": "Testing MMS from my API!", "mediaUrl": "https://media.giphy.com/media/3o7TKS6AWgP3FpO2LS/giphy.gif" }'Verification:
{ "message": "MMS sent successfully.", "messageUuid": "some-uuid-string-from-plivo", "plivoResponse": { ... detailed Plivo response ...} }node app.jsis running for log messagesPlivo API Authentication and Configuration
You've already used the Plivo credentials. Here's where to find them and how they work.
Auth ID and Auth Token:
.envlocally, secure configuration management in production) and never hardcode them directly into your source code. Usedotenvlocally and your deployment platform's environment variable system in production.MMS-Enabled Plivo Number (
src):+14155551212).envfile asPLIVO_SENDER_NUMBERMedia URLs (
media_urls):content-typeandcontent-lengthresponse headers, otherwise Plivo will reject the requestmedia_urlsandmedia_ids)media_ids): Upload media directly to Plivo via their console (Messaging > MMS Media Upload) or API. Uploaded media gets amedia_id. You can then usemedia_ids: ['YOUR_MEDIA_ID']instead ofmedia_urls. This can be useful for frequently used media or if you don't have a public hosting location. The same size limits apply (2 MB per media, 5 MB total).Plivo Console Dashboard: Familiarize yourself with:
MMS Error Handling and Retry Logic
Robust applications need solid error handling and logging.
Error Handling Strategy:
try…catchblocks around asynchronous operations like Plivo API callsapp.js) and return clear4xxerror responses5xxfor server/Plivo issues,4xxfor client errors) and informative JSON error messages to the API caller.catchblocks handle generic network errors too.Logging:
console.logandconsole.errorare sufficient for simple cases and developmentwinstonorpino. These offer:Example using
console(adapt for libraries):// Inside the /send-mms route console.info(`[INFO] Received request to send MMS to ${recipientNumber}`); // ... validation ... try { // ... plivo call ... const response = await plivoClient.messages.create({ /* ... params ... */ }); console.info(`[SUCCESS] Plivo API call successful for ${recipientNumber}. UUID: ${response.messageUuid[0]}`); res.status(200).json(/* ... */); } catch (error) { console.error(`[ERROR] Plivo API call failed for ${recipientNumber}. Status: ${error.statusCode || 'N/A'}, Message: ${error.message}`, error); // Log full error object too // Determine statusCode based on error const statusCode = error.statusCode && error.statusCode >= 400 && error.statusCode < 500 ? error.statusCode : 500; res.status(statusCode).json(/* ... */); }Retry Mechanisms:
async-retryorp-retrycan simplify this. Be cautious about retrying errors that are clearly not transient (e.g., 401 Unauthorized, 400 Bad Request due to invalid number format). Only retry on likely temporary issues (e.g., 5xx errors, network timeouts).Conceptual example (without a library):
async function sendMmsWithRetry(params, retries = 3, delay = 1000) { try { return await plivoClient.messages.create(params); } catch (error) { // Only retry on specific error types (e.g., network or 5xx) // Example condition: retry on network errors or Plivo server errors (5xx) const shouldRetry = !error.statusCode || error.statusCode >= 500 || (error.code && ['ECONNRESET', 'ETIMEDOUT', 'ENETUNREACH'].includes(error.code)); if (retries > 0 && shouldRetry) { console.warn(`[RETRY] Plivo call failed, retrying in ${delay}ms... (${retries} retries left)`); await new Promise(resolve => setTimeout(resolve, delay)); // Exponential backoff: double the delay for the next retry return sendMmsWithRetry(params, retries - 1, delay * 2); } else { console.error(`[FAIL] Plivo call failed after retries or due to non-retryable error.`); throw error; // Re-throw the error if retries exhausted or not retryable } } } // In your API route: // try { // const response = await sendMmsWithRetry({ /* Plivo params */ }); // // Handle success // } catch (error) { // // Handle final failure // }Tracking MMS Status with a Database
While your simple API doesn't require a database, a production system often benefits from one for:
messageUuidreturned by Plivo and later update its status (sent, delivered, failed) using Plivo's message status callbacks (webhooks)If adding a database (e.g., PostgreSQL with Prisma):
Schema Design (Conceptual
schema.prisma):Data Access: Use an ORM like Prisma or Sequelize to interact with the database, providing type safety and simplifying queries. Your API endpoint would first save a record with
status: 'initiated'and then update it based on the Plivo API response or subsequent status callbacks.Migrations: Use the ORM's migration tools (e.g.,
prisma migrate dev,sequelize db:migrate) to manage schema changes safely.This section is conceptual. Implementing a full database layer is beyond the scope of this specific MMS sending guide but is a common next step for production systems.
Security Best Practices for MMS APIs
Secure your API endpoint.
Input Validation and Sanitization:
joi,express-validator,libphonenumber-js) to enforce strict rules onrecipientNumber(valid E.164 format),messageText(length limits, character types), andmediaUrl(valid URL format, perhaps restricting domains).messageTextthat might be displayed elsewhere, use libraries likedompurify(for HTML) or ensure proper escaping to prevent cross-site scripting (XSS). Plivo handles the SMS/MMS encoding.Authentication/Authorization:
Authorization: Bearer YOUR_API_KEYorX-API-Key: YOUR_API_KEY). Your Express app verifies the key against a stored list/database before processing the request.Rate Limiting:
express-rate-limit. Configure it based on IP address or API key.Example using
express-rate-limit:// app.js const rateLimit = require('express-rate-limit'); const apiLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // Limit each IP to 100 requests per windowMs message: 'Too many requests from this IP, please try again after 15 minutes', standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers legacyHeaders: false, // Disable the `X-RateLimit-*` headers }); // Apply the rate limiting middleware to API routes // Apply it before your main route handler for /send-mms app.use('/send-mms', apiLimiter); // ... rest of your app.js, including the app.post('/send-mms', ...) route ...HTTPS: Always use HTTPS in production to encrypt data in transit. Deployment platforms like Heroku or using a reverse proxy like Nginx typically handle TLS/SSL termination.
Environment Variables: Keep sensitive data (API keys, Plivo Auth tokens) out of your code using environment variables. Ensure your
.envfile is in.gitignore. For production, do not deploy the.envfile. Instead, use your hosting platform's built-in environment variable configuration (e.g., Heroku Config Vars, AWS Parameter Store, Azure App Configuration) or a dedicated secrets management service (e.g., AWS Secrets Manager, HashiCorp Vault). For related SMS implementations, check out our guides on basic SMS sending with Plivo and two-way messaging.Frequently Asked Questions
What is the maximum file size for MMS attachments with Plivo?
Plivo supports a maximum of 2 MB per attachment with up to 10 attachments per message. The total message size (text + all attachments) must be less than 5 MB. Messages exceeding 5 MB will fail with error code 120. For optimal carrier compatibility, keep attachments under 600 KB for US carriers and under 1 MB for Canadian carriers.
Can I send MMS messages internationally with Plivo?
No. Plivo MMS is only available for US and Canadian phone numbers. You need a US or Canadian Plivo number to send MMS, and recipients must also be in the US or Canada. For international messaging, consider using SMS or other messaging channels like WhatsApp.
How do I send MMS from a Plivo trial account?
Trial accounts can only send MMS to verified sandbox numbers. Add recipient numbers in the Plivo Console under Phone Numbers > Sandbox Numbers. Trial accounts cannot use alphanumeric sender IDs and require a minimum $25 recharge to upgrade and remove restrictions.
What media formats does Plivo MMS support?
Plivo fully supports and formats JPEG, PNG, and GIF images for device compatibility. Additional formats (various image, video, and audio types) are accepted but not automatically formatted for destination devices. See the complete list of supported MIME types. Always test with your target carriers to ensure compatibility.
How do I handle MMS delivery status in Node.js?
Implement webhook endpoints to receive Plivo delivery status callbacks. Configure the callback URL in your Plivo Console under message settings or use the
urlparameter when sending messages via the Send Message API. Your Express app should handle POST requests containing status updates (queued, sent, delivered, failed) and store them in your database using the message UUID.Can I send the same media file to multiple recipients?
Yes. Upload media to Plivo using Messaging > MMS Media Upload in the console or via the Upload Media API. You'll receive a
media_idthat you can reuse across multiple messages without re-uploading. This approach is more efficient than usingmedia_urlsfor frequently sent media.What's the difference between media_urls and media_ids?
media_urlsare publicly accessible URLs that Plivo fetches when sending the MMS.media_idsrefer to media files you've pre-uploaded to Plivo's servers. Usemedia_idsfor frequently used media or when you don't have public hosting. Both methods support the same file size limits (2 MB per file, 5 MB total).How do I test MMS sending locally in development?
Run your Express server locally with
node app.js, then use curl or Postman to send POST requests tohttp://localhost:3000/send-mms. Ensure your Plivo credentials are in.envand recipient numbers are verified in sandbox (for trial accounts). Check the Plivo Console logs for detailed delivery information.Why is my MMS failing with error code 120?
Error code 120 indicates your message exceeds the 5 MB total size limit. Check that each attachment is under 2 MB and the combined size of all attachments plus text is under 5 MB. Consider compressing images or videos before sending, or split content across multiple messages.
Can toll-free numbers send MMS with Plivo?
Yes. Plivo added MMS support for toll-free numbers in July 2020. However, as of January 31, 2024, unverified toll-free numbers may have messages blocked. Verify MMS capability and toll-free verification status in the Plivo Console under Phone Numbers > Your Numbers before sending.