How to Send SMS with Plivo in Next.js Using NextAuth (2025 Guide)
Learn how to send SMS messages using Plivo API in Next.js 15 with NextAuth authentication. Step-by-step tutorial covering protected API routes, session management, error handling, and production best practices.
Build a Next.js application with NextAuth authentication to send SMS messages via the Plivo API. Learn how to implement authenticated API routes, protect SMS endpoints with session management, and build production-ready SMS features using Next.js 15 App Router or Pages Router.
By the end of this guide, you'll have a functional Next.js application with NextAuth-protected API endpoints that send SMS messages programmatically, plus the knowledge to integrate this capability into larger applications.
Project Overview and Goals
Goal: Build a Next.js application with NextAuth authentication that accepts a destination phone number and message text, then uses the Plivo API to send an SMS message through a protected API route.
Problem Solved: Enables authenticated applications to programmatically send SMS notifications, alerts, verification codes, or other messages with proper session management and security controls.
Technologies Used:
Next.js 15: A React framework for building full-stack web applications with built-in routing, API routes, and server-side rendering capabilities. Latest stable version is 15.5 (released January 2025) which includes App Router with React Server Components and Turbopack builds in beta.
NextAuth.js (v4): Complete open-source authentication solution for Next.js applications. Supports OAuth, JWT, database sessions, and is designed for serverless environments. Version 4.24.x is compatible with Next.js 15 with some considerations for App Router usage.
Plivo Node.js SDK (v4.x): Official library provided by Plivo to interact with their communication APIs from Node.js applications. Latest stable version as of January 2025 is available via npm install plivo. Supports SMS, voice calls, and WhatsApp messaging (Plivo Node.js SDK).
Plivo API: The third-party service used for actual SMS delivery.
Node.js 18.17 or later: Required for Next.js 15. Download from nodejs.org. Check version with node --version. Note: Node.js 18 reaches end-of-life on April 30, 2025; consider using Node.js 20 LTS or later for new projects.
npm, yarn, or pnpm: Package manager included with Node.js. Next.js 15 supports all three.
A Plivo Account: Sign up at plivo.com. A trial account is sufficient to start.
A Plivo Phone Number: You need an SMS-enabled Plivo phone number to send messages, especially to the US and Canada. Purchase one from the Plivo console (Phone Numbers > Buy Numbers). Alternatively, for some regions, you might register an Alphanumeric Sender ID.
Plivo Auth ID and Auth Token: Found on the dashboard of your Plivo console after logging in. Store securely in environment variables.
Basic understanding of React, Next.js, and TypeScript (optional but recommended).
Familiarity with REST APIs and authentication concepts.
Final Outcome: A running Next.js application with NextAuth authentication and a protected /api/send-sms endpoint that successfully sends SMS messages via Plivo when called by authenticated users with valid credentials and parameters.
Important: Never commit .env.local to version control. Use environment variables in your hosting provider (Vercel, AWS, etc.) for production.
2. Configuring NextAuth Authentication for SMS Endpoints
Set up NextAuth to handle authentication across your application.
Create NextAuth Configuration (src/lib/auth.ts):
TypeScript
// src/lib/auth.tsimport { NextAuthOptions } from"next-auth";
importCredentialsProviderfrom"next-auth/providers/credentials";
// Define your NextAuth configurationexportconstauthOptions: NextAuthOptions = {
providers: [
CredentialsProvider({
name: "Credentials",
credentials: {
username: { label: "Username", type: "text" },
password: { label: "Password", type: "password" }
},
asyncauthorize(credentials) {
// Replace this with your actual authentication logic// This is a simple example - DO NOT use in productionif (credentials?.username === "demo" && credentials?.password === "demo123") {
return {
id: "1",
name: "Demo User",
email: "demo@example.com"
};
}
returnnull;
}
})
],
session: {
strategy: "jwt"
},
pages: {
signIn: "/auth/signin",
},
callbacks: {
asyncjwt({ token, user }) {
if (user) {
token.id = user.id;
}
return token;
},
asyncsession({ session, token }) {
if (session.user) {
session.user.id = token.idasstring;
}
return session;
}
},
secret: process.env.NEXTAUTH_SECRET,
};
Note: The credentials provider shown here demonstrates the concept. For production applications, integrate with a proper authentication provider (OAuth, database-backed authentication, etc.). See NextAuth providers documentation for options.
Create NextAuth API Route Handler (src/app/api/auth/[...nextauth]/route.ts):
This route handles all NextAuth requests including sign in, sign out, and callback operations at /api/auth/* endpoints.
3. Building the Protected Plivo SMS API Route
Create the protected API endpoint that sends SMS messages via Plivo.
Create SMS API Route (src/app/api/send-sms/route.ts):
TypeScript
// src/app/api/send-sms/route.tsimport { NextRequest, NextResponse } from"next/server";
import { getServerSession } from"next-auth";
import { authOptions } from"@/lib/auth";
import plivo from"plivo";
// Initialize Plivo client// Credentials are loaded from environment variablesconst plivoClient = new plivo.Client(
process.env.PLIVO_AUTH_ID!,
process.env.PLIVO_AUTH_TOKEN!
);
// E.164 phone number validation regex// Format: + followed by 1-15 digits (country code + subscriber number)// Source: https://www.twilio.com/docs/glossary/what-e164constE164_REGEX = /^\+[1-9]\d{1,14}$/;
exportasyncfunctionPOST(request: NextRequest) {
try {
// 1. Check authenticationconst session = awaitgetServerSession(authOptions);
if (!session) {
returnNextResponse.json(
{ error: "Unauthorized. Sign in to send SMS." },
{ status: 401 }
);
}
// 2. Parse and validate request bodyconst body = await request.json();
const { to, text } = body;
// Validate required fieldsif (!to || !text) {
returnNextResponse.json(
{ error: "Missing required fields: 'to' and 'text'." },
{ status: 400 }
);
}
// Validate phone number format (E.164)if (!E164_REGEX.test(to)) {
returnNextResponse.json(
{
error: "Invalid 'to' phone number format. Use E.164 format (e.g., +12025551234).",
format: "E.164 format: +[country code][subscriber number]"
},
{ status: 400 }
);
}
// Validate message textif (typeof text !== "string" || text.trim().length === 0) {
returnNextResponse.json(
{ error: "Invalid 'text' field. Must be a non-empty string." },
{ status: 400 }
);
}
if (text.length > 1600) {
returnNextResponse.json(
{ error: "Message text exceeds maximum length of 1600 characters." },
{ status: 400 }
);
}
// 3. Verify Plivo credentials are configuredif (!process.env.PLIVO_AUTH_ID || !process.env.PLIVO_AUTH_TOKEN || !process.env.PLIVO_SENDER_ID) {
console.error("Missing Plivo environment variables");
returnNextResponse.json(
{ error: "SMS service configuration error. Contact support." },
{ status: 500 }
);
}
// 4. Send SMS via Plivoconsole.log(`Sending SMS to ${to} from user ${session.user?.email}`);
const response = await plivoClient.messages.create(
process.env.PLIVO_SENDER_ID!, // src: Sender ID or Plivo number
to, // dst: Destination number in E.164 format
text // text: The content of the SMS message
);
console.log("Plivo API Response:", response);
// 5. Return success responsereturnNextResponse.json({
success: true,
message: "SMS sent successfully.",
details: {
messageUuid: response.messageUuid,
apiId: response.apiId
}
}, { status: 200 });
} catch (error: any) {
// Log detailed error server-sideconsole.error("Error sending SMS via Plivo:", {
error: error.message,
stack: error.stack,
response: error.response?.data
});
// Return generic error to client (don't expose internal details)returnNextResponse.json(
{
error: "Failed to send SMS. Try again later.",
// Include error code if available for debugging
...(error.response?.status && { statusCode: error.response.status })
},
{ status: 500 }
);
}
}
Explanation:
Authentication Check: Uses getServerSession() from NextAuth to verify authentication before processing requests. Returns 401 Unauthorized if no valid session exists.
Input Validation:
Validates required fields (to, text)
Validates phone number format using E.164 standard regex: ^\+[1-9]\d{1,14}$ (ITU E.164 standard)
Validates message text is non-empty and under 1600 characters
Plivo Client Initialization: Creates a Plivo client instance with credentials from environment variables
SMS Sending: Calls plivoClient.messages.create() with sender ID, destination number, and message text
Error Handling: Catches and logs errors server-side, returns generic error messages to client to avoid exposing internal details
4. Creating a React Frontend for SMS Sending
Create a basic UI to test your SMS sending functionality.
The application will be available at http://localhost:3000
Test Authentication:
Click "Sign In" on the home page
Use the demo credentials (username: demo, password: demo123)
You should be redirected back to the home page, now showing the SMS form
Send a Test SMS:
Enter a valid phone number in E.164 format (e.g., +14155551234)
Note: For Plivo trial accounts, this number must be verified in your Plivo Console under Phone Numbers > Sandbox Numbers
Enter your message text
Click "Send SMS"
Check for success message and verify SMS delivery on the destination device
Expected API Responses:
Success (200 OK):
JSON
{"success":true,"message":"SMS sent successfully.","details":{"messageUuid":["xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"],"apiId":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}}
Unauthorized (401):
JSON
{"error":"Unauthorized. Sign in to send SMS."}
Validation Error (400 Bad Request):
JSON
{"error":"Invalid 'to' phone number format. Use E.164 format (e.g., +12025551234).","format":"E.164 format: +[country code][subscriber number]"}
Server Error (500 Internal Server Error):
JSON
{"error":"Failed to send SMS. Try again later."}
6. Plivo Account Setup and Configuration
Plivo Credentials and Setup:
Auth ID & Auth Token: Required for all API authentication. Found at Plivo Console Dashboard.
Sender ID Configuration:
US/Canada: Must use a Plivo phone number (long code or Toll-Free) enabled for SMS. Purchase from Phone Numbers > Buy Numbers in the Plivo Console. For US-based applications, you may also need 10DLC registration for application-to-person messaging.
Other Regions: Can use a Plivo number or pre-approved Alphanumeric Sender ID (e.g., "MyApp"). Regulations vary by country (Plivo SMS pricing page has country-specific details).
Format: Always use E.164 format for phone numbers: +[country code][subscriber number] (e.g., +14155551212).
Trial Account Limitations:
Plivo trial accounts can only send SMS to verified phone numbers
Never commit .env.local or hardcode credentials in source code.
9. SMS Best Practices: Character Encoding and Compliance
Character Encoding:
GSM 03.38 (7-bit): Standard SMS character set, 160 characters per segment. Includes basic Latin characters, numbers, and common symbols.
Unicode (UCS-2): Required for emojis, non-Latin scripts (Arabic, Chinese, etc.), and special characters. Reduces capacity to 70 characters per segment.
Automatic Handling: Plivo automatically detects encoding and segments messages appropriately. See Plivo's encoding guide for details.
Multipart Messages (Concatenation):
Messages exceeding single segment limits are automatically split
GSM: 153 chars per segment (7 chars reserved for concatenation headers)
The international standard for phone numbers (ITU E.164):
Format:+[country code][subscriber number]
Length: Maximum 15 digits (including country code)
Examples:
US: +14155551234 (country code 1)
UK: +442071234567 (country code 44)
Brazil: +551155256325 (country code 55)
Validation Regex:^\+[1-9]\d{1,14}$ (starts with +, followed by 1-15 digits, no leading zero)
Sender ID Regulations (Country-Specific):
India: Requires registered Alphanumeric Sender IDs. Unregistered numbers may be replaced with generic codes like VM-PLVS. Contact Plivo support for registration.
United States: Requires 10DLC registration for application-to-person (A2P) messaging. See Plivo's 10DLC guide.
European Union: Sender ID regulations vary by country. Some require pre-registration.
NextAuth Session Issues: Ensure NEXTAUTH_SECRET is set and NEXTAUTH_URL matches your application URL.
Debugging Tips:
Check Server Logs: Run npm run dev and watch console output for detailed error messages
Plivo Console: Check Logs section in Plivo Console for API call history and errors
Test Authentication: Use /api/auth/session endpoint to verify NextAuth is working
Verify Environment Variables: Add console.log to verify env vars are loaded (remove before production)
Test Phone Number Format: Use online E.164 validators to verify phone number format
Production Considerations:
Enable HTTPS (required for NextAuth callbacks)
Set up proper error monitoring (Sentry, Datadog)
Implement proper logging with log aggregation
Configure rate limiting on both API and Plivo levels
Set up delivery status webhooks for tracking
Implement message queuing for high-volume scenarios
Regular security audits of authentication flow
Frequently Asked Questions (FAQ)
How do I send SMS with Plivo in Next.js 15?
Install the Plivo Node.js SDK (npm install plivo) and create an API route at app/api/send-sms/route.ts. Initialize the Plivo client with your Auth ID and Token from environment variables, then call client.messages.create() with the sender number, destination number (E.164 format), and message text. For Next.js 15 App Router, use NextRequest and NextResponse for request/response handling.
What is the difference between Next.js API routes and NextAuth protected routes?
Next.js API routes are serverless functions that handle HTTP requests at /api/* endpoints. NextAuth protected routes add authentication middleware that checks for valid user sessions before allowing access to the API endpoint. Use getServerSession() from next-auth in Next.js 13-15 App Router to verify authentication status before processing SMS requests.
How much does it cost to send SMS with Plivo?
Plivo charges per SMS segment based on the destination country. US SMS costs approximately $0.0070 per message for long codes and $0.0072 for toll-free numbers (January 2025 pricing). Messages exceeding 160 characters (GSM-7) or 70 characters (Unicode) split into multiple segments, with each segment billed separately. Check Plivo's official pricing page for current rates and volume discounts.
What is E.164 phone number format and why does Plivo require it?
E.164 is the international standard for phone numbers, formatted as + followed by country code and subscriber number (e.g., +14155551212 for US, +442071234567 for UK). Plivo requires E.164 format to ensure accurate message routing across global telecommunications networks. The format allows up to 15 digits total and excludes spaces, dashes, or parentheses. Always validate phone numbers with the regex ^\+[1-9]\d{1,14}$ before calling the Plivo API.
How do I protect my Plivo SMS endpoint with NextAuth?
Install NextAuth (npm install next-auth) and configure authentication providers in app/api/auth/[...nextauth]/route.ts. In your SMS API route, import getServerSession from next-auth, call it with your auth options, and return a 401 Unauthorized response if no session exists. This ensures only authenticated users can trigger SMS sending. See NextAuth documentation for App Router integration details.
Can I use Plivo with Next.js App Router and Server Components?
Yes, Plivo works seamlessly with Next.js 15 App Router and Server Components. Create API routes in the app/api directory as Route Handlers, which run on the server side. Server Components cannot directly call the Plivo API (they don't handle POST requests), so use Server Actions or API routes to send SMS messages from your Next.js application. The Plivo Node.js SDK is fully compatible with Next.js serverless functions.
What are the Plivo trial account limitations for SMS?
Plivo trial accounts can only send SMS to verified phone numbers that you manually add in the Plivo Console under Phone Numbers > Sandbox Numbers. Trial accounts have limited credits (typically $10) and may display a trial message prefix on outgoing SMS. Upgrade to a paid account to remove these restrictions and send to any phone number globally.
How do I handle SMS delivery failures in Next.js?
Implement error handling with try-catch blocks around client.messages.create() calls and check for specific Plivo error codes (401 for authentication, 400 for invalid numbers, 402 for insufficient funds). Log errors to monitoring services like Sentry, return appropriate HTTP status codes to clients, and optionally implement retry logic with exponential backoff for transient failures (429 rate limits, 5xx server errors). Do not retry authentication failures or invalid parameters.
What is the best way to store Plivo credentials in Next.js?
Store Plivo credentials in .env.local for development (automatically ignored by Git) and use environment variables in your hosting provider for production. Never hardcode Auth ID or Auth Token in source code. Access credentials server-side only using process.env.PLIVO_AUTH_ID and process.env.PLIVO_AUTH_TOKEN. For enhanced security, use secret management services like Vercel Environment Variables, AWS Secrets Manager, or HashiCorp Vault.
How do I send SMS verification codes with Plivo and NextAuth?
Create a Next.js API route that generates a random 6-digit code, stores it in your database (or cache like Redis) with a 10-minute expiration, and sends it via Plivo to the user's phone number. When the user submits the code, verify it matches the stored value and hasn't expired. Integrate this with NextAuth's credentials provider to enable SMS-based two-factor authentication (2FA). See NextAuth credentials provider documentation for implementation details.
Related Resources: Next.js SMS Integration & Authentication
Master Next.js SMS integration with Plivo and NextAuth to build production-ready authenticated messaging features. Implement proper error handling, session management, and security best practices for reliable SMS delivery in your applications.
Use the Plivo Node.js SDK and Express.js to create an API endpoint that handles sending SMS messages. This involves initializing the Plivo client with your credentials, then using the client.messages.create() method to send messages via the Plivo API. Ensure you have the necessary dependencies installed (npm install express plivo dotenv).
The Plivo Node.js SDK is a library that simplifies interaction with the Plivo communications API from your Node.js applications. It provides methods for sending SMS messages, making calls, and other communication functions, abstracting away the low-level API details.
Plivo uses the E.164 format (e.g., +14155551212) for phone numbers to ensure global compatibility and accurate routing of messages. This standardized format includes the country code and full phone number, eliminating ambiguity.
Use an Alphanumeric Sender ID (e.g., "MyApp") as your message sender when allowed by the destination country's regulations. Check Plivo's documentation as rules vary by country. In some regions, it's an alternative to using a phone number.
Yes, Plivo allows sending bulk SMS messages. You can use their API's built-in method by joining multiple destination numbers with '<' or send messages in parallel using Promise.all for greater control, although the former is generally more network-efficient.
Initialize a Node.js project, install Express, Plivo, and dotenv, then set up environment variables for your Plivo credentials. You'll also need to create a Plivo account, buy a Plivo number if sending to certain regions, and obtain your API keys.
Dotenv loads environment variables from a .env file into process.env, allowing you to store sensitive Plivo API credentials (Auth ID, Auth Token) securely, separate from your codebase. This crucial for security and should never be committed to version control.
A 400 error often means an invalid request. Verify your 'to' number is in E.164 format and both 'to' and 'text' fields are correctly supplied in your API call. Also, make sure your 'from' number (Sender ID) is valid.
Implement retry mechanisms when you encounter transient network errors (timeouts) or specific Plivo errors indicating temporary issues (like rate limiting). Use exponential backoff to wait progressively longer between retries.
Catch Plivo errors in a try...catch block and implement logging with enough context to understand the issue. Do not expose internal error details to the client; instead, log those server-side and return a generic error message to the client. Consider using dedicated logging libraries like Winston or Pino and integrate with error tracking services.
Optimize performance when sending many messages by utilizing Plivo's bulk send feature (joining numbers with '<') or making parallel API calls using Promise.all. Ensure Node.js resource limits are appropriate for your workload.
A database schema allows storing SMS logs, including recipient, sender, message, status, and any errors, creating an audit trail. This enables tracking message history, managing opt-outs, and analyzing delivery success rates.
Secure your setup by validating inputs, implementing rate limiting to prevent abuse, protecting your API endpoints with authentication or API keys, and storing Plivo credentials securely using environment variables or a secrets manager. Always use HTTPS in production.
Check common problems: invalid credentials, trial account limits, insufficient funds, incorrect 'to' and 'from' formats, rate limits, or network connectivity. Plivo's console logs can provide valuable information. Ensure your sender ID complies with destination country rules.
GSM encoding is the standard 7-bit character set for SMS, allowing 160 characters per segment. Using characters outside GSM (like emojis or some non-Latin scripts) requires Unicode, reducing the segment length to 70 characters. Plivo handles this but be aware of potential cost implications for longer Unicode messages.
Build a Next.js application with NextAuth authentication to send SMS messages via the Plivo API. Learn how to implement authenticated API routes, protect SMS endpoints with session management, and build production-ready SMS features using Next.js 15 App Router or Pages Router.
By the end of this guide, you'll have a functional Next.js application with NextAuth-protected API endpoints that send SMS messages programmatically, plus the knowledge to integrate this capability into larger applications.
Project Overview and Goals
Goal: Build a Next.js application with NextAuth authentication that accepts a destination phone number and message text, then uses the Plivo API to send an SMS message through a protected API route.
Problem Solved: Enables authenticated applications to programmatically send SMS notifications, alerts, verification codes, or other messages with proper session management and security controls.
Technologies Used:
npm install plivo. Supports SMS, voice calls, and WhatsApp messaging (Plivo Node.js SDK).System Architecture:
Prerequisites:
node --version. Note: Node.js 18 reaches end-of-life on April 30, 2025; consider using Node.js 20 LTS or later for new projects.Phone Numbers>Buy Numbers). Alternatively, for some regions, you might register an Alphanumeric Sender ID.Final Outcome: A running Next.js application with NextAuth authentication and a protected
/api/send-smsendpoint that successfully sends SMS messages via Plivo when called by authenticated users with valid credentials and parameters.Related Guides: If you're also working with other frameworks, check out our guides on implementing SMS in Express.js or explore bulk SMS sending strategies for high-volume scenarios.
1. Setting Up Your Next.js Project with Plivo SDK
Initialize your Next.js project and install the necessary dependencies.
Create Next.js Project: Open your terminal and use
create-next-appto scaffold a new Next.js application:When prompted, select these options:
src/directory? Yes (for better organization)cd plivo-sms-nextauthInstall Dependencies: Install the Plivo Node.js SDK and NextAuth for authentication:
Current versions (as of January 2025):
plivo: Latest available via npm (GitHub: plivo/plivo-node)next-auth: ^4.24.x (v4 for Next.js 13-15, NextAuth.js docs)next: ^15.5.0 (included via create-next-app, Next.js 15.5 release)Project Structure: Your Next.js project should have this structure:
Configure
.gitignore: Ensure.env.localis in your.gitignore(Next.js includes this by default):Configure Environment Variables (
.env.local): Create a.env.localfile in your project root. Next.js automatically loads variables from this file.PLIVO_AUTH_ID: Your Plivo Account Auth ID for API authentication (found at https://console.plivo.com/dashboard/)PLIVO_AUTH_TOKEN: Your Plivo Account Auth Token for API authenticationPLIVO_SENDER_ID: The Plivo phone number (E.164 format:+14155551212) or approved Alphanumeric Sender IDNEXTAUTH_SECRET: Random string for encrypting JWT tokens. Generate with:openssl rand -base64 32NEXTAUTH_URL: Your application URL (http://localhost:3000 for development)Auth IDandAuth Tokenare displayed on the dashboardPLIVO_SENDER_ID: Navigate toPhone Numbers→Buy Numbersto purchase an SMS-enabled number. Use E.164 format (e.g., +14155551212)Important: Never commit
.env.localto version control. Use environment variables in your hosting provider (Vercel, AWS, etc.) for production.2. Configuring NextAuth Authentication for SMS Endpoints
Set up NextAuth to handle authentication across your application.
Create NextAuth Configuration (
src/lib/auth.ts):// src/lib/auth.ts import { NextAuthOptions } from "next-auth"; import CredentialsProvider from "next-auth/providers/credentials"; // Define your NextAuth configuration export const authOptions: NextAuthOptions = { providers: [ CredentialsProvider({ name: "Credentials", credentials: { username: { label: "Username", type: "text" }, password: { label: "Password", type: "password" } }, async authorize(credentials) { // Replace this with your actual authentication logic // This is a simple example - DO NOT use in production if (credentials?.username === "demo" && credentials?.password === "demo123") { return { id: "1", name: "Demo User", email: "demo@example.com" }; } return null; } }) ], session: { strategy: "jwt" }, pages: { signIn: "/auth/signin", }, callbacks: { async jwt({ token, user }) { if (user) { token.id = user.id; } return token; }, async session({ session, token }) { if (session.user) { session.user.id = token.id as string; } return session; } }, secret: process.env.NEXTAUTH_SECRET, };Note: The credentials provider shown here demonstrates the concept. For production applications, integrate with a proper authentication provider (OAuth, database-backed authentication, etc.). See NextAuth providers documentation for options.
Create NextAuth API Route Handler (
src/app/api/auth/[...nextauth]/route.ts):// src/app/api/auth/[...nextauth]/route.ts import NextAuth from "next-auth"; import { authOptions } from "@/lib/auth"; const handler = NextAuth(authOptions); export { handler as GET, handler as POST };This route handles all NextAuth requests including sign in, sign out, and callback operations at
/api/auth/*endpoints.3. Building the Protected Plivo SMS API Route
Create the protected API endpoint that sends SMS messages via Plivo.
Create SMS API Route (
src/app/api/send-sms/route.ts):// src/app/api/send-sms/route.ts import { NextRequest, NextResponse } from "next/server"; import { getServerSession } from "next-auth"; import { authOptions } from "@/lib/auth"; import plivo from "plivo"; // Initialize Plivo client // Credentials are loaded from environment variables const plivoClient = new plivo.Client( process.env.PLIVO_AUTH_ID!, process.env.PLIVO_AUTH_TOKEN! ); // E.164 phone number validation regex // Format: + followed by 1-15 digits (country code + subscriber number) // Source: https://www.twilio.com/docs/glossary/what-e164 const E164_REGEX = /^\+[1-9]\d{1,14}$/; export async function POST(request: NextRequest) { try { // 1. Check authentication const session = await getServerSession(authOptions); if (!session) { return NextResponse.json( { error: "Unauthorized. Sign in to send SMS." }, { status: 401 } ); } // 2. Parse and validate request body const body = await request.json(); const { to, text } = body; // Validate required fields if (!to || !text) { return NextResponse.json( { error: "Missing required fields: 'to' and 'text'." }, { status: 400 } ); } // Validate phone number format (E.164) if (!E164_REGEX.test(to)) { return NextResponse.json( { error: "Invalid 'to' phone number format. Use E.164 format (e.g., +12025551234).", format: "E.164 format: +[country code][subscriber number]" }, { status: 400 } ); } // Validate message text if (typeof text !== "string" || text.trim().length === 0) { return NextResponse.json( { error: "Invalid 'text' field. Must be a non-empty string." }, { status: 400 } ); } if (text.length > 1600) { return NextResponse.json( { error: "Message text exceeds maximum length of 1600 characters." }, { status: 400 } ); } // 3. Verify Plivo credentials are configured if (!process.env.PLIVO_AUTH_ID || !process.env.PLIVO_AUTH_TOKEN || !process.env.PLIVO_SENDER_ID) { console.error("Missing Plivo environment variables"); return NextResponse.json( { error: "SMS service configuration error. Contact support." }, { status: 500 } ); } // 4. Send SMS via Plivo console.log(`Sending SMS to ${to} from user ${session.user?.email}`); const response = await plivoClient.messages.create( process.env.PLIVO_SENDER_ID!, // src: Sender ID or Plivo number to, // dst: Destination number in E.164 format text // text: The content of the SMS message ); console.log("Plivo API Response:", response); // 5. Return success response return NextResponse.json({ success: true, message: "SMS sent successfully.", details: { messageUuid: response.messageUuid, apiId: response.apiId } }, { status: 200 }); } catch (error: any) { // Log detailed error server-side console.error("Error sending SMS via Plivo:", { error: error.message, stack: error.stack, response: error.response?.data }); // Return generic error to client (don't expose internal details) return NextResponse.json( { error: "Failed to send SMS. Try again later.", // Include error code if available for debugging ...(error.response?.status && { statusCode: error.response.status }) }, { status: 500 } ); } }Explanation:
getServerSession()from NextAuth to verify authentication before processing requests. Returns 401 Unauthorized if no valid session exists.to,text)^\+[1-9]\d{1,14}$(ITU E.164 standard)plivoClient.messages.create()with sender ID, destination number, and message text4. Creating a React Frontend for SMS Sending
Create a basic UI to test your SMS sending functionality.
Update Home Page (
src/app/page.tsx):// src/app/page.tsx "use client"; import { useSession, signIn, signOut } from "next-auth/react"; import { useState } from "react"; export default function Home() { const { data: session, status } = useSession(); const [to, setTo] = useState(""); const [text, setText] = useState(""); const [loading, setLoading] = useState(false); const [result, setResult] = useState<{ success?: boolean; message?: string; error?: string } | null>(null); const handleSendSMS = async (e: React.FormEvent) => { e.preventDefault(); setLoading(true); setResult(null); try { const response = await fetch("/api/send-sms", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ to, text }), }); const data = await response.json(); if (response.ok) { setResult({ success: true, message: data.message }); setTo(""); setText(""); } else { setResult({ success: false, error: data.error }); } } catch (error) { setResult({ success: false, error: "Network error. Try again." }); } finally { setLoading(false); } }; if (status === "loading") { return ( <main className="flex min-h-screen items-center justify-center"> <p>Loading…</p> </main> ); } return ( <main className="flex min-h-screen flex-col items-center justify-center p-24"> <div className="w-full max-w-md space-y-8"> <div className="text-center"> <h1 className="text-3xl font-bold">Plivo SMS Sender</h1> <p className="mt-2 text-gray-600">Next.js + NextAuth + Plivo</p> </div> {!session ? ( <div className="space-y-4 text-center"> <p>Sign in to send SMS messages.</p> <button onClick={() => signIn()} className="rounded-md bg-blue-600 px-4 py-2 text-white hover:bg-blue-700" > Sign In </button> </div> ) : ( <div className="space-y-6"> <div className="rounded-lg border p-4"> <p className="text-sm text-gray-600">Signed in as</p> <p className="font-medium">{session.user?.email}</p> <button onClick={() => signOut()} className="mt-2 text-sm text-red-600 hover:text-red-700" > Sign Out </button> </div> <form onSubmit={handleSendSMS} className="space-y-4"> <div> <label htmlFor="to" className="block text-sm font-medium text-gray-700"> Recipient Phone Number (E.164 format) </label> <input type="tel" id="to" value={to} onChange={(e) => setTo(e.target.value)} placeholder="+14155551234" className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2" required /> <p className="mt-1 text-xs text-gray-500"> Format: +[country code][number] </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)} placeholder="Enter your message here" rows={4} className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2" required /> <p className="mt-1 text-xs text-gray-500"> {text.length}/1600 characters </p> </div> <button type="submit" disabled={loading} className="w-full rounded-md bg-green-600 px-4 py-2 text-white hover:bg-green-700 disabled:bg-gray-400" > {loading ? "Sending…" : "Send SMS"} </button> </form> {result && ( <div className={`rounded-md p-4 ${ result.success ? "bg-green-50 text-green-800" : "bg-red-50 text-red-800" }`} > {result.message || result.error} </div> )} </div> )} </div> </main> ); }Add Session Provider to Root Layout (
src/app/layout.tsx):// src/app/layout.tsx import "./globals.css"; import { SessionProvider } from "@/components/SessionProvider"; export const metadata = { title: "Plivo SMS Sender - Next.js", description: "Send SMS with Plivo, Next.js, and NextAuth", }; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <html lang="en"> <body> <SessionProvider>{children}</SessionProvider> </body> </html> ); }Create Session Provider Component (
src/components/SessionProvider.tsx):// src/components/SessionProvider.tsx "use client"; import { SessionProvider as NextAuthSessionProvider } from "next-auth/react"; export function SessionProvider({ children }: { children: React.ReactNode }) { return <NextAuthSessionProvider>{children}</NextAuthSessionProvider>; }5. Testing Your Plivo SMS Integration
Start the Development Server:
The application will be available at
http://localhost:3000Test Authentication:
demo, password:demo123)Send a Test SMS:
+14155551234)Phone Numbers>Sandbox NumbersExpected API Responses:
Success (200 OK):
{ "success": true, "message": "SMS sent successfully.", "details": { "messageUuid": ["xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"], "apiId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" } }Unauthorized (401):
{ "error": "Unauthorized. Sign in to send SMS." }Validation Error (400 Bad Request):
{ "error": "Invalid 'to' phone number format. Use E.164 format (e.g., +12025551234).", "format": "E.164 format: +[country code][subscriber number]" }Server Error (500 Internal Server Error):
{ "error": "Failed to send SMS. Try again later." }6. Plivo Account Setup and Configuration
Plivo Credentials and Setup:
Phone Numbers>Buy Numbersin the Plivo Console. For US-based applications, you may also need 10DLC registration for application-to-person messaging.+[country code][subscriber number](e.g.,+14155551212).Trial Account Limitations:
Phone Numbers>Sandbox NumbersSMS Pricing:
According to Plivo's US SMS pricing (as of January 2025):
Pricing varies by destination country. Check Plivo's pricing page for international rates.
7. Implementing Error Handling and Retry Logic
Enhanced Error Handling:
Implement more robust error handling for production applications:
Specific Error Types: Plivo SDK errors include status codes and error messages. Handle common cases:
401 Unauthorized: Invalid credentials400 Bad Request: Invalid destination number or parameters402 Payment Required: Insufficient account balance429 Too Many Requests: Rate limiting5xx Server Errors: Plivo service issuesStructured Logging: Use a logging library like
winstonorpinofor production:// Example enhanced logging import pino from 'pino'; const logger = pino({ level: process.env.NODE_ENV === 'production' ? 'info' : 'debug', }); logger.info({ action: 'send_sms', destination: to, userId: session.user?.id, timestamp: new Date().toISOString() }, 'SMS send attempt');Error Tracking: Integrate services like Sentry or Datadog for production error monitoring.
Retry Logic:
Implement exponential backoff for transient failures:
async function sendSmsWithRetry( client: plivo.Client, src: string, dst: string, text: string, maxRetries: number = 3 ) { let lastError; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const response = await client.messages.create(src, dst, text); return { success: true, data: response }; } catch (error: any) { lastError = error; const statusCode = error.response?.status; // Only retry on server errors (5xx) or rate limits (429) const isRetryable = statusCode >= 500 || statusCode === 429; if (!isRetryable || attempt === maxRetries) { throw error; } // Exponential backoff: 1s, 2s, 4s const delay = Math.pow(2, attempt - 1) * 1000; console.log(`Retry attempt ${attempt}/${maxRetries} after ${delay}ms`); await new Promise(resolve => setTimeout(resolve, delay)); } } throw lastError; }Note: Do not retry authentication failures (401), invalid parameters (400), or insufficient funds (402) – these require manual intervention.
8. Securing Your SMS API with Rate Limiting
Rate Limiting:
Protect your API from abuse using rate limiting. Install
@upstash/ratelimitfor serverless-friendly rate limiting:// src/lib/ratelimit.ts import { Ratelimit } from "@upstash/ratelimit"; import { Redis } from "@upstash/redis"; // Create rate limiter (requires Upstash Redis) export const ratelimit = new Ratelimit({ redis: Redis.fromEnv(), limiter: Ratelimit.slidingWindow(10, "15 m"), // 10 requests per 15 minutes analytics: true, });Then in your API route:
// In send-sms route.ts, before authentication check const identifier = request.ip ?? "anonymous"; const { success, limit, reset, remaining } = await ratelimit.limit(identifier); if (!success) { return NextResponse.json( { error: "Too many requests. Try again later." }, { status: 429 } ); }Input Sanitization:
Always validate and sanitize inputs, even for SMS text:
zodfor type-safe validationSecrets Management:
For production deployments:
Never commit
.env.localor hardcode credentials in source code.9. SMS Best Practices: Character Encoding and Compliance
Character Encoding:
Multipart Messages (Concatenation):
E.164 Phone Number Format:
The international standard for phone numbers (ITU E.164):
+[country code][subscriber number]+14155551234(country code 1)+442071234567(country code 44)+551155256325(country code 55)^\+[1-9]\d{1,14}$(starts with +, followed by 1-15 digits, no leading zero)Sender ID Regulations (Country-Specific):
VM-PLVS. Contact Plivo support for registration.Opt-Out Handling:
In the US, TCPA regulations require honoring opt-out requests (e.g., "STOP" replies):
10. Optimizing SMS Performance for Production
Connection Management:
Bulk SMS Sending:
For sending to multiple recipients:
// Plivo's bulk method - most efficient const destinations = ['+14155551212', '+14155551213']; const destinationString = destinations.join('<'); await plivoClient.messages.create( process.env.PLIVO_SENDER_ID!, destinationString, "Bulk message text" );Parallel Processing:
For different messages to different recipients:
const messages = [ { to: '+14155551212', text: 'Message 1' }, { to: '+14155551213', text: 'Message 2' } ]; const results = await Promise.all( messages.map(msg => plivoClient.messages.create( process.env.PLIVO_SENDER_ID!, msg.to, msg.text ) ) );Monitoring:
11. Troubleshooting Common Plivo SMS Issues
Common Issues:
PLIVO_AUTH_IDorPLIVO_AUTH_TOKEN. Verify credentials in Plivo Console and.env.local.Phone Numbers>Sandbox Numbersin Plivo Console.+[country code][number].PLIVO_SENDER_IDis a purchased Plivo number or registered Alphanumeric ID.ECONNREFUSED,ETIMEDOUT– Check network connectivity toapi.plivo.com. Verify firewalls allow outbound HTTPS.NEXTAUTH_SECRETis set andNEXTAUTH_URLmatches your application URL.Debugging Tips:
npm run devand watch console output for detailed error messagesLogssection in Plivo Console for API call history and errors/api/auth/sessionendpoint to verify NextAuth is workingProduction Considerations:
Frequently Asked Questions (FAQ)
How do I send SMS with Plivo in Next.js 15?
Install the Plivo Node.js SDK (
npm install plivo) and create an API route atapp/api/send-sms/route.ts. Initialize the Plivo client with your Auth ID and Token from environment variables, then callclient.messages.create()with the sender number, destination number (E.164 format), and message text. For Next.js 15 App Router, useNextRequestandNextResponsefor request/response handling.What is the difference between Next.js API routes and NextAuth protected routes?
Next.js API routes are serverless functions that handle HTTP requests at
/api/*endpoints. NextAuth protected routes add authentication middleware that checks for valid user sessions before allowing access to the API endpoint. UsegetServerSession()fromnext-authin Next.js 13-15 App Router to verify authentication status before processing SMS requests.How much does it cost to send SMS with Plivo?
Plivo charges per SMS segment based on the destination country. US SMS costs approximately $0.0070 per message for long codes and $0.0072 for toll-free numbers (January 2025 pricing). Messages exceeding 160 characters (GSM-7) or 70 characters (Unicode) split into multiple segments, with each segment billed separately. Check Plivo's official pricing page for current rates and volume discounts.
What is E.164 phone number format and why does Plivo require it?
E.164 is the international standard for phone numbers, formatted as
+followed by country code and subscriber number (e.g.,+14155551212for US,+442071234567for UK). Plivo requires E.164 format to ensure accurate message routing across global telecommunications networks. The format allows up to 15 digits total and excludes spaces, dashes, or parentheses. Always validate phone numbers with the regex^\+[1-9]\d{1,14}$before calling the Plivo API.How do I protect my Plivo SMS endpoint with NextAuth?
Install NextAuth (
npm install next-auth) and configure authentication providers inapp/api/auth/[...nextauth]/route.ts. In your SMS API route, importgetServerSessionfromnext-auth, call it with your auth options, and return a 401 Unauthorized response if no session exists. This ensures only authenticated users can trigger SMS sending. See NextAuth documentation for App Router integration details.Can I use Plivo with Next.js App Router and Server Components?
Yes, Plivo works seamlessly with Next.js 15 App Router and Server Components. Create API routes in the
app/apidirectory as Route Handlers, which run on the server side. Server Components cannot directly call the Plivo API (they don't handle POST requests), so use Server Actions or API routes to send SMS messages from your Next.js application. The Plivo Node.js SDK is fully compatible with Next.js serverless functions.What are the Plivo trial account limitations for SMS?
Plivo trial accounts can only send SMS to verified phone numbers that you manually add in the Plivo Console under Phone Numbers > Sandbox Numbers. Trial accounts have limited credits (typically $10) and may display a trial message prefix on outgoing SMS. Upgrade to a paid account to remove these restrictions and send to any phone number globally.
How do I handle SMS delivery failures in Next.js?
Implement error handling with try-catch blocks around
client.messages.create()calls and check for specific Plivo error codes (401 for authentication, 400 for invalid numbers, 402 for insufficient funds). Log errors to monitoring services like Sentry, return appropriate HTTP status codes to clients, and optionally implement retry logic with exponential backoff for transient failures (429 rate limits, 5xx server errors). Do not retry authentication failures or invalid parameters.What is the best way to store Plivo credentials in Next.js?
Store Plivo credentials in
.env.localfor development (automatically ignored by Git) and use environment variables in your hosting provider for production. Never hardcode Auth ID or Auth Token in source code. Access credentials server-side only usingprocess.env.PLIVO_AUTH_IDandprocess.env.PLIVO_AUTH_TOKEN. For enhanced security, use secret management services like Vercel Environment Variables, AWS Secrets Manager, or HashiCorp Vault.How do I send SMS verification codes with Plivo and NextAuth?
Create a Next.js API route that generates a random 6-digit code, stores it in your database (or cache like Redis) with a 10-minute expiration, and sends it via Plivo to the user's phone number. When the user submits the code, verify it matches the stored value and hasn't expired. Integrate this with NextAuth's credentials provider to enable SMS-based two-factor authentication (2FA). See NextAuth credentials provider documentation for implementation details.
Related Resources: Next.js SMS Integration & Authentication
Next.js & API Development:
NextAuth Authentication:
Plivo SMS Integration:
Standards & Specifications:
Security & Best Practices:
Master Next.js SMS integration with Plivo and NextAuth to build production-ready authenticated messaging features. Implement proper error handling, session management, and security best practices for reliable SMS delivery in your applications.
Primary Source Citations: