code examples

Sent logo
Sent TeamMay 3, 2025 / code examples / Article

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.

Send SMS with Plivo in Next.js using NextAuth: Complete Guide

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.

System Architecture:

+-------------+ +------------------------+ +-------------+ +-----------------+ | Client |------>| Next.js API Route |------>| Plivo SDK |------>| Plivo API/Infra | | (Browser/ | | (Protected by NextAuth)| | (Node Module)| | (Sends SMS) | | React) | +------------------------+ +-------------+ +-----------------+ +-------------+ | | POST /api/send-sms | | { "to": "...", | | "text": "..." } | | | | [NextAuth] | Session Check | | +----------------------+

Prerequisites:

  • 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.

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.

  1. Create Next.js Project: Open your terminal and use create-next-app to scaffold a new Next.js application:

    bash
    npx create-next-app@latest plivo-sms-nextauth

    When prompted, select these options:

    • TypeScript? Yes (recommended for type safety)
    • ESLint? Yes
    • Tailwind CSS? Optional (your preference)
    • src/ directory? Yes (for better organization)
    • App Router? Yes (Next.js 15 default, uses React Server Components)
    • Import alias? @/* (default)
    bash
    cd plivo-sms-nextauth
  2. Install Dependencies: Install the Plivo Node.js SDK and NextAuth for authentication:

    bash
    npm install plivo next-auth

    Current versions (as of January 2025):

  3. Project Structure: Your Next.js project should have this structure:

    plivo-sms-nextauth/ ├── src/ │ ├── app/ # App Router (Next.js 15) │ │ ├── api/ │ │ │ ├── auth/ │ │ │ │ └── [...nextauth]/ │ │ │ │ └── route.ts # NextAuth configuration │ │ │ └── send-sms/ │ │ │ └── route.ts # SMS API endpoint │ │ ├── layout.tsx # Root layout │ │ └── page.tsx # Home page │ └── lib/ │ └── auth.ts # NextAuth config options ├── .env.local # Environment variables (local) ├── .gitignore ├── next.config.js ├── package.json └── tsconfig.json
  4. Configure .gitignore: Ensure .env.local is in your .gitignore (Next.js includes this by default):

    Code
    # environment variables
    .env*.local
    .env.production
    
    # next.js
    /.next/
    /out/
    
    # dependencies
    /node_modules
    
    # logs
    npm-debug.log*
  5. Configure Environment Variables (.env.local): Create a .env.local file in your project root. Next.js automatically loads variables from this file.

    • Purpose:
      • 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 authentication
      • PLIVO_SENDER_ID: The Plivo phone number (E.164 format: +14155551212) or approved Alphanumeric Sender ID
      • NEXTAUTH_SECRET: Random string for encrypting JWT tokens. Generate with: openssl rand -base64 32
      • NEXTAUTH_URL: Your application URL (http://localhost:3000 for development)
    • How to Obtain Plivo Credentials:
      1. Log in to your Plivo Console
      2. Your Auth ID and Auth Token are displayed on the dashboard
      3. For PLIVO_SENDER_ID: Navigate to Phone NumbersBuy Numbers to purchase an SMS-enabled number. Use E.164 format (e.g., +14155551212)
      4. For Alphanumeric Sender IDs (availability varies by country), check Plivo's documentation or support for registration requirements
    • Format:
    Code
    # Plivo Credentials - KEEP THESE SECRET
    PLIVO_AUTH_ID=your_auth_id_here
    PLIVO_AUTH_TOKEN=your_auth_token_here
    PLIVO_SENDER_ID=+14155551212
    
    # NextAuth Configuration
    NEXTAUTH_SECRET=your_generated_secret_here
    NEXTAUTH_URL=http://localhost:3000

    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.

  1. Create NextAuth Configuration (src/lib/auth.ts):

    typescript
    // 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.

  2. Create NextAuth API Route Handler (src/app/api/auth/[...nextauth]/route.ts):

    typescript
    // 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):

typescript
// 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:

  1. Authentication Check: Uses getServerSession() from NextAuth to verify authentication before processing requests. Returns 401 Unauthorized if no valid session exists.
  2. 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
  3. Plivo Client Initialization: Creates a Plivo client instance with credentials from environment variables
  4. SMS Sending: Calls plivoClient.messages.create() with sender ID, destination number, and message text
  5. 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.

Update Home Page (src/app/page.tsx):

typescript
// 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):

typescript
// 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):

typescript
// 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

  1. Start the Development Server:

    bash
    npm run dev

    The application will be available at http://localhost:3000

  2. 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
  3. 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
  • Add verified numbers in Plivo Console: Phone Numbers > Sandbox Numbers
  • Trial accounts include limited credits (typically $10 USD)
  • Messages may include a trial account prefix
  • Upgrade to a paid account to remove restrictions and send to any number globally

SMS Pricing:

According to Plivo's US SMS pricing (as of January 2025):

  • Long Codes: Starting at $0.0070/SMS (outbound)
  • Toll-Free: $0.0072/SMS (outbound)
  • Receiving SMS: $0.0055/SMS (inbound)

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:

  1. Specific Error Types: Plivo SDK errors include status codes and error messages. Handle common cases:

    • 401 Unauthorized: Invalid credentials
    • 400 Bad Request: Invalid destination number or parameters
    • 402 Payment Required: Insufficient account balance
    • 429 Too Many Requests: Rate limiting
    • 5xx Server Errors: Plivo service issues
  2. Structured Logging: Use a logging library like winston or pino for production:

    typescript
    // 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');
  3. Error Tracking: Integrate services like Sentry or Datadog for production error monitoring.

Retry Logic:

Implement exponential backoff for transient failures:

typescript
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/ratelimit for serverless-friendly rate limiting:

bash
npm install @upstash/ratelimit @upstash/redis
typescript
// 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:

typescript
// 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:

  • Use validation libraries like zod for type-safe validation
  • Limit message length (160 chars GSM-7, 70 chars Unicode per segment)
  • Strip dangerous characters if storing in databases
  • Consider content filtering for compliance

Secrets Management:

For production deployments:

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)
  • Unicode: 67 chars per segment
  • Recipients' devices reassemble segments automatically
  • Billing: Each segment is billed separately

E.164 Phone Number Format:

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.
  • Other Countries: Regulations vary widely. Always consult Plivo's country-specific documentation before sending.

Opt-Out Handling:

In the US, TCPA regulations require honoring opt-out requests (e.g., "STOP" replies):

  • Implement webhook handlers to receive incoming SMS (see our guide on two-way SMS messaging)
  • Maintain an opt-out list in your database
  • Check opt-out status before sending messages
  • See Plivo's compliance guide for requirements

10. Optimizing SMS Performance for Production

Connection Management:

  • The Plivo Node.js SDK handles HTTP connection pooling automatically
  • Reuse client instances (as shown in our implementation) rather than creating new clients per request
  • Next.js API routes are stateless, but module-level client initialization provides reuse within the same serverless function instance

Bulk SMS Sending:

For sending to multiple recipients:

typescript
// 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:

typescript
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:

  • Track API response times using Next.js middleware
  • Monitor error rates and delivery success
  • Use Plivo Console analytics for message status tracking
  • Set up alerts for unusual activity or failures

11. Troubleshooting Common Plivo SMS Issues

Common Issues:

  • 401 Unauthorized: Invalid PLIVO_AUTH_ID or PLIVO_AUTH_TOKEN. Verify credentials in Plivo Console and .env.local.
  • Trial Account Restrictions: "Cannot send SMS to unverified number" – Add destination numbers to Phone Numbers > Sandbox Numbers in Plivo Console.
  • 402 Payment Required: Insufficient account balance. Add funds or upgrade from trial account.
  • 400 Bad Request - Invalid dst: Phone number not in E.164 format. Ensure format is +[country code][number].
  • 400 Bad Request - Invalid src: Sender ID not owned or approved. Verify PLIVO_SENDER_ID is a purchased Plivo number or registered Alphanumeric ID.
  • 429 Too Many Requests: Rate limiting. Implement backoff or reduce send frequency.
  • Network Errors: ECONNREFUSED, ETIMEDOUT – Check network connectivity to api.plivo.com. Verify firewalls allow outbound HTTPS.
  • NextAuth Session Issues: Ensure NEXTAUTH_SECRET is set and NEXTAUTH_URL matches your application URL.

Debugging Tips:

  1. Check Server Logs: Run npm run dev and watch console output for detailed error messages
  2. Plivo Console: Check Logs section in Plivo Console for API call history and errors
  3. Test Authentication: Use /api/auth/session endpoint to verify NextAuth is working
  4. Verify Environment Variables: Add console.log to verify env vars are loaded (remove before production)
  5. 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.

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:

Frequently Asked Questions

How to send SMS with Node.js and Express?

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`).

What is the Plivo Node.js SDK?

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.

Why does Plivo require E.164 format?

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.

When should I use Alphanumeric Sender ID?

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.

Can I send bulk SMS messages with Plivo?

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.

How to set up a Plivo SMS project in Node.js?

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.

What is the purpose of dotenv in a Plivo project?

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.

Why does my Plivo SMS fail with a 400 error?

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.

When should I implement SMS retry mechanisms?

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.

How to handle Plivo SMS errors effectively?

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.

How to improve Plivo SMS sending performance?

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.

What is the role of a database schema in a Plivo SMS application?

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.

How can I add security to my Plivo SMS integration?

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.

How to troubleshoot Plivo SMS delivery issues?

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.

What is GSM encoding in SMS, and how does it affect messages?

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.