How to Send MMS with Plivo, Node.js & Fastify: Complete 2025 Guide
Learn how to send MMS messages using Plivo API, Fastify, and Node.js. Complete tutorial with code examples, authentication, rate limiting, and error handling for multimedia messaging.
Learn how to build a production-ready service using Fastify and Node.js to send Multimedia Messaging Service (MMS) messages via the Plivo API. This comprehensive tutorial covers project setup, authentication, sending logic, error handling, rate limiting, and testing.
By the end of this guide, you'll have a robust Fastify application that sends MMS messages with media attachments (images, audio, video) using Plivo's reliable messaging infrastructure.
MMS vs SMS: When to Use Multimedia Messaging
MMS (Multimedia Messaging Service) allows you to send rich media content including images, audio files, videos, and longer text (up to 1600 characters), while SMS is limited to 160 characters of text only. Use MMS when you need to:
Send visual content like product images, QR codes, or promotional graphics
Share audio messages or video clips
Deliver richer, more engaging marketing campaigns
Provide visual instructions or tutorials
Note that MMS messages typically cost more than SMS and require cellular data connection on the recipient's device. Plivo supports MMS exclusively within the US and Canada.
Technologies Used:
Node.js: The JavaScript runtime environment.
Fastify: A high-performance, low-overhead web framework for Node.js. Chosen for its speed, extensive plugin ecosystem, and developer-friendly features like built-in validation.
Plivo Node.js SDK: The official library for interacting with the Plivo API, simplifying MMS sending.
dotenv: A module to load environment variables from a .env file, keeping sensitive credentials out of the codebase.
pino-pretty (Optional): A development dependency to make Fastify's logs more human-readable during development.
Prerequisites:
Node.js and npm (or yarn): Ensure you have Node.js 22.x LTS installed (Active LTS until October 2025, Maintenance LTS until April 2027). Node.js 22 is the recommended version for production applications as of January 2025. Download from Node.js Downloads. Source: Node.js Release Schedule
Plivo Account: You need an active Plivo account. (Sign up for Plivo)
Plivo Auth ID and Auth Token: Found on your Plivo console dashboard after logging in.
Plivo Phone Number: An MMS-enabled Plivo phone number (available for US & Canada only). Plivo supports MMS messaging exclusively within the US and Canada using local long code, toll-free, or short code numbers. You can purchase one from the Plivo console under "Phone Numbers" > "Buy Numbers". Ensure the number has MMS capabilities enabled. Source: Plivo MMS Support
Publicly Accessible Media URL: The URL of the image or media file you want to send. Supported formats include JPEG, GIF, PNG (images), MP3 (audio), and MP4 (video). Crucially, Plivo's servers must be able to publicly fetch the media from this URL. For Canadian carriers, keep MMS attachments smaller than 1 MB to ensure broad compatibility across all networks. For testing, services like Postimages or public GitHub repository links can work. Source: Plivo MMS Country Support
(Optional) Verified Destination Number: If using a Plivo trial account, you can only send messages to numbers verified in your Plivo console under "Messaging" > "Sandbox Numbers".
System Architecture:
(A diagram illustrating the flow from HTTP Client -> Fastify App -> Plivo API -> End User would typically be included here.)
This guide focuses on the "Fastify Application" component and its interaction with the Plivo API for sending MMS.
Setting Up Your Plivo MMS Project
Let's initialize the project, install dependencies, and set up the basic structure.
Create Project Directory:
Open your terminal and create a new directory for your project, then navigate into it.
Bash
mkdir fastify-plivo-mms
cd fastify-plivo-mms
Initialize Node.js Project:
Initialize a package.json file. The -y flag accepts default settings.
Bash
npm init -y
# or
yarn init -y
Install Dependencies:
Install Fastify, the Plivo SDK, and dotenv.
src/app.js: The main Fastify application entry point.
src/routes/mms.js: The route handler specifically for MMS sending.
.env: Stores sensitive environment variables (API keys, etc.). Never commit this file.
.env.example: An example file showing required environment variables (safe to commit).
.gitignore: Specifies files and directories that Git should ignore.
Configure .gitignore:
Add node_modules and .env to your .gitignore file to prevent committing them to version control.
Text
# .gitignore
node_modules
.env
*.log
Configure .env.example:
Add the necessary environment variable placeholders to .env.example.
.env
# .env.example
# Plivo API Credentials
PLIVO_AUTH_ID=YOUR_PLIVO_AUTH_ID
PLIVO_AUTH_TOKEN=YOUR_PLIVO_AUTH_TOKEN
# Plivo MMS-enabled phone number (in E.164 format, e.g., +14151234567)
PLIVO_SENDER_NUMBER=YOUR_PLIVO_MMS_NUMBER
# Server Configuration
HOST=0.0.0.0
PORT=3000
Configure .env:
Copy .env.example to .env and fill in your actual Plivo credentials and phone number.
Bash
cp .env.example .env
Now, edit the .env file with your real values obtained from the Plivo console.
.env
# .env
# Plivo API Credentials
PLIVO_AUTH_ID=MXXXXXXXXXXXXXXXXXXD
PLIVO_AUTH_TOKEN=NXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXZA
# Plivo MMS-enabled phone number (in E.164 format, e.g., +14151234567)
PLIVO_SENDER_NUMBER=+14151112222
# Server Configuration
HOST=0.0.0.0
PORT=3000
Why .env? Storing sensitive information like API keys directly in code is a major security risk. .env files allow you to keep configuration separate from the codebase, making it easier to manage different environments (development, production) and preventing accidental exposure.
Implementing the Fastify Server for MMS
Let's set up the basic Fastify server and configure it to use the Plivo client.
Edit src/app.js:
This file will initialize Fastify, load environment variables, instantiate the Plivo client, register routes, and start the server.
JavaScript
// src/app.js
'use strict'constFastify = require('fastify');
const plivo = require('plivo');
const mmsRoutes = require('./routes/mms');
require('dotenv').config(); // Load .env variables into process.env// Basic validation for essential environment variablesconst requiredEnv = ['PLIVO_AUTH_ID', 'PLIVO_AUTH_TOKEN', 'PLIVO_SENDER_NUMBER', 'PORT', 'HOST'];
const missingEnv = requiredEnv.filter(envVar => !process.env[envVar]);
if (missingEnv.length > 0) {
console.error(`Error: Missing required environment variables: ${missingEnv.join(', ')}`);
console.error('Please ensure they are set in your .env file or environment.');
process.exit(1); // Exit if essential config is missing
}
// Configure Fastify logger for developmentconst loggerConfig = process.env.NODE_ENV === 'development' ? {
transport: {
target: 'pino-pretty',
options: {
translateTime: 'HH:MM:ss Z',
ignore: 'pid,hostname',
},
},
} : true; // Use default JSON logger in production// Initialize Fastifyconst app = Fastify({
logger: loggerConfig
});
// --- Plivo Client Initialization ---// Why here? Instantiate the client once and make it available// throughout the application via Fastify's decoration feature.try {
const plivoClient = new plivo.Client(
process.env.PLIVO_AUTH_ID,
process.env.PLIVO_AUTH_TOKEN
);
// Decorate the Fastify instance with the Plivo client// Makes `app.plivo` accessible in route handlers
app.decorate('plivo', plivoClient);
app.log.info('Plivo client initialized successfully.');
} catch (error) {
app.log.error('Failed to initialize Plivo client:', error);
process.exit(1); // Cannot proceed without a working client
}
// Decorate Fastify with the sender number for easy access
app.decorate('plivoSenderNumber', process.env.PLIVO_SENDER_NUMBER);
// --- Register Routes ---// Prefix all routes defined in mms.js with /api/mms
app.register(mmsRoutes, { prefix: '/api/mms' });
// --- Health Check Route ---// Good practice for monitoring and load balancers
app.get('/health', async (request, reply) => {
return { status: 'ok', timestamp: newDate().toISOString() };
});
// --- Start Server ---conststart = async () => {
try {
const port = parseInt(process.env.PORT, 10);
const host = process.env.HOST;
await app.listen({ port: port, host: host });
// Logger already announces the address
} catch (err) {
app.log.error(err);
process.exit(1);
}
};
start();
Environment Variables:dotenv.config() loads variables from .env. We add basic checks to ensure critical variables are present.
Logger: We configure pino-pretty for development for easier reading and standard JSON logging for production (better for log aggregation tools).
Plivo Client: The plivo.Client is instantiated using credentials from process.env.
Fastify Decorators (app.decorate): This is a key Fastify pattern. We "decorate" the Fastify app instance with the plivoClient and plivoSenderNumber. This makes them easily accessible within route handlers via request.server.plivo or reply.server.plivo (and similarly for plivoSenderNumber), avoiding the need to pass them around manually or re-initialize them.
Route Registration: We register the routes defined in src/routes/mms.js under the /api/mms prefix.
Health Check: A simple /health endpoint is added, which is standard practice for monitoring.
Server Start: The app.listen function starts the server on the configured host and port.
Building the MMS Sending API Endpoint
Now, let's implement the route that will handle incoming requests to send an MMS.
Edit src/routes/mms.js:
This file defines the API endpoint for sending MMS messages.
JavaScript
// src/routes/mms.js
'use strict';
const plivo = require('plivo'); // Required for Plivo specific error types if needed// Define the JSON schema for the request body// Why schema? Fastify uses this for automatic validation.// It's faster than manual checks and ensures data consistency.const sendMmsSchema = {
body: {
type: 'object',
required: ['to', 'media_urls', 'text'], // Define mandatory fieldsproperties: {
to: {
type: 'string',
// E.164 format: + followed by 1-3 digit country code + subscriber number (max 15 digits total)pattern: '^\\+[1-9]\\d{1,14}$',
description: 'Destination phone number in E.164 format (e.g., +14151234567). Must start with + followed by country code and subscriber number. Maximum 15 digits total per ITU-T E.164 specification.'// ITU-T specification reference
},
media_urls: {
type: 'array',
minItems: 1, // Must have at least one media URLmaxItems: 10, // Plivo supports up to 10 media URLs per MMSitems: {
type: 'string',
format: 'url', // Basic URL format validationdescription: 'Publicly accessible URL of the media file. Supported formats: JPEG, GIF, PNG (images), MP3 (audio), MP4 (video). Keep files under 1 MB for Canadian carriers.'
}
},
text: {
type: 'string',
minLength: 1, // Text cannot be emptydescription: 'The text content of the MMS message'
}
// Optional: Add other Plivo parameters here if needed (e.g., url, method for status callbacks)
},
additionalProperties: false// Disallow properties not defined in the schema
},
response: { // Define expected success response structure200: {
type: 'object',
properties: {
message: { type: 'string' },
// Use camelCase for consistency with Node.js conventions and SDK responsemessageUuid: { type: 'array', items: { type: 'string' } },
apiId: { type: 'string' }
}
},
// Define potential error responses (Fastify handles validation errors automatically -> 400)500: {
type: 'object',
properties: {
statusCode: { type: 'number' },
error: { type: 'string' },
message: { type: 'string' }
}
},
429: { // For rate limitingtype: 'object',
properties: {
statusCode: { type: 'number' },
error: { type: 'string' },
message: { type: 'string' }
}
}
// Add other specific error status codes Plivo might return if needed (e.g., 400, 401, 402)
}
};
asyncfunctionmmsRoutes(fastify, options) {
// Access the decorated Plivo client and sender numberconst plivoClient = fastify.plivo;
const senderNumber = fastify.plivoSenderNumber;
fastify.post('/send', { schema: sendMmsSchema }, async (request, reply) => {
const { to, media_urls, text } = request.body; // Data is already validated by Fastify
request.log.info(`Attempting to send MMS to ${to} from ${senderNumber}`);
try {
const params = {
src: senderNumber,
dst: to, // Plivo uses 'dst' for destinationtext: text,
media_urls: media_urls,
type: 'mms'// Explicitly set type to MMS// Optional: Configure status callbacks// url: 'https://your-callback-url.com/plivo-status', // Your webhook endpoint// method: 'POST'
};
const response = await plivoClient.messages.create(params);
request.log.info({ msg: 'MMS sent successfully', response: response }, `MMS UUID(s): ${response.messageUuid.join(', ')}`);
// Send successful response back to the client (using camelCase keys)return reply.code(200).send({
message: response.message, // Success message from PlivomessageUuid: response.messageUuid, // Plivo's message identifier(s)apiId: response.apiId// Plivo's API request identifier
});
} catch (error) {
request.log.error({ err: error }, `Failed to send MMS to ${to}`);
// Handle potential Plivo-specific errors or general errorslet statusCode = 500;
let errorMessage = 'An unexpected error occurred while sending the MMS.';
if (error instanceof plivo.PlivoResponseError) {
// Plivo API returned an error response
statusCode = error.statusCode || 500; // Use Plivo's status if available
errorMessage = `Plivo API Error (${error.statusCode}): ${error.message || 'Unknown Plivo error'}`;
// Common Plivo errors include 400 (e.g., invalid 'dst', number capability issues), 401 (auth), 402 (insufficient balance).// You might want more specific handling based on Plivo error codes here.
} elseif (error.name === 'ValidationError') {
// Although Fastify handles schema validation, keep this for potential future manual checks
statusCode = 400;
errorMessage = `Validation Error: ${error.message}`;
}
// Add more specific error handling if needed (e.g., network errors)// Send error responsereturn reply.code(statusCode).send({
statusCode: statusCode,
error: error.name || 'Internal Server Error',
message: errorMessage
});
}
});
}
module.exports = mmsRoutes;
Schema Validation: We define a sendMmsSchema using JSON Schema. Fastify automatically validates incoming request bodies against this schema before the handler function runs. If validation fails, Fastify sends a 400 Bad Request response automatically. This simplifies the handler logic significantly. We specify required fields (to, media_urls, text), types, and formats (like E.164 pattern and URL format). additionalProperties: false prevents extra, unexpected fields. The to field pattern was corrected, and its description enhanced. Added maxItems: 10 to media_urls based on Plivo limits.
Route Definition:fastify.post('/send', { schema: sendMmsSchema }, ...) defines a POST endpoint at /api/mms/send. The schema is attached directly to the route options.
Accessing Decorators:fastify.plivo and fastify.plivoSenderNumber provide the initialized Plivo client and sender number.
Plivo API Call: We construct the params object required by plivoClient.messages.create. Note that Plivo uses dst for the destination number. We explicitly set type: 'mms'.
async/await: The route handler is an async function, allowing us to use await for the Plivo API call, making the asynchronous code cleaner.
Success Response: On success, we log the result and return a 200 OK response with details from the Plivo API. The response keys (messageUuid, apiId) are now consistently camelCase, matching the schema and common Node.js practice.
Error Handling: The try...catch block handles errors during the API call. We log the error and attempt to provide a meaningful status code and message back to the client. We check if the error is a specific PlivoResponseError to potentially extract more details, adding a comment about common error codes. The response schema was updated to use camelCase for consistency.
Adding Rate Limiting for Security
To prevent abuse and protect your Plivo account balance, implementing rate limiting is crucial.
Install Rate Limiter Plugin:
Bash
npm install @fastify/rate-limit
# or
yarn add @fastify/rate-limit
Register and Configure in src/app.js:
Modify src/app.js to include the rate limiter.
JavaScript
// src/app.js// ... other importsconst rateLimit = require('@fastify/rate-limit');
// ... after Fastify initialization// --- Rate Limiting ---// Why? Prevents abuse and controls costs. Apply globally or per-route.// Register *before* your main application routes
app.register(rateLimit, {
max: 100, // Max requests per time window per IP (adjust as needed)timeWindow: '1 minute', // Time window duration// Optional: Add custom key generator, redis store for distributed systems, etc.// keyGenerator: function (req) { /* ... */ },// redis: new Redis({ host: '127.0.0.1' }),errorResponseBuilder: function (req, context) {
return {
statusCode: 429,
error: 'Too Many Requests',
message: `Rate limit exceeded. Please try again after ${context.after}.`,
retryAfter: context.ttl, // Time in milliseconds until reset
}
},
enableDraftSpec: true, // Recommended: Set standard RateLimit-* headers
});
// --- Plivo Client Initialization --- // Moved *after* rate limit registrationtry {
const plivoClient = new plivo.Client(
process.env.PLIVO_AUTH_ID,
process.env.PLIVO_AUTH_TOKEN
);
// Decorate the Fastify instance with the Plivo client// Makes `app.plivo` accessible in route handlers
app.decorate('plivo', plivoClient);
app.log.info('Plivo client initialized successfully.');
} catch (error) {
app.log.error('Failed to initialize Plivo client:', error);
process.exit(1); // Cannot proceed without a working client
}
// Decorate Fastify with the sender number for easy access
app.decorate('plivoSenderNumber', process.env.PLIVO_SENDER_NUMBER);
// --- Register Routes ---// Prefix all routes defined in mms.js with /api/mms
app.register(mmsRoutes, { prefix: '/api/mms' });
// --- Health Check Route ---// Good practice for monitoring and load balancers
app.get('/health', async (request, reply) => {
return { status: 'ok', timestamp: newDate().toISOString() };
});
// --- Start Server ---conststart = async () => {
try {
const port = parseInt(process.env.PORT, 10);
const host = process.env.HOST;
await app.listen({ port: port, host: host });
// Logger already announces the address
} catch (err) {
app.log.error(err);
process.exit(1);
}
};
start();
Registration: We use app.register to add the @fastify/rate-limit plugin. It's important to register this before the routes you want to limit.
Configuration:
max: Sets the maximum number of requests allowed within the timeWindow. Adjust this based on expected traffic and Plivo's own rate limits.
timeWindow: Defines the duration over which requests are counted (e.g., '1 minute', '1 hour', 60000 ms).
errorResponseBuilder: Customizes the 429 Too Many Requests response sent when the limit is hit.
enableDraftSpec: Adds standard RateLimit-* headers to responses, which is good practice.
Scope: By registering it here before other routes, it applies globally by default (based on IP address). You can configure it more granularly per-route if needed.
Testing Your MMS Service
Now, let's run the server and test the MMS sending endpoint.
Add Run Scripts to package.json:
JSON
// package.json snippet{// ... other fields like name, version, main, etc."scripts":{"start":"node src/app.js","dev":"NODE_ENV=development node src/app.js | pino-pretty"}// ... dependencies, etc.}
start: Runs the application in production mode (default logger).
dev: Runs in development mode, setting NODE_ENV and piping logs through pino-pretty.
Run the Server (Development Mode):
Bash
npm run dev
# or
yarn dev
You should see output indicating the server is running, similar to:
Code
[HH:MM:SS Z] INFO: Plivo client initialized successfully.
[HH:MM:SS Z] INFO: Server listening at http://0.0.0.0:3000
Test with curl:
Open another terminal window. Replace placeholders with your verified destination number (for trial accounts) or any US/Canada number (for paid accounts), and a publicly accessible media URL.
Success Case:
Bash
curl -X POST http://localhost:3000/api/mms/send \
-H "Content-Type: application/json" \
-d '{
"to": "+1DESTINATION_NUMBER",
"media_urls": ["https://upload.wikimedia.org/wikipedia/commons/thumb/a/a4/Plivo_logo.png/600px-Plivo_logo.png"],
"text": "Hello from Fastify and Plivo!"
}'
Expected Success Response (JSON - using camelCase):
You should also see corresponding logs in the server terminal and receive the MMS on the destination phone shortly.
Code
**Validation Error Case (Missing `text`):**
```bash
curl -X POST http://localhost:3000/api/mms/send \
-H "Content-Type: application/json" \
-d '{
"to": "+1DESTINATION_NUMBER",
"media_urls": ["https://example.com/image.png"]
}'
```
*Expected Error Response (400 Bad Request - Handled by Fastify):*
```json
{
"statusCode": 400,
"error": "Bad Request",
"message": "body should have required property 'text'"
}
```
**Plivo API Error Case (e.g., Invalid Auth Token in `.env`):**
*(Simulate by temporarily putting an incorrect token in `.env` and restarting the server)*
```bash
# Send the valid request again after messing up the token
curl -X POST http://localhost:3000/api/mms/send \
-H "Content-Type: application/json" \
-d '{
"to": "+1DESTINATION_NUMBER",
"media_urls": ["https://upload.wikimedia.org/wikipedia/commons/thumb/a/a4/Plivo_logo.png/600px-Plivo_logo.png"],
"text": "Testing error"
}'
```
*Expected Error Response (Handled by our `catch` block):*
```json
{
"statusCode": 401,
"error": "PlivoResponseError",
"message": "Plivo API Error (401): Authentication credentials were not provided or are invalid."
}
```
Troubleshooting Common MMS Issues
Authentication Errors (401): Double-check PLIVO_AUTH_ID and PLIVO_AUTH_TOKEN in your .env file. Ensure they are copied correctly from the Plivo console.
Invalid src or dst Number (e.g., 400 Bad Request from Plivo):
Ensure PLIVO_SENDER_NUMBER is an MMS-enabled number you own on Plivo.
Ensure both src and dst numbers are in E.164 format (e.g., +14151234567). The to field validator checks the format (^\\+[1-9]\\d{1,14}$).
If using a trial account, the dst number must be verified in the Sandbox Numbers section of the Plivo console.
Invalid Media URL: The media_urlsmust point to publicly accessible URLs. Plivo's servers need to fetch the media from these URLs. Test the URL in your browser first. Supported file types include JPEG, GIF, PNG (images), MP3 (audio), and MP4 (video). For Canadian carriers, keep MMS attachments smaller than 1 MB to ensure broad compatibility across all networks.
Rate Limits (429): If you send too many requests too quickly, you'll hit the rate limit configured in Fastify (max: 100, timeWindow: '1 minute' by default in this guide) or Plivo's own API limits. Plivo enforces a default limit of 300 API requests per 5 seconds for non-call APIs, and supports up to 100 concurrent MMS requests. When limits are exceeded, Plivo responds with HTTP 429 Too Many Requests. The error response indicates when you can retry. Source: Plivo Rate Limits and Plivo MMS Account Rate Limits
MMS Not Supported: Plivo primarily supports sending MMS between US and Canadian numbers. Sending to other countries may fail or be converted to SMS with a link. Check Plivo's documentation for current country support.
Other Plivo API Errors (4xx/5xx): Beyond authentication (401), you might encounter errors like 400 (Bad Request - often due to invalid numbers, lack of MMS capability on the source number, or malformed requests), 402 (Payment Required - insufficient account balance), or Plivo-side server errors (5xx). The error message provided by the API is usually informative. Check the Plivo API documentation for specific error code meanings.
Network Issues: Ensure your server has outbound internet connectivity to reach the Plivo API (api.plivo.com). Firewalls or network configuration could block requests.
Message Queued vs. Delivered: A successful API response (200 OK, message: "message(s) queued") only means Plivo accepted the request. It doesn't guarantee delivery to the handset. For delivery confirmation, you need to implement webhook handlers for Plivo's status callbacks (using the url and method parameters in the messages.create call). This is a more advanced topic.
Large Media Files: Very large media files might take longer to process or could potentially time out. Consider optimizing media file sizes. Check Plivo's limits on file size if you encounter issues.
Production Deployment Best Practices
Environment Variables: In production, never commit your .env file. Use your hosting provider's mechanism for setting environment variables (e.g., Heroku Config Vars, AWS Systems Manager Parameter Store, Docker environment variables).
Process Management: Use a process manager like pm2 or run your application within a container (Docker) to handle restarts, clustering (for multi-core utilization), and logging.
Example with pm2: npm install -g pm2, then pm2 start src/app.js --name fastify-plivo-mms
HTTPS: Always run your production application behind a reverse proxy (like Nginx or Caddy) that handles HTTPS termination. Fastify itself can handle HTTPS, but offloading it is common practice.
Logging: Configure production logging to output structured JSON (Fastify's default) and forward these logs to a centralized logging service (e.g., Better Stack, Datadog, ELK stack) for analysis and monitoring.
Monitoring: Set up external uptime monitoring (e.g., Better Stack Uptime, UptimeRobot) to ping your /health endpoint. Monitor application performance (APM tools) and error rates.
Plivo MMS Frequently Asked Questions
How do I send MMS with Plivo and Node.js?
To send MMS with Plivo and Node.js, install the Plivo SDK (npm install plivo), initialize a Plivo client with your Auth ID and Auth Token, and call client.messages.create() with parameters including src (your Plivo number), dst (recipient in E.164 format), text, media_urls (array of public media URLs), and type: 'mms'. Plivo supports MMS only within the US and Canada.
What file formats does Plivo MMS support?
Plivo MMS supports JPEG, GIF, and PNG for images, MP3 for audio, and MP4 for video. Media files must be hosted on publicly accessible URLs that Plivo servers can fetch. For Canadian carriers, keep MMS attachments smaller than 1 MB to ensure broad compatibility across all networks. You can include up to 10 media URLs per MMS message.
What is E.164 phone number format?
E.164 is the international phone number format standardized by ITU-T. It consists of a plus sign (+) followed by a country code (1-3 digits) and subscriber number, with a maximum of 15 digits total. Example: +14151234567 for a US number. Both source and destination numbers must use E.164 format for Plivo API calls.
Why use Fastify for MMS messaging?
Fastify is a high-performance, low-overhead Node.js web framework ideal for MMS messaging services. It provides built-in JSON schema validation, automatic request/response serialization, extensive plugin ecosystem (including @fastify/rate-limit), and superior performance compared to Express. Fastify's decorator pattern simplifies sharing the Plivo client across routes.
What are Plivo API rate limits?
Plivo enforces a default limit of 300 API requests per 5 seconds for non-call APIs and supports up to 100 concurrent MMS requests. When limits are exceeded, Plivo responds with HTTP 429 Too Many Requests. Implement rate limiting in your Fastify application using @fastify/rate-limit to control costs and prevent abuse.
Can I send MMS to countries outside US and Canada with Plivo?
No. Plivo supports MMS messaging exclusively within the US and Canada. Attempts to send MMS to other countries may fail or be converted to SMS with a media link. You can only send and receive MMS using US or Canadian Plivo phone numbers (local long code, toll-free, or short code).
How do I handle Plivo MMS delivery status?
Plivo's messages.create() returns a 200 OK response when messages are queued, not delivered. For delivery confirmation, implement webhook handlers for Plivo's status callbacks by adding url and method parameters to your API call. Plivo will POST delivery status updates (delivered, failed, undelivered) to your webhook endpoint.
What Node.js version should I use for Plivo MMS?
Use Node.js 22.x LTS for production applications. Node.js 22 is in Active LTS until October 2025 and Maintenance LTS until April 2027. This version provides full support for modern JavaScript features, security updates, and optimal performance for Fastify and Plivo SDK integration.
Related Resources and Next Steps
Now that you've built a Fastify MMS service with Plivo, consider exploring these related topics:
You have now successfully built a Fastify application capable of sending MMS messages using the Plivo Node.js SDK. We covered project setup, secure credential management, API route implementation with validation, error handling, rate limiting, and essential testing procedures.
This provides a solid foundation for integrating MMS capabilities into your Node.js applications. From here, you could expand the service to include status webhooks for delivery tracking, build a frontend interface, or integrate it into a larger communication workflow.
Frequently asked questions
Use the Plivo Node.js SDK with a web framework like Fastify. This allows you to create a server-side application that can handle MMS sending requests via the Plivo API. Make sure to set up your Plivo account and configure the necessary credentials in your project.
Fastify is a high-performance Node.js web framework known for its speed and plugin ecosystem. The tutorial uses it for its efficiency and features like built-in request validation and easy route handling.
Dotenv helps manage environment variables, allowing you to store sensitive information like API keys outside your codebase. This improves security and makes it easier to manage different environments (development, production).
Use npm or yarn. Install fastify, plivo, and dotenv as core dependencies. For development, pino-pretty is recommended for enhanced logging. Run npm install fastify plivo dotenv or yarn add fastify plivo dotenv, and similarly for dev dependencies.
The .env file securely stores your Plivo Auth ID, Auth Token, sender number, host, and port. This keeps sensitive credentials out of your codebase, enhancing security.
Create directories for source code (src), routes (src/routes), and place your main application file (app.js) in src. Routes are defined in files within src/routes. Also, include .env, .env.example, and .gitignore files in the root.
Your Plivo Auth ID and Auth Token are found on your Plivo console dashboard after you log in. These credentials are essential for authenticating with the Plivo API.
You need a Plivo phone number with MMS capabilities enabled, typically available for the US and Canada. Purchase one from the Plivo console and confirm MMS is enabled.
The provided code example uses try...catch blocks and checks for PlivoResponseError to handle errors. You can provide specific responses depending on the error code returned by the Plivo API.
The tutorial uses @fastify/rate-limit to implement rate limiting. The default example configuration limits to 100 requests per minute per IP, which is customizable.
Rate limiting helps prevent abuse and protects your Plivo account balance by controlling the number of MMS messages sent within a specific time window.
Use curl to send test requests to your local Fastify server. Provide valid JSON data with recipient number, media URLs, and message text in the request body.
A successful response will return a 200 OK status code with a JSON body including the message, messageUuid (an array of UUIDs), and apiId.
It means Plivo has accepted your request and queued the message for sending. It doesn't guarantee delivery; for that, implement status callbacks.
The tutorial provides troubleshooting for authentication errors, invalid numbers, media URL issues, rate limiting, and other potential Plivo API errors. Refer to the article for more details.
Learn how to build a production-ready service using Fastify and Node.js to send Multimedia Messaging Service (MMS) messages via the Plivo API. This comprehensive tutorial covers project setup, authentication, sending logic, error handling, rate limiting, and testing.
By the end of this guide, you'll have a robust Fastify application that sends MMS messages with media attachments (images, audio, video) using Plivo's reliable messaging infrastructure.
MMS vs SMS: When to Use Multimedia Messaging
MMS (Multimedia Messaging Service) allows you to send rich media content including images, audio files, videos, and longer text (up to 1600 characters), while SMS is limited to 160 characters of text only. Use MMS when you need to:
Note that MMS messages typically cost more than SMS and require cellular data connection on the recipient's device. Plivo supports MMS exclusively within the US and Canada.
Technologies Used:
.envfile, keeping sensitive credentials out of the codebase.Prerequisites:
System Architecture:
(A diagram illustrating the flow from HTTP Client -> Fastify App -> Plivo API -> End User would typically be included here.)
This guide focuses on the "Fastify Application" component and its interaction with the Plivo API for sending MMS.
Setting Up Your Plivo MMS Project
Let's initialize the project, install dependencies, and set up the basic structure.
Create Project Directory: Open your terminal and create a new directory for your project, then navigate into it.
mkdir fastify-plivo-mms cd fastify-plivo-mmsInitialize Node.js Project: Initialize a
package.jsonfile. The-yflag accepts default settings.npm init -y # or yarn init -yInstall Dependencies: Install Fastify, the Plivo SDK, and
dotenv.npm install fastify plivo dotenv # or yarn add fastify plivo dotenvInstall Development Dependencies (Optional): Install
pino-prettyfor better log readability during development.npm install --save-dev pino-pretty # or yarn add --dev pino-prettyCreate Project Structure: Set up a basic directory structure for better organization.
mkdir src mkdir src/routes touch src/app.js touch src/routes/mms.js touch .env touch .env.example touch .gitignoresrc/: Contains the main application source code.src/routes/: Will hold route definitions.src/app.js: The main Fastify application entry point.src/routes/mms.js: The route handler specifically for MMS sending..env: Stores sensitive environment variables (API keys, etc.). Never commit this file..env.example: An example file showing required environment variables (safe to commit)..gitignore: Specifies files and directories that Git should ignore.Configure
.gitignore: Addnode_modulesand.envto your.gitignorefile to prevent committing them to version control.Configure
.env.example: Add the necessary environment variable placeholders to.env.example.Configure
.env: Copy.env.exampleto.envand fill in your actual Plivo credentials and phone number.cp .env.example .envNow, edit the
.envfile with your real values obtained from the Plivo console.Why
.env? Storing sensitive information like API keys directly in code is a major security risk..envfiles allow you to keep configuration separate from the codebase, making it easier to manage different environments (development, production) and preventing accidental exposure.Implementing the Fastify Server for MMS
Let's set up the basic Fastify server and configure it to use the Plivo client.
Edit
src/app.js: This file will initialize Fastify, load environment variables, instantiate the Plivo client, register routes, and start the server.// src/app.js 'use strict' const Fastify = require('fastify'); const plivo = require('plivo'); const mmsRoutes = require('./routes/mms'); require('dotenv').config(); // Load .env variables into process.env // Basic validation for essential environment variables const requiredEnv = ['PLIVO_AUTH_ID', 'PLIVO_AUTH_TOKEN', 'PLIVO_SENDER_NUMBER', 'PORT', 'HOST']; const missingEnv = requiredEnv.filter(envVar => !process.env[envVar]); if (missingEnv.length > 0) { console.error(`Error: Missing required environment variables: ${missingEnv.join(', ')}`); console.error('Please ensure they are set in your .env file or environment.'); process.exit(1); // Exit if essential config is missing } // Configure Fastify logger for development const loggerConfig = process.env.NODE_ENV === 'development' ? { transport: { target: 'pino-pretty', options: { translateTime: 'HH:MM:ss Z', ignore: 'pid,hostname', }, }, } : true; // Use default JSON logger in production // Initialize Fastify const app = Fastify({ logger: loggerConfig }); // --- Plivo Client Initialization --- // Why here? Instantiate the client once and make it available // throughout the application via Fastify's decoration feature. try { const plivoClient = new plivo.Client( process.env.PLIVO_AUTH_ID, process.env.PLIVO_AUTH_TOKEN ); // Decorate the Fastify instance with the Plivo client // Makes `app.plivo` accessible in route handlers app.decorate('plivo', plivoClient); app.log.info('Plivo client initialized successfully.'); } catch (error) { app.log.error('Failed to initialize Plivo client:', error); process.exit(1); // Cannot proceed without a working client } // Decorate Fastify with the sender number for easy access app.decorate('plivoSenderNumber', process.env.PLIVO_SENDER_NUMBER); // --- Register Routes --- // Prefix all routes defined in mms.js with /api/mms app.register(mmsRoutes, { prefix: '/api/mms' }); // --- Health Check Route --- // Good practice for monitoring and load balancers app.get('/health', async (request, reply) => { return { status: 'ok', timestamp: new Date().toISOString() }; }); // --- Start Server --- const start = async () => { try { const port = parseInt(process.env.PORT, 10); const host = process.env.HOST; await app.listen({ port: port, host: host }); // Logger already announces the address } catch (err) { app.log.error(err); process.exit(1); } }; start();dotenv.config()loads variables from.env. We add basic checks to ensure critical variables are present.pino-prettyfor development for easier reading and standard JSON logging for production (better for log aggregation tools).plivo.Clientis instantiated using credentials fromprocess.env.app.decorate): This is a key Fastify pattern. We "decorate" the Fastifyappinstance with theplivoClientandplivoSenderNumber. This makes them easily accessible within route handlers viarequest.server.plivoorreply.server.plivo(and similarly forplivoSenderNumber), avoiding the need to pass them around manually or re-initialize them.src/routes/mms.jsunder the/api/mmsprefix./healthendpoint is added, which is standard practice for monitoring.app.listenfunction starts the server on the configured host and port.Building the MMS Sending API Endpoint
Now, let's implement the route that will handle incoming requests to send an MMS.
Edit
src/routes/mms.js: This file defines the API endpoint for sending MMS messages.// src/routes/mms.js 'use strict'; const plivo = require('plivo'); // Required for Plivo specific error types if needed // Define the JSON schema for the request body // Why schema? Fastify uses this for automatic validation. // It's faster than manual checks and ensures data consistency. const sendMmsSchema = { body: { type: 'object', required: ['to', 'media_urls', 'text'], // Define mandatory fields properties: { to: { type: 'string', // E.164 format: + followed by 1-3 digit country code + subscriber number (max 15 digits total) pattern: '^\\+[1-9]\\d{1,14}$', description: 'Destination phone number in E.164 format (e.g., +14151234567). Must start with + followed by country code and subscriber number. Maximum 15 digits total per ITU-T E.164 specification.' // ITU-T specification reference }, media_urls: { type: 'array', minItems: 1, // Must have at least one media URL maxItems: 10, // Plivo supports up to 10 media URLs per MMS items: { type: 'string', format: 'url', // Basic URL format validation description: 'Publicly accessible URL of the media file. Supported formats: JPEG, GIF, PNG (images), MP3 (audio), MP4 (video). Keep files under 1 MB for Canadian carriers.' } }, text: { type: 'string', minLength: 1, // Text cannot be empty description: 'The text content of the MMS message' } // Optional: Add other Plivo parameters here if needed (e.g., url, method for status callbacks) }, additionalProperties: false // Disallow properties not defined in the schema }, response: { // Define expected success response structure 200: { type: 'object', properties: { message: { type: 'string' }, // Use camelCase for consistency with Node.js conventions and SDK response messageUuid: { type: 'array', items: { type: 'string' } }, apiId: { type: 'string' } } }, // Define potential error responses (Fastify handles validation errors automatically -> 400) 500: { type: 'object', properties: { statusCode: { type: 'number' }, error: { type: 'string' }, message: { type: 'string' } } }, 429: { // For rate limiting type: 'object', properties: { statusCode: { type: 'number' }, error: { type: 'string' }, message: { type: 'string' } } } // Add other specific error status codes Plivo might return if needed (e.g., 400, 401, 402) } }; async function mmsRoutes(fastify, options) { // Access the decorated Plivo client and sender number const plivoClient = fastify.plivo; const senderNumber = fastify.plivoSenderNumber; fastify.post('/send', { schema: sendMmsSchema }, async (request, reply) => { const { to, media_urls, text } = request.body; // Data is already validated by Fastify request.log.info(`Attempting to send MMS to ${to} from ${senderNumber}`); try { const params = { src: senderNumber, dst: to, // Plivo uses 'dst' for destination text: text, media_urls: media_urls, type: 'mms' // Explicitly set type to MMS // Optional: Configure status callbacks // url: 'https://your-callback-url.com/plivo-status', // Your webhook endpoint // method: 'POST' }; const response = await plivoClient.messages.create(params); request.log.info({ msg: 'MMS sent successfully', response: response }, `MMS UUID(s): ${response.messageUuid.join(', ')}`); // Send successful response back to the client (using camelCase keys) return reply.code(200).send({ message: response.message, // Success message from Plivo messageUuid: response.messageUuid, // Plivo's message identifier(s) apiId: response.apiId // Plivo's API request identifier }); } catch (error) { request.log.error({ err: error }, `Failed to send MMS to ${to}`); // Handle potential Plivo-specific errors or general errors let statusCode = 500; let errorMessage = 'An unexpected error occurred while sending the MMS.'; if (error instanceof plivo.PlivoResponseError) { // Plivo API returned an error response statusCode = error.statusCode || 500; // Use Plivo's status if available errorMessage = `Plivo API Error (${error.statusCode}): ${error.message || 'Unknown Plivo error'}`; // Common Plivo errors include 400 (e.g., invalid 'dst', number capability issues), 401 (auth), 402 (insufficient balance). // You might want more specific handling based on Plivo error codes here. } else if (error.name === 'ValidationError') { // Although Fastify handles schema validation, keep this for potential future manual checks statusCode = 400; errorMessage = `Validation Error: ${error.message}`; } // Add more specific error handling if needed (e.g., network errors) // Send error response return reply.code(statusCode).send({ statusCode: statusCode, error: error.name || 'Internal Server Error', message: errorMessage }); } }); } module.exports = mmsRoutes;sendMmsSchemausing JSON Schema. Fastify automatically validates incoming request bodies against this schema before the handler function runs. If validation fails, Fastify sends a400 Bad Requestresponse automatically. This simplifies the handler logic significantly. We specify required fields (to,media_urls,text), types, and formats (like E.164 pattern and URL format).additionalProperties: falseprevents extra, unexpected fields. Thetofield pattern was corrected, and its description enhanced. AddedmaxItems: 10to media_urls based on Plivo limits.fastify.post('/send', { schema: sendMmsSchema }, ...)defines a POST endpoint at/api/mms/send. The schema is attached directly to the route options.fastify.plivoandfastify.plivoSenderNumberprovide the initialized Plivo client and sender number.paramsobject required byplivoClient.messages.create. Note that Plivo usesdstfor the destination number. We explicitly settype: 'mms'.async/await: The route handler is anasyncfunction, allowing us to useawaitfor the Plivo API call, making the asynchronous code cleaner.200 OKresponse with details from the Plivo API. The response keys (messageUuid,apiId) are now consistently camelCase, matching the schema and common Node.js practice.try...catchblock handles errors during the API call. We log the error and attempt to provide a meaningful status code and message back to the client. We check if the error is a specificPlivoResponseErrorto potentially extract more details, adding a comment about common error codes. The response schema was updated to use camelCase for consistency.Adding Rate Limiting for Security
To prevent abuse and protect your Plivo account balance, implementing rate limiting is crucial.
Install Rate Limiter Plugin:
npm install @fastify/rate-limit # or yarn add @fastify/rate-limitRegister and Configure in
src/app.js: Modifysrc/app.jsto include the rate limiter.// src/app.js // ... other imports const rateLimit = require('@fastify/rate-limit'); // ... after Fastify initialization // --- Rate Limiting --- // Why? Prevents abuse and controls costs. Apply globally or per-route. // Register *before* your main application routes app.register(rateLimit, { max: 100, // Max requests per time window per IP (adjust as needed) timeWindow: '1 minute', // Time window duration // Optional: Add custom key generator, redis store for distributed systems, etc. // keyGenerator: function (req) { /* ... */ }, // redis: new Redis({ host: '127.0.0.1' }), errorResponseBuilder: function (req, context) { return { statusCode: 429, error: 'Too Many Requests', message: `Rate limit exceeded. Please try again after ${context.after}.`, retryAfter: context.ttl, // Time in milliseconds until reset } }, enableDraftSpec: true, // Recommended: Set standard RateLimit-* headers }); // --- Plivo Client Initialization --- // Moved *after* rate limit registration try { const plivoClient = new plivo.Client( process.env.PLIVO_AUTH_ID, process.env.PLIVO_AUTH_TOKEN ); // Decorate the Fastify instance with the Plivo client // Makes `app.plivo` accessible in route handlers app.decorate('plivo', plivoClient); app.log.info('Plivo client initialized successfully.'); } catch (error) { app.log.error('Failed to initialize Plivo client:', error); process.exit(1); // Cannot proceed without a working client } // Decorate Fastify with the sender number for easy access app.decorate('plivoSenderNumber', process.env.PLIVO_SENDER_NUMBER); // --- Register Routes --- // Prefix all routes defined in mms.js with /api/mms app.register(mmsRoutes, { prefix: '/api/mms' }); // --- Health Check Route --- // Good practice for monitoring and load balancers app.get('/health', async (request, reply) => { return { status: 'ok', timestamp: new Date().toISOString() }; }); // --- Start Server --- const start = async () => { try { const port = parseInt(process.env.PORT, 10); const host = process.env.HOST; await app.listen({ port: port, host: host }); // Logger already announces the address } catch (err) { app.log.error(err); process.exit(1); } }; start();app.registerto add the@fastify/rate-limitplugin. It's important to register this before the routes you want to limit.max: Sets the maximum number of requests allowed within thetimeWindow. Adjust this based on expected traffic and Plivo's own rate limits.timeWindow: Defines the duration over which requests are counted (e.g., '1 minute', '1 hour',60000ms).errorResponseBuilder: Customizes the429 Too Many Requestsresponse sent when the limit is hit.enableDraftSpec: Adds standardRateLimit-*headers to responses, which is good practice.Testing Your MMS Service
Now, let's run the server and test the MMS sending endpoint.
Add Run Scripts to
package.json:// package.json snippet { // ... other fields like name, version, main, etc. "scripts": { "start": "node src/app.js", "dev": "NODE_ENV=development node src/app.js | pino-pretty" } // ... dependencies, etc. }start: Runs the application in production mode (default logger).dev: Runs in development mode, settingNODE_ENVand piping logs throughpino-pretty.Run the Server (Development Mode):
npm run dev # or yarn devYou should see output indicating the server is running, similar to:
Test with
curl: Open another terminal window. Replace placeholders with your verified destination number (for trial accounts) or any US/Canada number (for paid accounts), and a publicly accessible media URL.Success Case:
curl -X POST http://localhost:3000/api/mms/send \ -H "Content-Type: application/json" \ -d '{ "to": "+1DESTINATION_NUMBER", "media_urls": ["https://upload.wikimedia.org/wikipedia/commons/thumb/a/a4/Plivo_logo.png/600px-Plivo_logo.png"], "text": "Hello from Fastify and Plivo!" }'Expected Success Response (JSON - using camelCase):
{ "message": "message(s) queued", "messageUuid": ["xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"], "apiId": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy" }You should also see corresponding logs in the server terminal and receive the MMS on the destination phone shortly.
Troubleshooting Common MMS Issues
PLIVO_AUTH_IDandPLIVO_AUTH_TOKENin your.envfile. Ensure they are copied correctly from the Plivo console.srcordstNumber (e.g., 400 Bad Request from Plivo):PLIVO_SENDER_NUMBERis an MMS-enabled number you own on Plivo.srcanddstnumbers are in E.164 format (e.g.,+14151234567). Thetofield validator checks the format (^\\+[1-9]\\d{1,14}$).dstnumber must be verified in the Sandbox Numbers section of the Plivo console.media_urlsmust point to publicly accessible URLs. Plivo's servers need to fetch the media from these URLs. Test the URL in your browser first. Supported file types include JPEG, GIF, PNG (images), MP3 (audio), and MP4 (video). For Canadian carriers, keep MMS attachments smaller than 1 MB to ensure broad compatibility across all networks.max: 100,timeWindow: '1 minute'by default in this guide) or Plivo's own API limits. Plivo enforces a default limit of 300 API requests per 5 seconds for non-call APIs, and supports up to 100 concurrent MMS requests. When limits are exceeded, Plivo responds with HTTP 429 Too Many Requests. The error response indicates when you can retry. Source: Plivo Rate Limits and Plivo MMS Account Rate Limitsapi.plivo.com). Firewalls or network configuration could block requests.200 OK,message: "message(s) queued") only means Plivo accepted the request. It doesn't guarantee delivery to the handset. For delivery confirmation, you need to implement webhook handlers for Plivo's status callbacks (using theurlandmethodparameters in themessages.createcall). This is a more advanced topic.Production Deployment Best Practices
.envfile. Use your hosting provider's mechanism for setting environment variables (e.g., Heroku Config Vars, AWS Systems Manager Parameter Store, Docker environment variables).pm2or run your application within a container (Docker) to handle restarts, clustering (for multi-core utilization), and logging.pm2:npm install -g pm2, thenpm2 start src/app.js --name fastify-plivo-mms/healthendpoint. Monitor application performance (APM tools) and error rates.Plivo MMS Frequently Asked Questions
How do I send MMS with Plivo and Node.js?
To send MMS with Plivo and Node.js, install the Plivo SDK (
npm install plivo), initialize a Plivo client with your Auth ID and Auth Token, and callclient.messages.create()with parameters includingsrc(your Plivo number),dst(recipient in E.164 format),text,media_urls(array of public media URLs), andtype: 'mms'. Plivo supports MMS only within the US and Canada.What file formats does Plivo MMS support?
Plivo MMS supports JPEG, GIF, and PNG for images, MP3 for audio, and MP4 for video. Media files must be hosted on publicly accessible URLs that Plivo servers can fetch. For Canadian carriers, keep MMS attachments smaller than 1 MB to ensure broad compatibility across all networks. You can include up to 10 media URLs per MMS message.
What is E.164 phone number format?
E.164 is the international phone number format standardized by ITU-T. It consists of a plus sign (+) followed by a country code (1-3 digits) and subscriber number, with a maximum of 15 digits total. Example: +14151234567 for a US number. Both source and destination numbers must use E.164 format for Plivo API calls.
Why use Fastify for MMS messaging?
Fastify is a high-performance, low-overhead Node.js web framework ideal for MMS messaging services. It provides built-in JSON schema validation, automatic request/response serialization, extensive plugin ecosystem (including @fastify/rate-limit), and superior performance compared to Express. Fastify's decorator pattern simplifies sharing the Plivo client across routes.
What are Plivo API rate limits?
Plivo enforces a default limit of 300 API requests per 5 seconds for non-call APIs and supports up to 100 concurrent MMS requests. When limits are exceeded, Plivo responds with HTTP 429 Too Many Requests. Implement rate limiting in your Fastify application using @fastify/rate-limit to control costs and prevent abuse.
Can I send MMS to countries outside US and Canada with Plivo?
No. Plivo supports MMS messaging exclusively within the US and Canada. Attempts to send MMS to other countries may fail or be converted to SMS with a media link. You can only send and receive MMS using US or Canadian Plivo phone numbers (local long code, toll-free, or short code).
How do I handle Plivo MMS delivery status?
Plivo's
messages.create()returns a200 OKresponse when messages are queued, not delivered. For delivery confirmation, implement webhook handlers for Plivo's status callbacks by addingurlandmethodparameters to your API call. Plivo will POST delivery status updates (delivered, failed, undelivered) to your webhook endpoint.What Node.js version should I use for Plivo MMS?
Use Node.js 22.x LTS for production applications. Node.js 22 is in Active LTS until October 2025 and Maintenance LTS until April 2027. This version provides full support for modern JavaScript features, security updates, and optimal performance for Fastify and Plivo SDK integration.
Related Resources and Next Steps
Now that you've built a Fastify MMS service with Plivo, consider exploring these related topics:
Wrapping Up
You have now successfully built a Fastify application capable of sending MMS messages using the Plivo Node.js SDK. We covered project setup, secure credential management, API route implementation with validation, error handling, rate limiting, and essential testing procedures.
This provides a solid foundation for integrating MMS capabilities into your Node.js applications. From here, you could expand the service to include status webhooks for delivery tracking, build a frontend interface, or integrate it into a larger communication workflow.