code examples
code examples
How to Send MMS with Plivo in Next.js: Complete 2025 Guide
Learn how to send MMS messages with Plivo API in Next.js. Step-by-step tutorial with TypeScript, App Router, authentication, and production deployment for multimedia messaging.
Send MMS with Plivo, Node.js, and Next.js
Learn how to send MMS messages with Plivo in a Next.js application. This comprehensive tutorial shows you how to build a production-ready MMS sender using the Plivo API, Node.js, and the Next.js App Router. You'll implement multimedia messaging with TypeScript, proper error handling, and secure credential management for US and Canadian markets.
This guide walks you through creating a web interface where users can send MMS (Multimedia Messaging Service) containing text and media attachments. The application uses a Next.js API route that connects to the Plivo Node.js SDK v4.x, supporting JPEGs, GIFs, MP3 audio, and MP4 video files—perfect for marketing campaigns, notifications, and customer engagement.
Prerequisites
Before you start building your MMS application, ensure you have:
- Node.js and npm/yarn: Installed on your development machine (Node.js v18 or later recommended). Download Node.js
- Plivo Account: A registered Plivo account. Sign up for Plivo.
- Plivo Auth ID and Auth Token: Found on your Plivo Console dashboard homepage.
- MMS-Enabled Plivo Phone Number: You need a Plivo phone number capable of sending MMS messages. MMS is currently supported only for US and Canadian numbers (long code, toll-free, and short code). Plivo supports sending MMS to major carriers including AT&T, Verizon, T-Mobile, Rogers, Bell, Fido, Telus, and Wind Canada. You can rent one via the Plivo Console under "Phone Numbers" > "Buy Numbers". Ensure MMS capability is listed for the number.
- Verified Destination Number (Trial Accounts): If using a Plivo trial account, the destination phone number must be verified (sandboxed) in the Plivo Console under "Phone Numbers" > "Sandbox Numbers".
- Code Editor: Such as VS Code.
- Git: For version control (optional but recommended).
Plivo SDK Version: This guide uses Plivo Node.js SDK v4.x (latest: 4.74.0 as of January 2025). Install via npm install plivo.
Supported Media Formats: Plivo supports JPEGs, GIFs, MP3 audio, and MP4 video files for MMS.
What Will You Build in This Tutorial?
Goal: Create a functional Next.js application that sends MMS messages (text + image/GIF/audio/video) via the Plivo API.
Problem Solved: Provides a clear, secure, and scalable way to integrate programmatic MMS sending into a modern web application framework like Next.js.
Technologies Used:
- Next.js: A React framework for building server-side rendered (SSR) and static web applications. Chosen for its robust features, API routes, and developer experience.
- React: For building the user interface.
- Node.js: The runtime environment for Next.js and the Plivo SDK.
- Plivo Node.js SDK: Simplifies interaction with the Plivo REST API.
- Plivo API: The underlying communications API for sending MMS.
System Architecture:
The data flow is as follows:
- The user interacts with a form in their browser (React component in
pages/index.js). - On submission, the frontend sends a POST request containing the recipient number (
to), message text (text), and media URL (mediaUrl) to a Next.js API route (/api/send-mms). - The API route (running server-side on Node.js) receives the request, validates the data, and uses the Plivo Node.js SDK (with Auth ID/Token) to make an authenticated request to the Plivo API.
- The Plivo API processes the request and sends the MMS message to the recipient.
- Plivo returns a response (e.g., message UUID, status) to the API route.
- The API route sends a success or error response back to the frontend.
Expected Outcome: A running Next.js application with a webpage containing a form. Submitting the form successfully sends an MMS message containing the specified text and media to the target phone number.
Setting Up Your Next.js Project
Let's start by creating a new Next.js project and installing the necessary Plivo SDK.
-
Create a Next.js App: Open your terminal and run the following command. Replace
plivo-mms-senderwith your desired project name. We'll use TypeScript for enhanced type safety, but you can opt for JavaScript if preferred.bashnpx create-next-app@latest plivo-mms-sender --typescript --eslint --tailwind --src-dir --app --import-alias "@/*" # Follow the prompts (you can accept defaults)--typescript: Initializes the project with TypeScript.--eslint: Includes ESLint for code linting.--tailwind: Sets up Tailwind CSS for styling (optional, but useful for UI).--src-dir: Creates asrcdirectory for organizing code.--app: Uses the newer App Router (recommended).--import-alias "@/*": Configures path aliases.
-
Navigate to Project Directory:
bashcd plivo-mms-sender -
Install Plivo Node.js SDK: Add the Plivo helper library to your project dependencies.
bashnpm install plivo # or using yarn: # yarn add plivo -
Set Up Environment Variables: Sensitive credentials like API keys should never be hardcoded. We'll use environment variables. Create a file named
.env.localin the root of your project. Important: Add.env.localto your.gitignorefile to prevent committing secrets.-
Create the
.gitignorefile if it doesn't exist or add the following line:text# .gitignore .env*.local -
Create the
.env.localfile:bashtouch .env.local -
Add your Plivo credentials and sender ID (your Plivo number) to
.env.local:dotenv# .env.local # Plivo Credentials - Found on Plivo Console Dashboard PLIVO_AUTH_ID=YOUR_PLIVO_AUTH_ID PLIVO_AUTH_TOKEN=YOUR_PLIVO_AUTH_TOKEN # Your MMS-Enabled Plivo Phone Number (Digits only, e.g., 14155551212) PLIVO_SENDER_ID=YOUR_PLIVO_PHONE_NUMBER_DIGITS_ONLY -
Explanation:
PLIVO_AUTH_ID,PLIVO_AUTH_TOKEN: Used by the Plivo SDK to authenticate API requests. Obtained from the Plivo Console.PLIVO_SENDER_ID: Thesrc(source) phone number for sending MMS. Must be an MMS-enabled number rented from Plivo, provided here as digits only (e.g.,14155551212).- Next.js automatically loads variables prefixed with
NEXT_PUBLIC_into the browser environment. Since our API key usage is server-side only (in the API route), we do not need theNEXT_PUBLIC_prefix, keeping our credentials secure. - Important: After creating or modifying the
.env.localfile, you must restart your Next.js development server (npm run dev) for the changes to take effect.
-
-
Project Structure Overview (Simplified): Your relevant project structure should look something like this:
plaintextplivo-mms-sender/ └── src/ └── app/ ├── api/ │ └── send-mms/ │ └── route.ts # <-- Our API endpoint logic ├── layout.tsx └── page.tsx # <-- Our Frontend UI └── .env.local # <-- Plivo credentials (Git ignored) └── .gitignore └── next.config.mjs └── package.json └── tsconfig.jsonsrc/app/api/send-mms/route.ts: This file will contain the server-side logic to handle requests and interact with the Plivo API.src/app/page.tsx: This will contain the React component for our user interface (the form)..env.local: Stores our sensitive credentials.
Implementing the MMS API Endpoint
Now, let's build the API route that will handle the MMS sending logic using the Next.js App Router.
-
Create the API Route File: Create the directory structure and file
src/app/api/send-mms/route.ts. -
Implement the API Logic: Paste the following code into
src/app/api/send-mms/route.ts:typescript// src/app/api/send-mms/route.ts import { NextRequest, NextResponse } from 'next/server'; import * as plivo from 'plivo'; // Validate environment variables on server start const authId = process.env.PLIVO_AUTH_ID; const authToken = process.env.PLIVO_AUTH_TOKEN; const senderId = process.env.PLIVO_SENDER_ID; if (!authId || !authToken || !senderId) { console.error("Plivo credentials or Sender ID are missing in environment variables."); // In a real app, you might throw an error or handle this more gracefully // For this example, we log and proceed, but requests will fail later. } // Basic regex for E.164-like format (allows optional '+') for destination const phoneRegexDest = /^\+?[1-9]\d{1,14}$/; // Stricter regex for Sender ID from .env (digits only expected) const phoneRegexSrc = /^[1-9]\d{9,14}$/; // Adjust min/max length as needed export async function POST(request: NextRequest) { // Ensure Plivo credentials are loaded correctly within the request context if (!authId || !authToken || !senderId) { return NextResponse.json( { error: 'Server configuration error: Plivo credentials missing.' }, { status: 500 } ); } // Validate Sender ID format from .env.local early if (!phoneRegexSrc.test(senderId)) { console.error(`Invalid PLIVO_SENDER_ID format in .env.local: ${senderId}. Expected digits only (e.g., 14155551212).`); return NextResponse.json( { error: 'Server configuration error: Invalid Sender ID format.' }, { status: 500 } ); } let client: plivo.Client | null = null; try { client = new plivo.Client(authId, authToken); } catch (error) { console.error("Failed to initialize Plivo client:", error); return NextResponse.json( { error: 'Failed to initialize Plivo client.' }, { status: 500 } ); } try { const body = await request.json(); const { to, text, mediaUrl } = body; // --- Input Validation --- if (!to || !text || !mediaUrl) { return NextResponse.json( { error: 'Missing required fields: to, text, mediaUrl' }, { status: 400 } // Bad Request ); } // Basic E.164-like check. Consider using libphonenumber-js for robust validation. if (!phoneRegexDest.test(to)) { return NextResponse.json( { error: 'Invalid "to" phone number format. Use E.164 format (e.g., +14155551234).' }, { status: 400 } ); } // Basic URL format check try { new URL(mediaUrl); } catch (_) { return NextResponse.json( { error: 'Invalid "mediaUrl" format.' }, { status: 400 } ); } // --- End Input Validation --- console.log(`Attempting to send MMS to: ${to} from: ${senderId}`); const response = await client.messages.create( senderId, // src: Your Plivo number (digits only from .env.local) to, // dst: Recipient number (E.164 format from request body) text, // text: Message content from request body { // options type: "mms", media_urls: [mediaUrl] // Media URL from request body } // Plivo SDK v4+ typically handles formatting 'src' if needed, but providing digits is safest. ); console.log("Plivo API Response:", response); // Successfully sent return NextResponse.json( { success: true, message: `MMS queued successfully to ${to}`, // Plivo Node SDK v4+ returns messageUuid in an array, even for single sends. message_uuid: response.messageUuid?.[0] }, { status: 200 } // OK ); } catch (error: any) { console.error("Error sending MMS via Plivo:", error); // Provide more specific feedback if possible let errorMessage = 'Failed to send MMS.'; if (error.message) { errorMessage = `Failed to send MMS: ${error.message}`; } // Plivo errors often have more details in error.response or similar properties. // Consider logging the full error object for debugging. return NextResponse.json( { error: errorMessage }, { status: 500 } // Internal Server Error ); } } // Optional: Handle other methods like GET if needed, otherwise they default to 405 Method Not Allowed export async function GET() { return NextResponse.json({ error: 'Method Not Allowed' }, { status: 405 }); }- Explanation:
- We import
NextRequest,NextResponsefromnext/serverand theplivoSDK. - We read the Plivo credentials and sender ID from
process.env. Crucially, this happens server-side, protecting your keys. We add checks to ensure these variables are present. - The
POSTfunction is the handler for POST requests to/api/send-mms. - It initializes the Plivo
Clientusing the Auth ID and Token. - It parses the incoming JSON request body to get
to,text, andmediaUrl. - Input Validation: It performs basic checks:
- Ensures all required fields are present.
- Validates the format of the
tophone number (E.164-like) andmediaUrl. - Validates the format of the
senderIdfrom.env.local(digits only). - Returns a
400 Bad Requestor500 Internal Server Errorif validation fails.
- It calls
client.messages.create()with:src: Your Plivo number (senderId, digits only).dst: The recipient number (to).text: The message text (text).options: An object specifying the messagetypeas"mms"and providing themedia_urlsas an array containing themediaUrl.
- Error Handling: It uses a
try...catchblock to handle potential errors during the API call (e.g., invalid credentials, network issues, Plivo API errors). It logs the error server-side and returns a500 Internal Server Errorresponse with an error message. - Success Response: If the Plivo API call is successful (doesn't throw an error), it returns a
200 OKresponse with a success message and themessage_uuidprovided by Plivo (accessing the first element of themessageUuidarray). - A basic
GEThandler is added to return405 Method Not Allowedif someone tries to access the endpoint via GET.
- We import
- Explanation:
Building the Frontend Interface
Now, let's create the form in our main page component (src/app/page.tsx) to interact with the API endpoint.
-
Clear Boilerplate and Add Form: Replace the contents of
src/app/page.tsxwith the following code:typescript// src/app/page.tsx 'use client'; // Required for components with hooks (useState, useEffect) import { useState, FormEvent } from 'react'; export default function HomePage() { const [to, setTo] = useState(''); const [text, setText] = useState(''); // Default to a known working GIF for ease of testing const [mediaUrl, setMediaUrl] = useState('https://media.giphy.com/media/tPerToLqZRRFa8/giphy.gif'); const [statusMessage, setStatusMessage] = useState(''); const [isLoading, setIsLoading] = useState(false); const [isError, setIsError] = useState(false); const handleSubmit = async (event: FormEvent<HTMLFormElement>) => { event.preventDefault(); // Prevent default form submission setIsLoading(true); setStatusMessage(''); setIsError(false); try { const response = await fetch('/api/send-mms', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ to, text, mediaUrl }), }); const result = await response.json(); if (!response.ok) { // Handle HTTP errors (4xx, 5xx) throw new Error(result.error || `HTTP error! status: ${response.status}`); } // Success setStatusMessage(`Success: ${result.message} (UUID: ${result.message_uuid})`); // Optionally clear the form on success // setTo(''); // setText(''); // setMediaUrl('https://media.giphy.com/media/tPerToLqZRRFa8/giphy.gif'); // Reset to default or empty } catch (error: any) { console.error('Submission error:', error); setStatusMessage(`Error: ${error.message || 'Failed to send MMS.'}`); setIsError(true); } finally { setIsLoading(false); } }; return ( <main className="flex min-h-screen flex-col items-center justify-center p-8 bg-gray-100"> <div className="w-full max-w-md p-8 space-y-6 bg-white rounded-lg shadow-md"> <h1 className="text-2xl font-bold text-center text-gray-800">Send Plivo MMS</h1> <form onSubmit={handleSubmit} className="space-y-4"> <div> <label htmlFor="to" className="block text-sm font-medium text-gray-700"> To Phone Number: </label> <input type="tel" id="to" value={to} onChange={(e) => setTo(e.target.value)} required placeholder="+14155551234" className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" /> <p className="mt-1 text-xs text-gray-500">Include country code (e.g., +1 for US/Canada).</p> </div> <div> <label htmlFor="text" className="block text-sm font-medium text-gray-700"> Message Text: </label> <textarea id="text" value={text} onChange={(e) => setText(e.target.value)} required rows={3} placeholder="Your message here..." className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" /> </div> <div> <label htmlFor="mediaUrl" className="block text-sm font-medium text-gray-700"> Media URL (Image/GIF): </label> <input type="url" id="mediaUrl" value={mediaUrl} onChange={(e) => setMediaUrl(e.target.value)} required placeholder="https://example.com/image.gif" className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" /> <p className="mt-1 text-xs text-gray-500">Must be a publicly accessible URL.</p> </div> <button type="submit" disabled={isLoading} className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed" > {isLoading ? 'Sending...' : 'Send MMS'} </button> </form> {statusMessage && ( <div className={`mt-4 p-3 rounded-md text-sm ${isError ? 'bg-red-100 text-red-700' : 'bg-green-100 text-green-700'}`}> {statusMessage} </div> )} </div> </main> ); }- Explanation:
'use client': This directive marks the component as a Client Component, necessary because we use React hooks (useState).- State Variables:
useStatehooks manage the form inputs (to,text,mediaUrl), loading state (isLoading), error state (isError), and status messages (statusMessage). handleSubmitFunction:- Prevents the default form submission behavior.
- Sets loading state and clears previous status messages.
- Uses the
fetchAPI to make aPOSTrequest to our/api/send-mmsendpoint. - Sends the form data as a JSON string in the request body.
- Sets the
Content-Typeheader toapplication/json. - Processes the response:
- If the response is not
ok(status code outside 200-299), it throws an error using the message from the API response. - If successful, it updates the status message with success details from the API.
- If the response is not
- Handles errors using a
catchblock, updating the status message accordingly. - Uses a
finallyblock to ensure the loading state is reset regardless of success or failure.
- JSX Form:
- A standard HTML form element with an
onSubmithandler linked tohandleSubmit. - Input fields for "To Phone Number", "Message Text", and "Media URL", bound to their respective state variables using
valueandonChange. Basic HTML5 validation (required,type="tel",type="url") is included. - A submit button that is disabled during loading.
- A status message area that conditionally renders based on
statusMessage, styled differently for success and error messages using Tailwind CSS classes.
- A standard HTML form element with an
- Explanation:
Integrating with Plivo API
This section focuses specifically on the Plivo integration points and best practices for connecting your Next.js application to Plivo's messaging service.
-
Obtaining Credentials (Recap):
- Navigate to the Plivo Console.
- Your Auth ID and Auth Token are displayed prominently on the main dashboard page.
- Important: Treat your Auth Token like a password – keep it secure and do not share it or commit it to version control.
-
Renting/Verifying Phone Number:
- Go to "Phone Numbers" in the Plivo Console.
- Rent Number: Click "Buy Numbers". Search by country (US or Canada for MMS), select features (ensure MMS is checked), choose a number, and complete the purchase.
- Verify Sender ID: Copy the full phone number (e.g.,
14155551212) that you rented and paste it as digits only as the value forPLIVO_SENDER_IDin your.env.localfile. - Verify Destination Number (Trial Accounts Only): Go to "Phone Numbers" > "Sandbox Numbers". Click "Add Sandbox Number", enter the destination phone number you want to test sending to, and follow the verification process (usually involves receiving a call or SMS with a code). Only verified numbers can receive messages from trial accounts.
-
Secure Storage (
.env.local):- As done in Step 1, store
PLIVO_AUTH_ID,PLIVO_AUTH_TOKEN, andPLIVO_SENDER_ID(digits only) in the.env.localfile. - Ensure
.env.localis listed in your.gitignorefile. Next.js automatically makes these variables available server-side viaprocess.env. Remember to restart the dev server after changes.
- As done in Step 1, store
-
SDK Initialization:
-
In
src/app/api/send-mms/route.ts, the Plivo client is initialized securely using the environment variables:typescriptconst authId = process.env.PLIVO_AUTH_ID; const authToken = process.env.PLIVO_AUTH_TOKEN; // ... validation ... client = new plivo.Client(authId, authToken); -
This happens server-side only, protecting your credentials.
-
-
API Call:
- The core interaction happens via
client.messages.create(...)within the API route. The parameters (src,dst,text,type,media_urls) map directly to the Plivo Message API requirements for sending an MMS. Thesrcparameter uses the digits-onlysenderIdfrom.env.local.
- The core interaction happens via
Error Handling and Logging Best Practices
Our current implementation includes basic error handling and logging. Let's refine it for production use.
-
Consistent Error Strategy (API Route):
- Validation Errors: Return
400 Bad Requestwith a clearerrormessage indicating the specific field or format issue (as implemented). - Configuration Errors: If server-side config (like missing env vars or invalid sender ID format) is the issue, return
500 Internal Server Errorand log the specific detail server-side (as implemented). Avoid exposing sensitive config details to the client. - Plivo API Errors: Catch errors from
client.messages.create. Log the full error server-side for debugging. Return a generic500 Internal Server Errorto the client, potentially including a sanitized error message from Plivo if available and safe (e.g.,error.message). - Initialization Errors: Handle errors during
new plivo.Client()– return500.
- Validation Errors: Return
-
Logging (API Route):
- We use
console.log()for basic informational messages (attempting send, Plivo response) andconsole.error()for critical issues (missing credentials, Plivo API errors, validation failures). - Enhancement: For production, use a structured logging library (e.g.,
pino,winston) to output logs in JSON format. This makes them easier to parse, filter, and analyze with log management tools (like Datadog, Logtail, Papertrail). Include request IDs for tracing.
typescript// Example using basic console logging structure console.log(JSON.stringify({ level: 'info', message: 'Attempting to send MMS', data: { to, from: senderId } })); console.error(JSON.stringify({ level: 'error', message: 'Plivo API Error', error: error.message, stack: error.stack, plivoDetails: error.response?.data })); // Log more details if available - We use
-
Retry Mechanisms (Conceptual):
-
Sending an MMS is generally idempotent if using unique identifiers, but Plivo handles retries internally for deliverability to some extent. Implementing client-side retries in our API route for transient network errors or temporary Plivo issues (like
503 Service Unavailable) can improve robustness. -
Strategy: Use a library like
async-retryor implement manually.- Retry only on specific error types (e.g., network errors, 5xx errors from Plivo). Do not retry on 4xx errors (bad request, invalid number).
- Implement exponential backoff (e.g., wait 1s, then 2s, then 4s) with jitter (random variation) to avoid overwhelming the API.
- Set a maximum number of retries (e.g., 3).
-
Setup: Install the library:
bashnpm install async-retry @types/async-retry # or # yarn add async-retry @types/async-retry -
Implementation:
typescript// Conceptual example using async-retry import retry from 'async-retry'; // Potentially move Plivo client initialization inside the retry scope if needed // Inside the POST function, wrap the Plivo call try { const response = await retry( async (bail, attempt) => { // Changed variable name for clarity console.log(`Attempt #${attempt}...`); try { // Ensure client is initialized if needed within retry scope if (!client) throw new Error("Plivo client not initialized"); const plivoResponse = await client.messages.create( senderId, to, text, { type: "mms", media_urls: [mediaUrl] } ); return plivoResponse; // Success! } catch (error: any) { // Don't retry on bad requests or auth errors (4xx) if (error.statusCode && error.statusCode >= 400 && error.statusCode < 500) { bail(new Error(`Non-retriable Plivo error: ${error.statusCode} - ${error.message}`)); return; // Bail out definitively } // For other errors (network, 5xx), throw to trigger retry console.warn(`Retrying attempt ${attempt} due to error: ${error.message}`); throw error; // Throw error to signal retry is needed } }, { retries: 3, // Number of retries factor: 2, // Exponential factor minTimeout: 1000, // Initial timeout 1s randomize: true, // Add jitter } ); // ... handle success ... console.log("Plivo API Response after retries:", response); return NextResponse.json({ /* success response */ }, { status: 200 }); } catch (error: any) { // ... handle final error after all retries failed ... console.error("Failed to send MMS after multiple retries:", error.message); return NextResponse.json({ error: `Failed to send MMS after retries: ${error.message}` }, { status: 500 }); }
-
-
Frontend Error Handling:
- The
handleSubmitfunction insrc/app/page.tsxalready catches errors from thefetchcall and non-OK responses. - It displays user-friendly messages based on the
errorfield from the API response or a generic message. This is good practice.
- The
Frequently Asked Questions About Plivo MMS
What countries does Plivo support for MMS?
Plivo supports MMS only within the US and Canada. You can send MMS messages from US and Canadian long code, toll-free, and short code numbers to major carriers including AT&T, Verizon, T-Mobile, Rogers, Bell, Fido, Telus, and Wind Canada. International MMS outside these two countries is not currently supported.
What media formats does Plivo MMS support?
Plivo MMS supports JPEGs, GIFs, MP3 audio files, and MP4 video files. All media must be accessible via a publicly available URL.
What is the latest version of the Plivo Node.js SDK?
The latest version of the Plivo Node.js SDK is v4.74.0 (as of January 2025). Install it using npm install plivo. The SDK supports TypeScript and can automatically fetch credentials from environment variables.
Do I need to verify phone numbers for trial accounts?
Yes, if using a Plivo trial account, destination phone numbers must be verified (sandboxed) in the Plivo Console under "Phone Numbers" > "Sandbox Numbers" before you can send messages to them.
How do I handle MMS delivery failures?
Implement retry logic with exponential backoff for transient errors (5xx status codes, network errors). Do not retry on 4xx errors (bad requests, invalid numbers). Use the async-retry library and set a maximum of 3 retries with jitter to avoid overwhelming the API.
Can I send MMS from toll-free numbers?
Yes, Plivo supports MMS on toll-free numbers in the US and Canada. Ensure the toll-free number is MMS-enabled when renting it from the Plivo Console.
What is the E.164 format for phone numbers?
E.164 is the international phone number format: +[country code][subscriber number]. For example, a US number would be +14155551234. Plivo requires destination numbers in E.164 format.
How do I secure my Plivo credentials in Next.js?
Store credentials in .env.local without the NEXT_PUBLIC_ prefix to keep them server-side only. Never commit .env.local to version control – add it to .gitignore. Access credentials via process.env in API routes only.
What HTTP methods does the Next.js App Router support?
Next.js App Router route handlers support GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS. Export async functions with these names from route.ts files in the app/api/ directory.
How long does it take for an MMS to be delivered?
MMS delivery times vary by carrier but typically occur within seconds to a few minutes. Use Plivo webhooks to receive delivery status updates and track message delivery in real-time.
Database Integration for Message History
For this specific guide (sending one-off MMS via a form), a database is not strictly required. The state is transient – we take user input, send it to Plivo, and report back.
If you needed to:
- Store message history: Track sent messages, their status (using Plivo webhooks for delivery reports), recipients, etc.
- Manage contacts: Store recipient information.
- Queue messages: Implement a more robust queuing system.
You would typically add:
- Database Choice: PostgreSQL, MySQL, MongoDB, etc.
- ORM/Query Builder: Prisma (recommended for Next.js/TypeScript), TypeORM, Drizzle ORM, or Knex.js.
- Schema Example (Using Prisma):
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Message {
id String @id @default(cuid())
messageUuid String @unique // Plivo's message UUID
to String
from String
text String
mediaUrl String?
status String @default("queued") // queued, sent, delivered, failed
errorMessage String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([status])
@@index([createdAt])
}
model Contact {
id String @id @default(cuid())
phoneNumber String @unique
name String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}-
Setup Steps:
- Install Prisma:
npm install prisma @prisma/client - Initialize:
npx prisma init - Add
DATABASE_URLto.env.local - Run migrations:
npx prisma migrate dev --name init - Generate client:
npx prisma generate
- Install Prisma:
-
Update API Route to Store Messages:
// src/app/api/send-mms/route.ts
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export async function POST(request: NextRequest) {
// ... existing validation code ...
try {
const response = await client.messages.create(
senderId, to, text,
{ type: "mms", media_urls: [mediaUrl] }
);
// Store in database
await prisma.message.create({
data: {
messageUuid: response.messageUuid[0],
to,
from: senderId,
text,
mediaUrl,
status: 'sent'
}
});
return NextResponse.json({
success: true,
message: `MMS queued successfully`,
message_uuid: response.messageUuid[0]
}, { status: 200 });
} catch (error: any) {
// Log failure to database if needed
console.error("Error sending MMS:", error);
return NextResponse.json(
{ error: 'Failed to send MMS.' },
{ status: 500 }
);
}
}- Webhook Handler for Delivery Status:
// src/app/api/webhooks/plivo/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { MessageUUID, Status, ErrorMessage } = body;
// Update message status in database
await prisma.message.update({
where: { messageUuid: MessageUUID },
data: {
status: Status.toLowerCase(),
errorMessage: ErrorMessage || null
}
});
return NextResponse.json({ success: true }, { status: 200 });
} catch (error) {
console.error("Webhook error:", error);
return NextResponse.json({ error: 'Webhook processing failed' }, { status: 500 });
}
}Testing and Deployment
Local Testing
-
Start the Development Server:
bashnpm run devNavigate to
http://localhost:3000 -
Test the Form:
- Enter a verified phone number (E.164 format: +14155551234)
- Add message text
- Use the default GIF URL or provide your own
- Submit and verify the MMS is received
-
Check Console Logs: Review server-side logs for Plivo API responses and any errors.
Production Deployment
Vercel (Recommended for Next.js):
-
Prepare for Deployment:
- Ensure
.env.localis in.gitignore - Push code to GitHub/GitLab
- Create Vercel account: vercel.com
- Ensure
-
Deploy:
- Import project from Git repository
- Add environment variables in Vercel dashboard:
PLIVO_AUTH_IDPLIVO_AUTH_TOKENPLIVO_SENDER_IDDATABASE_URL(if using database)
- Deploy
-
Post-Deployment:
- Test production endpoint
- Configure Plivo webhooks to point to your production URL
- Monitor logs and error rates
Other Platforms:
- AWS Amplify: Similar Git-based deployment
- DigitalOcean App Platform: Node.js application support
- Railway: PostgreSQL + Next.js hosting
- Docker: Create
Dockerfilefor containerized deployment
Security Checklist
- ✅ Environment variables never committed to Git
- ✅ API routes validate all inputs
- ✅ Rate limiting implemented (use
@upstash/ratelimitfor serverless) - ✅ HTTPS enabled in production
- ✅ Webhook endpoints verify Plivo signatures
- ✅ Error messages don't expose sensitive data
Conclusion
You've built a production-ready Next.js application for sending MMS messages via the Plivo API. This implementation includes:
- Secure credential management with environment variables
- Type-safe TypeScript code
- Robust error handling and retry logic
- Input validation for phone numbers and media URLs
- User-friendly React interface with loading states
- Optional database integration for message history
- Deployment-ready architecture
Next Steps:
- Add authentication (NextAuth.js) to restrict access
- Implement message queuing with BullMQ or Inngest
- Add webhook handlers for delivery receipts
- Create admin dashboard to view message history
- Add media file upload instead of URL input
- Implement contact management system
For production applications, consider implementing rate limiting, monitoring with Sentry or LogRocket, and setting up CI/CD pipelines for automated testing and deployment.
Frequently Asked Questions
How to send MMS messages with Next.js?
You can send MMS messages with Next.js by using the Plivo API and Node.js SDK. This involves setting up a Next.js project, installing the Plivo SDK, configuring API credentials, and creating an API route to handle MMS sending logic. The frontend will use a form to collect recipient details, message content, and media URL, then send a POST request to the API route, which interacts with Plivo.
What is the Plivo Node.js SDK used for?
The Plivo Node.js SDK simplifies interaction with the Plivo REST API, making it easier to send MMS messages from your Next.js application. It handles authentication and communication with the Plivo API, allowing you to focus on the application logic rather than low-level API details.
Why use Next.js for sending MMS with Plivo?
Next.js is chosen for its robust features, such as API routes and server-side rendering (SSR), which are beneficial for handling API interactions securely and providing a good developer experience. The API routes simplify backend logic and allow for secure handling of sensitive information, such as API credentials.
When should I verify destination numbers in Plivo?
If you're using a Plivo trial account, you must verify any destination phone numbers before sending MMS messages to them. This is done through the Plivo Console under "Phone Numbers" > "Sandbox Numbers" to ensure compliance with Plivo's trial account restrictions.
Can I use a JavaScript Next.js project with Plivo?
While the guide recommends TypeScript for enhanced type safety, you can use a regular JavaScript Next.js project with Plivo. The core logic and setup will remain similar, with slight differences in type declarations and error handling.
How to set up Plivo credentials in a Next.js project?
Store your Plivo Auth ID, Auth Token, and MMS-enabled Plivo phone number (digits only) as environment variables in a `.env.local` file. This file should be added to your `.gitignore` to avoid committing secrets to version control. Next.js automatically loads these variables server-side via `process.env`.
What is the project structure for a Next.js Plivo MMS sender?
The main parts are `src/app/api/send-mms/route.ts` for API logic, `src/app/page.tsx` for the frontend form, and `.env.local` for storing the credentials securely. The API route handles POST requests containing recipient number, message text, and media URL, sending data to the Plivo API via the Plivo SDK.
How to handle errors when sending Plivo MMS?
The provided code implements error handling for missing credentials, invalid phone numbers, and issues with the Plivo API call. It uses a try-catch block to capture errors from `client.messages.create()` and provides specific feedback to the user, such as a 400 Bad Request for incorrect number formats. Additionally, structured logging can help in managing errors effectively.
What is the role of input validation in Plivo MMS sending?
Input validation is crucial for preventing errors and ensuring the MMS message is sent correctly. The code checks for missing fields (`to`, `text`, `mediaUrl`), validates the format of the destination phone number, and checks for a valid media URL. It returns 400 Bad Request if any issues are found.
How to structure the Plivo API call in Next.js?
The `client.messages.create()` method takes the sender's Plivo number (digits only), recipient number, message text, and an options object. This object includes the message type (`"mms"`) and an array of media URLs (`media_urls`). Ensure your sender's number is MMS-enabled and obtained from the Plivo console.
Why is my Plivo MMS not sending from a trial account?
A common reason is unverified destination numbers. Trial accounts require destination numbers to be added to the Sandbox Numbers list within the Plivo console for testing. Ensure the recipient's number is verified there, and restart your server if you've made changes to the `.env.local` file after starting the Next.js dev server.
How to improve error handling for Plivo MMS in production?
Use a structured logging library like `pino` or `winston` for logging Plivo API responses and errors in a JSON format, making analysis easier. Implement retry mechanisms using libraries like `async-retry` with exponential backoff and jitter for transient errors. Always log the full error details server-side and return a generic `500 Internal Server Error` to the client without revealing sensitive configuration details.