How to Send MMS with Plivo Node.js SDK: Complete Next.js + Supabase Guide
Learn to send MMS messages using Plivo's Node.js SDK in Next.js with Supabase integration. Step-by-step tutorial with code examples, webhooks, error handling, and production deployment best practices.
Learn how to send MMS messages using Plivo's Node.js SDK with Next.js and Supabase. Multimedia Messaging Service (MMS) enables you to send rich media content like images, GIFs, and videos alongside text messages. This comprehensive tutorial guides you through building a production-ready Node.js application that sends MMS messages via the Plivo API while tracking delivery status in Supabase.
When to Send MMS vs. SMS Messages
Use MMS For
Use SMS For
Marketing campaigns with rich media
Transactional messages
Product showcases and demonstrations
Appointment reminders
Event invitations with visual elements
Quick alerts and OTPs
Interactive customer engagement
Time-sensitive text notifications
MMS achieves higher engagement rates due to multimedia content but costs more per message than SMS. Source: SMS vs MMS Marketing Comparison 2025
This tutorial shows you how to build a Next.js API route that accepts recipient details, message text, and media URLs, then uses the Plivo Node.js SDK to send MMS messages programmatically. You'll also learn to track message delivery status in Supabase for monitoring and historical records.
What You'll Learn:
Integrate Plivo's messaging API into Next.js
Handle multimedia content delivery
Implement webhook callbacks for delivery status tracking
Store message logs in Supabase with Row Level Security
Apply production-ready error handling and retry logic
System Architecture:
graph LR
A[User/Client] -- HTTP POST Request --> B(Next.js API Route);
B -- Send MMS Request --> C(Plivo API);
B -- Store Message Log --> D(Supabase Database);
C -- Delivers MMS --> E(Carrier Network);
E -- Delivers MMS --> F(Recipient Phone);
C -- Sends Status Callback --> B;
B -- Update Status --> D;
Plivo Phone Number: An MMS-enabled phone number (available for US and Canadian numbers). Acquire one from the Plivo console under "Phone Numbers" > "Buy Numbers".
Supabase Account: Free account for database hosting. Sign up at https://supabase.com/.
Basic knowledge of Node.js, Next.js, Supabase, and REST APIs.
1. Setting Up Your Next.js Project to Send MMS with Plivo
Initialize your Next.js project and install the necessary dependencies for sending MMS with Plivo and storing data in Supabase.
Create Next.js Project:
Open your terminal and create a new Next.js application with TypeScript support.
Bash
npx create-next-app@latest plivo-mms-sender
cd plivo-mms-sender
When prompted, select:
TypeScript: Yes
ESLint: Yes
Tailwind CSS: Optional (your preference)
src/ directory: Yes
App Router: Yes
Customize default import alias: No
Install Dependencies:
Install plivo for the Plivo Node.js SDK (version 4.74.0 as of January 2025) and @supabase/supabase-js for the Supabase client. The Plivo SDK requires Node.js 5.5 or higher and is fully compatible with Node.js 18.18+ used by Next.js 15. Sources: Plivo Node.js SDK Documentation; Plivo SDK Version Compatibility
Bash
npm install plivo @supabase/supabase-js
Set Up Environment Variables:
Create .env.local in your project root to store sensitive credentials. Never commit this file to version control.
2. Creating Supabase Database Schema for MMS Tracking
Create a Supabase table to track MMS message logs with proper Row Level Security policies.
Create Messages Table:
In your Supabase dashboard, navigate to the SQL Editor and run this SQL:
SQL
CREATE TABLE message_logs (
id UUID PRIMARY KEYDEFAULT gen_random_uuid(),
plivo_message_uuid TEXT UNIQUE,
sender TEXT NOT NULL,
recipient TEXT NOT NULL,
message_text TEXT,
media_urls TEXT[],
status TEXT DEFAULT'queued',
plivo_status_info JSONB,
error_code TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Index for efficient lookupsCREATE INDEX idx_plivo_message_uuid ON message_logs(plivo_message_uuid);
CREATE INDEX idx_status ON message_logs(status);
CREATE INDEX idx_created_at ON message_logs(created_at DESC);
-- Enable Row Level SecurityALTER TABLE message_logs ENABLE ROW LEVEL SECURITY;
-- Policy to allow service role full accessCREATE POLICY "Service role has full access to message_logs"
ON message_logs
FORALLTO service_role
USING (true)
WITHCHECK (true);
-- Optional: Policy for authenticated users to read their own messages-- (Modify based on your authentication structure)CREATE POLICY "Users can read message_logs"
ON message_logs
FORSELECTTO authenticated
USING (true);
-- Function to update updated_at timestampCREATEOR REPLACE FUNCTION update_updated_at_column()
RETURNSTRIGGERAS $$
BEGIN
NEW.updated_at = NOW();
RETURNNEW;
END;
$$ LANGUAGE plpgsql;
-- Trigger to automatically update updated_atCREATETRIGGER update_message_logs_updated_at
BEFORE UPDATEON message_logs
FOREACHROWEXECUTEFUNCTION update_updated_at_column();
Verify Table Creation:
Navigate to the Table Editor in Supabase and confirm the message_logs table exists with proper columns and indexes.
3. Initializing Plivo and Supabase Clients in Node.js
Create utility modules for initializing Supabase and Plivo clients.
Webhook Security: This implementation validates Plivo webhook signatures using V2 signature method with HMAC-SHA256 and nonce validation. For SMS/MMS webhooks, Plivo uses X-Plivo-Signature-V2 and X-Plivo-Signature-V2-Nonce headers. Calculate the signature by concatenating the full callback URL with the nonce value, then sign with HMAC-SHA256 using your Auth Token. Sources: Plivo Webhook Signature Validation; Plivo V3 Signature Documentation
6. Testing Your Plivo MMS API Endpoint
Test your API endpoint using curl or Postman.
Using curl:
Bash
curl -X POST http://localhost:3000/api/send-mms \
-H "Content-Type: application/json" \
-d '{
"destinationNumber": "+14157654321",
"messageText": "Hello from Next.js! Check out this image.",
"mediaUrl": "https://media.giphy.com/media/26gscSULUcfKU7dHq/source.gif"
}'
Success Response (Status 202):
JSON
{"message":"MMS sent successfully","messageId":"a1b2c3d4-e5f6-7890-1234-567890abcdef","plivoMessageUuid":"x1y2z3w4-v5u6-t7s8-r9q0-p1o2n3m4l5k6"}
Verify in Supabase:
Check your message_logs table to confirm the message was logged with status "sent".
7. Production-Ready Error Handling and Retry Logic for MMS
Production applications require robust error handling strategies.
Structured Logging: Replace console.log and console.error with Pino or Winston.
Retry Mechanisms: Implement retry logic for transient errors (5xx status codes) using async-retry. Only retry network errors or Plivo 5xx responses – never 4xx client errors.
Validation Enhancement: Use libphonenumber-js for phone number validation and zod for request schema validation.
Media File Validation: Validate media file size and type before sending to Plivo using a HEAD request:
8. Understanding Plivo MMS Limitations and Media Requirements
Understand media files and carrier-specific requirements for reliable MMS delivery.
Supported Countries: Plivo supports sending MMS via long codes to the US and Canada only. Messages to other countries may fail or fall back to SMS without media. Source: Plivo MMS Documentation, 2025.
Media File Types and Size Limits:
Limit Type
Sending
Receiving
Total size
5 MB
7 MB
Per attachment
2 MB
2 MB
Max attachments
10
10
Message text
1,600 characters
1,600 characters
Supported Formats: JPEG, PNG, GIF, and some video/audio formats.
Carrier-Specific Recommendations:
US carriers: Keep non-JPEG/PNG/GIF files under 600 KB for best compatibility
Canadian carriers: Keep all attachments under 1 MB
Best Practice: Aim for individual files under 600 KB and total message size under 1 MB for optimal delivery.
Media URL Accessibility: The mediaUrl must be publicly accessible without authentication. Plivo servers fetch this URL to attach media. Private or localhost URLs will fail.
Multiple Media Files: Send up to 10 images/GIFs in one MMS by providing an array of URLs in media_urls. Keep total size under 5 MB.
Fallback to SMS: Implement fallback logic to send SMS if MMS fails (e.g., non-MMS-capable number, destination outside US/Canada, or specific error codes).
9. Security Best Practices for Production MMS APIs
Protect your MMS application with these essential security measures.
Secure Credentials: Use .env.local for development and Vercel Environment Variables or AWS Secrets Manager for production. Never commit secrets to version control.
Rate Limiting: Implement rate limiting using Next.js middleware or edge functions:
TypeScript
// src/middleware.tsimport { NextResponse } from'next/server';
importtype { NextRequest } from'next/server';
const rateLimit = newMap<string, { count: number; resetTime: number }>();
exportfunctionmiddleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith('/api/send-mms')) {
const ip = request.ip || 'unknown';
const now = Date.now();
const limit = 10; // requestsconstwindow = 60 * 1000; // 1 minuteconst record = rateLimit.get(ip);
if (record) {
if (now < record.resetTime) {
if (record.count >= limit) {
returnNextResponse.json(
{ error: 'Too many requests' },
{ status: 429 }
);
}
record.count++;
} else {
rateLimit.set(ip, { count: 1, resetTime: now + window });
}
} else {
rateLimit.set(ip, { count: 1, resetTime: now + window });
}
}
returnNextResponse.next();
}
CORS Configuration: The API route includes CORS headers in the OPTIONS handler. In production, replace * with specific domains in Access-Control-Allow-Origin.
Authentication/Authorization: For production APIs, implement JWT-based authentication using NextAuth.js or Supabase Auth:
Webhook Security: Always validate Plivo webhook signatures using HMAC-SHA256 with V2 headers before processing data.
10. Deploying Your Plivo MMS Application to Production
Deploy your MMS application to production:
Vercel Deployment (Recommended for Next.js):
Push your code to GitHub/GitLab
Connect repository to Vercel
Add environment variables in Vercel dashboard
Deploy with automatic HTTPS and edge functions
Environment Variables:
Set all variables from .env.local in your hosting platform
Use separate Plivo accounts for staging and production
Rotate credentials regularly
Supabase Configuration:
Enable database backups
Configure RLS policies for production security
Set up connection pooling for high traffic
Monitor query performance
Webhook URL Configuration:
Update Plivo callback URL to your production domain
Ensure webhook endpoint is publicly accessible
Configure in Plivo console or pass as url parameter when sending
Monitoring and Logging:
Implement error tracking (Sentry, LogRocket)
Set up Supabase monitoring and alerts
Monitor Plivo usage and costs in console
Track message delivery rates
Frequently Asked Questions (FAQ)
How do I send MMS messages using Plivo Node.js SDK in Next.js?
To send MMS with Plivo in Node.js, use the Plivo SDK's client.messages.create() method with type: 'mms' and media_urls parameters. First, install the SDK with npm install plivo, then initialize the client with your Auth ID and Token. Create a Next.js API route that calls this method with your sender number, recipient number, message text, and media URLs to send MMS programmatically.
Which countries support MMS messaging with Plivo?
Plivo supports MMS in the United States and Canada only. MMS to other countries may fail or fall back to SMS without media.
What are Plivo's MMS file size and attachment limits?
Plivo MMS limits: 5 MB total for sending, 7 MB total for receiving, 2 MB per attachment, up to 10 attachments per message, and 1,600 characters for message text. Keep individual files under 600 KB for best delivery.
How do I enable CORS for Next.js API routes when sending MMS?
Add an OPTIONS handler that returns CORS headers. Set Access-Control-Allow-Origin to your frontend domains, specify allowed methods (POST, OPTIONS), and include headers (Content-Type, Authorization).
How do I validate Plivo webhook signatures for MMS delivery status?
Plivo uses V2 signatures for SMS/MMS webhooks with X-Plivo-Signature-V2 and X-Plivo-Signature-V2-Nonce headers. Concatenate your callback URL with the nonce, then sign with HMAC-SHA256 using your Auth Token. Compare the result with the signature header to validate authenticity.
How do I store MMS logs in Supabase with Row Level Security?
Create a table with RLS enabled and appropriate policies. Use the Supabase service role key for server-side operations that need to bypass RLS. Create policies that allow the service role full access while restricting client access based on your authentication requirements.
Why is my Plivo MMS message failing or not being delivered?
Common issues: invalid or non-MMS-enabled sender number, media URL not publicly accessible, file size exceeding limits, unsupported format, destination outside US/Canada, or insufficient balance. Check Plivo Debug Logs for details.
Can I send multiple images or media files in one MMS message?
Yes. Send up to 10 media attachments in one MMS by providing an array of URLs in media_urls. Keep total size under 5 MB.
What Node.js version is required for Next.js 15?
Next.js 15 requires Node.js 18.18 or later. The Plivo Node.js SDK is compatible with Node.js 5.5 and higher, making it fully compatible with Next.js 15 requirements.
How do I deploy a Next.js MMS application with Plivo to Vercel?
Push your code to a Git repository, connect it to Vercel, configure environment variables in the Vercel dashboard (including Plivo and Supabase credentials), and deploy. Vercel automatically handles HTTPS, edge functions, and provides webhook URLs for Plivo callbacks.
Use the Plivo Node.js SDK and Express.js to create a server with an endpoint that accepts recipient details, message text, and media URL. The server then uses the Plivo SDK to send the MMS message via the Plivo API. This setup allows you to create a robust MMS sending service within your Node.js application.
You'll need Node.js and npm installed, a Plivo account, an MMS-enabled Plivo phone number (US or Canadian), and a basic understanding of Node.js, Express, and REST APIs. The Plivo number is essential for sending MMS, and you can purchase one through the Plivo console.
The /health endpoint is a best practice for monitoring service availability. It allows for easy verification that your MMS sending service is running and responding to requests. This aids in troubleshooting and ensures the application is operational.
Implement a try...catch block around the client.messages.create call to handle errors returned by the Plivo API. Use the error.statusCode property to customize the error response sent back to the client and implement appropriate logging using a structured logger like Winston or Pino. More advanced strategies include retries for specific error codes.
Use the E.164 format, which includes a plus sign (+) followed by the country code and national number (e.g., +14155551212). While basic regex validation is shown, it's recommended to use a library like libphonenumber-js in a production environment for better handling of international phone numbers and format variations.
Plivo supports sending multiple images or GIFs within one MMS message. Provide an array of media URLs using the media_urls option when calling client.messages.create(). Remember that each file has size limits and the total message size should be kept under carrier limits, typically around 1MB.
Environment variables (.env file) store sensitive information like Plivo Auth ID, Auth Token, and sender number securely. Never hardcode these credentials. The dotenv library helps load these variables into your application's environment, protecting them from exposure in version control.
Several methods are available, including input validation (discussed earlier), rate limiting using express-rate-limit to prevent abuse, authentication (API keys, JWT) to restrict access, and validating Plivo webhook signatures for callbacks to ensure they are authentic.
The mediaUrl field provides the URL of the image, GIF, or video you want to include in your MMS message. This URL must be publicly accessible by Plivo's servers to fetch and include in the message. Ensure the URL is valid and points to the correct file.
Use media_ids if you pre-upload your media to Plivo's servers. Obtain the media_id from the Plivo console under Messaging > MMS Media Upload after the upload is complete. This can be useful when dealing with media that is not publicly accessible via URL.
Plivo primarily supports MMS sending to US and Canadian numbers via long codes. MMS to other countries may not be supported or could fall back to SMS. Refer to Plivo's documentation for the most up-to-date list of supported countries and regions.
Implement a webhook endpoint (e.g., /plivo-status-callback) in your Express app. Configure this webhook URL either in the url parameter during the client.messages.create() call or on the Plivo Application settings. Plivo will then send updates to your webhook for various message statuses.
A MessageLog model should store message details like plivoMessageUuid, sender, recipient, text, media URLs, status (queued, sent, delivered, failed), Plivo status callback info, error codes, timestamps, and optionally user IDs.
The .gitignore file is crucial for preventing sensitive information (like API credentials in the .env file) and unnecessary files (like the node_modules folder) from being tracked by version control. This protects your credentials and keeps your repository clean.
Learn how to send MMS messages using Plivo's Node.js SDK with Next.js and Supabase. Multimedia Messaging Service (MMS) enables you to send rich media content like images, GIFs, and videos alongside text messages. This comprehensive tutorial guides you through building a production-ready Node.js application that sends MMS messages via the Plivo API while tracking delivery status in Supabase.
When to Send MMS vs. SMS Messages
MMS achieves higher engagement rates due to multimedia content but costs more per message than SMS. Source: SMS vs MMS Marketing Comparison 2025
This tutorial shows you how to build a Next.js API route that accepts recipient details, message text, and media URLs, then uses the Plivo Node.js SDK to send MMS messages programmatically. You'll also learn to track message delivery status in Supabase for monitoring and historical records.
What You'll Learn:
System Architecture:
Prerequisites:
1. Setting Up Your Next.js Project to Send MMS with Plivo
Initialize your Next.js project and install the necessary dependencies for sending MMS with Plivo and storing data in Supabase.
Create Next.js Project: Open your terminal and create a new Next.js application with TypeScript support.
npx create-next-app@latest plivo-mms-sender cd plivo-mms-senderWhen prompted, select:
src/directory: YesInstall Dependencies: Install
plivofor the Plivo Node.js SDK (version 4.74.0 as of January 2025) and@supabase/supabase-jsfor the Supabase client. The Plivo SDK requires Node.js 5.5 or higher and is fully compatible with Node.js 18.18+ used by Next.js 15. Sources: Plivo Node.js SDK Documentation; Plivo SDK Version CompatibilitySet Up Environment Variables: Create
.env.localin your project root to store sensitive credentials. Never commit this file to version control.Filename:
.env.localPLIVO_AUTH_ID&PLIVO_AUTH_TOKEN: Find these on your Plivo console dashboard overview page.PLIVO_SENDER_NUMBER: Your MMS-enabled Plivo phone number with country code (e.g.,+14151234567).Update
.gitignoreFile: Next.js automatically includes.env.localin.gitignore, ensuring your credentials aren't tracked by Git.Basic Project Structure: Your Next.js project structure should look like:
2. Creating Supabase Database Schema for MMS Tracking
Create a Supabase table to track MMS message logs with proper Row Level Security policies.
Create Messages Table: In your Supabase dashboard, navigate to the SQL Editor and run this SQL:
CREATE TABLE message_logs ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), plivo_message_uuid TEXT UNIQUE, sender TEXT NOT NULL, recipient TEXT NOT NULL, message_text TEXT, media_urls TEXT[], status TEXT DEFAULT 'queued', plivo_status_info JSONB, error_code TEXT, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- Index for efficient lookups CREATE INDEX idx_plivo_message_uuid ON message_logs(plivo_message_uuid); CREATE INDEX idx_status ON message_logs(status); CREATE INDEX idx_created_at ON message_logs(created_at DESC); -- Enable Row Level Security ALTER TABLE message_logs ENABLE ROW LEVEL SECURITY; -- Policy to allow service role full access CREATE POLICY "Service role has full access to message_logs" ON message_logs FOR ALL TO service_role USING (true) WITH CHECK (true); -- Optional: Policy for authenticated users to read their own messages -- (Modify based on your authentication structure) CREATE POLICY "Users can read message_logs" ON message_logs FOR SELECT TO authenticated USING (true); -- Function to update updated_at timestamp CREATE OR REPLACE FUNCTION update_updated_at_column() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql; -- Trigger to automatically update updated_at CREATE TRIGGER update_message_logs_updated_at BEFORE UPDATE ON message_logs FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();Verify Table Creation: Navigate to the Table Editor in Supabase and confirm the
message_logstable exists with proper columns and indexes.3. Initializing Plivo and Supabase Clients in Node.js
Create utility modules for initializing Supabase and Plivo clients.
src/lib/supabase.ts:import { createClient } from '@supabase/supabase-js'; const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; const supabaseServiceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY!; if (!supabaseUrl || !supabaseServiceRoleKey) { throw new Error('Missing Supabase environment variables'); } // Use service role key for server-side operations that bypass RLS export const supabaseAdmin = createClient(supabaseUrl, supabaseServiceRoleKey, { auth: { autoRefreshToken: false, persistSession: false } }); // Database types export interface MessageLog { id: string; plivo_message_uuid: string | null; sender: string; recipient: string; message_text: string | null; media_urls: string[]; status: string; plivo_status_info: any; error_code: string | null; created_at: string; updated_at: string; }src/lib/plivo.ts:import plivo from 'plivo'; const PLIVO_AUTH_ID = process.env.PLIVO_AUTH_ID; const PLIVO_AUTH_TOKEN = process.env.PLIVO_AUTH_TOKEN; const PLIVO_SENDER_NUMBER = process.env.PLIVO_SENDER_NUMBER; if (!PLIVO_AUTH_ID || !PLIVO_AUTH_TOKEN || !PLIVO_SENDER_NUMBER) { throw new Error('Missing Plivo environment variables'); } // Initialize Plivo client once and export export const plivoClient = new plivo.Client(PLIVO_AUTH_ID, PLIVO_AUTH_TOKEN); export const senderNumber = PLIVO_SENDER_NUMBER;4. Building the Next.js API Route to Send MMS via Plivo
Create the API endpoint that receives requests to send MMS messages via Plivo and logs them to Supabase.
src/app/api/send-mms/route.ts:import { NextRequest, NextResponse } from 'next/server'; import { plivoClient, senderNumber } from '@/lib/plivo'; import { supabaseAdmin } from '@/lib/supabase'; // Enable CORS for client-side requests export async function OPTIONS(request: NextRequest) { return new NextResponse(null, { status: 200, headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization', }, }); } export async function POST(request: NextRequest) { try { const body = await request.json(); const { destinationNumber, messageText, mediaUrl } = body; // --- Basic Request Validation --- if (!destinationNumber || !messageText || !mediaUrl) { return NextResponse.json( { error: 'Missing required fields: destinationNumber, messageText, mediaUrl' }, { status: 400 } ); } // E.164 format validation if (!/^\+\d{10,15}$/.test(destinationNumber)) { return NextResponse.json( { error: 'Invalid destinationNumber format. Use E.164 format (e.g., +14157654321).' }, { status: 400 } ); } // URL validation try { new URL(mediaUrl); } catch (_) { return NextResponse.json( { error: 'Invalid mediaUrl format.' }, { status: 400 } ); } // Message text length validation (1,600 character limit for MMS) if (messageText.length > 1600) { return NextResponse.json( { error: 'Message text exceeds 1,600 character limit for MMS.' }, { status: 400 } ); } // Create initial message log in Supabase const { data: messageLog, error: dbError } = await supabaseAdmin .from('message_logs') .insert({ sender: senderNumber, recipient: destinationNumber, message_text: messageText, media_urls: [mediaUrl], status: 'queued' }) .select() .single(); if (dbError) { console.error('Database error:', dbError); return NextResponse.json( { error: 'Failed to create message log', details: dbError.message }, { status: 500 } ); } // Send MMS via Plivo try { const response = await plivoClient.messages.create( senderNumber, destinationNumber, messageText, { type: 'mms', media_urls: [mediaUrl], // Optional: Configure callback URL for status updates // url: `${process.env.NEXT_PUBLIC_APP_URL}/api/plivo-callback` } ); // Update message log with Plivo UUID await supabaseAdmin .from('message_logs') .update({ plivo_message_uuid: response.messageUuid[0], status: 'sent' }) .eq('id', messageLog.id); return NextResponse.json({ message: 'MMS sent successfully', messageId: messageLog.id, plivoMessageUuid: response.messageUuid[0] }, { status: 202 }); } catch (plivoError: any) { console.error('Plivo error:', plivoError); // Update message log with error await supabaseAdmin .from('message_logs') .update({ status: 'failed', error_code: plivoError.statusCode?.toString(), plivo_status_info: { error: plivoError.message } }) .eq('id', messageLog.id); let statusCode = 500; let errorMessage = 'Failed to send MMS'; if (plivoError.statusCode) { switch (plivoError.statusCode) { case 400: statusCode = 400; errorMessage = plivoError.message || 'Bad Request (invalid number or media URL)'; break; case 401: statusCode = 401; errorMessage = 'Authentication failed'; break; case 500: case 503: statusCode = 503; errorMessage = 'Plivo service unavailable'; break; default: if (plivoError.statusCode >= 400 && plivoError.statusCode < 600) { statusCode = plivoError.statusCode; errorMessage = plivoError.message || `Plivo API error (${plivoError.statusCode})`; } } } return NextResponse.json( { error: errorMessage, details: plivoError.message }, { status: statusCode } ); } } catch (error: any) { console.error('Unexpected error:', error); return NextResponse.json( { error: 'Internal server error', details: error.message }, { status: 500 } ); } }Key Features:
OPTIONShandler enables Cross-Origin Resource Sharing for browser requests5. Implementing Plivo Webhook Handler for MMS Delivery Status
Create an API route to receive delivery status callbacks from Plivo and update Supabase.
src/app/api/plivo-callback/route.ts:import { NextRequest, NextResponse } from 'next/server'; import { supabaseAdmin } from '@/lib/supabase'; import crypto from 'crypto'; // Validate Plivo webhook signature function validatePlivoSignature( url: string, nonce: string, signature: string, authToken: string ): boolean { const message = url + nonce; const expectedSignature = crypto .createHmac('sha256', authToken) .update(message) .digest('base64'); return signature === expectedSignature; } export async function POST(request: NextRequest) { try { // Get signature headers const signature = request.headers.get('X-Plivo-Signature-V2'); const nonce = request.headers.get('X-Plivo-Signature-V2-Nonce'); if (!signature || !nonce) { return NextResponse.json( { error: 'Missing signature headers' }, { status: 401 } ); } // Validate signature const url = request.url; const authToken = process.env.PLIVO_AUTH_TOKEN!; if (!validatePlivoSignature(url, nonce, signature, authToken)) { return NextResponse.json( { error: 'Invalid signature' }, { status: 401 } ); } // Parse callback data const body = await request.json(); const { MessageUUID, Status, ErrorCode } = body; if (!MessageUUID) { return NextResponse.json( { error: 'Missing MessageUUID' }, { status: 400 } ); } // Update message log in Supabase const { error } = await supabaseAdmin .from('message_logs') .update({ status: Status?.toLowerCase() || 'unknown', error_code: ErrorCode || null, plivo_status_info: body }) .eq('plivo_message_uuid', MessageUUID); if (error) { console.error('Failed to update message log:', error); return NextResponse.json( { error: 'Failed to update message log' }, { status: 500 } ); } return NextResponse.json({ success: true }, { status: 200 }); } catch (error: any) { console.error('Webhook error:', error); return NextResponse.json( { error: 'Internal server error' }, { status: 500 } ); } }Webhook Security: This implementation validates Plivo webhook signatures using V2 signature method with HMAC-SHA256 and nonce validation. For SMS/MMS webhooks, Plivo uses
X-Plivo-Signature-V2andX-Plivo-Signature-V2-Nonceheaders. Calculate the signature by concatenating the full callback URL with the nonce value, then sign with HMAC-SHA256 using your Auth Token. Sources: Plivo Webhook Signature Validation; Plivo V3 Signature Documentation6. Testing Your Plivo MMS API Endpoint
Test your API endpoint using curl or Postman.
Using
curl:curl -X POST http://localhost:3000/api/send-mms \ -H "Content-Type: application/json" \ -d '{ "destinationNumber": "+14157654321", "messageText": "Hello from Next.js! Check out this image.", "mediaUrl": "https://media.giphy.com/media/26gscSULUcfKU7dHq/source.gif" }'Success Response (Status 202):
{ "message": "MMS sent successfully", "messageId": "a1b2c3d4-e5f6-7890-1234-567890abcdef", "plivoMessageUuid": "x1y2z3w4-v5u6-t7s8-r9q0-p1o2n3m4l5k6" }Verify in Supabase: Check your
message_logstable to confirm the message was logged with status "sent".7. Production-Ready Error Handling and Retry Logic for MMS
Production applications require robust error handling strategies.
Structured Logging: Replace
console.logandconsole.errorwith Pino or Winston.Retry Mechanisms: Implement retry logic for transient errors (5xx status codes) using
async-retry. Only retry network errors or Plivo 5xx responses – never 4xx client errors.Validation Enhancement: Use
libphonenumber-jsfor phone number validation andzodfor request schema validation.Media File Validation: Validate media file size and type before sending to Plivo using a HEAD request:
async function validateMediaFile(url: string): Promise<{ valid: boolean; error?: string }> { try { const response = await fetch(url, { method: 'HEAD' }); const contentLength = parseInt(response.headers.get('content-length') || '0'); const contentType = response.headers.get('content-type'); // Individual file limit: 2 MB if (contentLength > 2 * 1024 * 1024) { return { valid: false, error: 'File exceeds 2 MB limit' }; } // Check supported MIME types const supportedTypes = ['image/jpeg', 'image/png', 'image/gif', 'video/mp4']; if (contentType && !supportedTypes.includes(contentType)) { return { valid: false, error: 'Unsupported file type' }; } return { valid: true }; } catch (error) { return { valid: false, error: 'Unable to validate media file' }; } }8. Understanding Plivo MMS Limitations and Media Requirements
Understand media files and carrier-specific requirements for reliable MMS delivery.
Supported Countries: Plivo supports sending MMS via long codes to the US and Canada only. Messages to other countries may fail or fall back to SMS without media. Source: Plivo MMS Documentation, 2025.
Media File Types and Size Limits:
Supported Formats: JPEG, PNG, GIF, and some video/audio formats.
Carrier-Specific Recommendations:
Sources: Plivo MMS Size Limits Support Article; Plivo Send Message API Documentation
Media URL Accessibility: The
mediaUrlmust be publicly accessible without authentication. Plivo servers fetch this URL to attach media. Private or localhost URLs will fail.Multiple Media Files: Send up to 10 images/GIFs in one MMS by providing an array of URLs in
media_urls. Keep total size under 5 MB.Fallback to SMS: Implement fallback logic to send SMS if MMS fails (e.g., non-MMS-capable number, destination outside US/Canada, or specific error codes).
9. Security Best Practices for Production MMS APIs
Protect your MMS application with these essential security measures.
Secure Credentials: Use
.env.localfor development and Vercel Environment Variables or AWS Secrets Manager for production. Never commit secrets to version control.Input Validation and Sanitization:
libphonenumber-jsfor E.164 validationzodfor request body schema validationRate Limiting: Implement rate limiting using Next.js middleware or edge functions:
// src/middleware.ts import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; const rateLimit = new Map<string, { count: number; resetTime: number }>(); export function middleware(request: NextRequest) { if (request.nextUrl.pathname.startsWith('/api/send-mms')) { const ip = request.ip || 'unknown'; const now = Date.now(); const limit = 10; // requests const window = 60 * 1000; // 1 minute const record = rateLimit.get(ip); if (record) { if (now < record.resetTime) { if (record.count >= limit) { return NextResponse.json( { error: 'Too many requests' }, { status: 429 } ); } record.count++; } else { rateLimit.set(ip, { count: 1, resetTime: now + window }); } } else { rateLimit.set(ip, { count: 1, resetTime: now + window }); } } return NextResponse.next(); }CORS Configuration: The API route includes CORS headers in the
OPTIONShandler. In production, replace*with specific domains inAccess-Control-Allow-Origin.Authentication/Authorization: For production APIs, implement JWT-based authentication using NextAuth.js or Supabase Auth:
import { createServerClient } from '@supabase/ssr'; export async function POST(request: NextRequest) { const supabase = createServerClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, { cookies: { get: (name) => request.cookies.get(name)?.value, }, } ); const { data: { user } } = await supabase.auth.getUser(); if (!user) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } // Proceed with MMS sending... }Webhook Security: Always validate Plivo webhook signatures using HMAC-SHA256 with V2 headers before processing data.
10. Deploying Your Plivo MMS Application to Production
Deploy your MMS application to production:
Vercel Deployment (Recommended for Next.js):
Environment Variables:
.env.localin your hosting platformSupabase Configuration:
Webhook URL Configuration:
urlparameter when sendingMonitoring and Logging:
Frequently Asked Questions (FAQ)
How do I send MMS messages using Plivo Node.js SDK in Next.js?
To send MMS with Plivo in Node.js, use the Plivo SDK's
client.messages.create()method withtype: 'mms'andmedia_urlsparameters. First, install the SDK withnpm install plivo, then initialize the client with your Auth ID and Token. Create a Next.js API route that calls this method with your sender number, recipient number, message text, and media URLs to send MMS programmatically.Which countries support MMS messaging with Plivo?
Plivo supports MMS in the United States and Canada only. MMS to other countries may fail or fall back to SMS without media.
What are Plivo's MMS file size and attachment limits?
Plivo MMS limits: 5 MB total for sending, 7 MB total for receiving, 2 MB per attachment, up to 10 attachments per message, and 1,600 characters for message text. Keep individual files under 600 KB for best delivery.
How do I enable CORS for Next.js API routes when sending MMS?
Add an
OPTIONShandler that returns CORS headers. SetAccess-Control-Allow-Originto your frontend domains, specify allowed methods (POST, OPTIONS), and include headers (Content-Type, Authorization).How do I validate Plivo webhook signatures for MMS delivery status?
Plivo uses V2 signatures for SMS/MMS webhooks with
X-Plivo-Signature-V2andX-Plivo-Signature-V2-Nonceheaders. Concatenate your callback URL with the nonce, then sign with HMAC-SHA256 using your Auth Token. Compare the result with the signature header to validate authenticity.How do I store MMS logs in Supabase with Row Level Security?
Create a table with RLS enabled and appropriate policies. Use the Supabase service role key for server-side operations that need to bypass RLS. Create policies that allow the service role full access while restricting client access based on your authentication requirements.
Why is my Plivo MMS message failing or not being delivered?
Common issues: invalid or non-MMS-enabled sender number, media URL not publicly accessible, file size exceeding limits, unsupported format, destination outside US/Canada, or insufficient balance. Check Plivo Debug Logs for details.
Can I send multiple images or media files in one MMS message?
Yes. Send up to 10 media attachments in one MMS by providing an array of URLs in
media_urls. Keep total size under 5 MB.What Node.js version is required for Next.js 15?
Next.js 15 requires Node.js 18.18 or later. The Plivo Node.js SDK is compatible with Node.js 5.5 and higher, making it fully compatible with Next.js 15 requirements.
How do I deploy a Next.js MMS application with Plivo to Vercel?
Push your code to a Git repository, connect it to Vercel, configure environment variables in the Vercel dashboard (including Plivo and Supabase credentials), and deploy. Vercel automatically handles HTTPS, edge functions, and provides webhook URLs for Plivo callbacks.
Related Resources