code examples

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

How to Send SMS with Plivo in Next.js: Complete API Integration Tutorial

Learn how to send SMS messages using Plivo API in Next.js. Step-by-step tutorial with serverless API routes, E.164 phone validation, secure credential management, error handling, and Vercel deployment. Includes working code examples.

This guide shows you how to integrate the Plivo SMS API into a Next.js application for sending text messages programmatically. You'll build a Next.js application with a serverless backend API route that securely handles SMS delivery through Plivo's communication platform.

By the end of this tutorial, you'll have a functional Next.js SMS sending application that accepts phone numbers and message text via an API endpoint and dispatches messages through Plivo. This foundation enables you to add SMS notifications, alerts, two-factor authentication, and messaging features to your web applications.

Project Overview and Goals

What You're Building: A Next.js application featuring a serverless API route (/api/send-sms). This route accepts POST requests containing a destination phone number and a message body, then uses the Plivo Node.js SDK to send the SMS message.

Problem Solved: Send SMS messages programmatically from a modern web application framework like Next.js, leveraging a reliable communication platform like Plivo. This guide provides a secure and straightforward method for server-side SMS dispatch.

When to Use This Pattern: Choose this serverless API route approach when you need transactional SMS (password resets, order confirmations, alerts) sent in response to user actions. For high-volume broadcast messaging or complex workflows, consider dedicated message queue systems (RabbitMQ, AWS SQS) or background job processors (Bull, Celery). For simple notification needs under 1,000 messages per hour, this direct API approach works well.

Cost Implications: Plivo charges per message segment. Standard SMS in the US costs approximately $0.0065–$0.0085 per message segment. Messages exceeding 160 GSM characters or 70 Unicode characters split into multiple segments, multiplying costs. Budget accordingly – 10,000 standard messages monthly costs roughly $65–$85. Trial accounts include $10 credit but restrict sending to verified numbers only.

Technologies Used:

  • Next.js: A React framework for building server-side rendered and static web applications. Use its API Routes feature for backend functionality.
  • Node.js: The JavaScript runtime environment Next.js is built upon.
  • Plivo: A cloud communications platform providing SMS API services.
  • Plivo Node.js SDK: A helper library simplifying interaction with the Plivo API.

System Architecture:

mermaid
graph LR
    A[User/Client] -- POST /api/send-sms --> B(Next.js API Route);
    B -- Use Plivo SDK --> C(Plivo API);
    C -- Sends SMS --> D(Recipient's Phone);
    B -- Returns Success/Error --> A;

Prerequisites:

  • Node.js 18.x or later (LTS recommended) and npm 9.x or later (or yarn 1.22+) installed.
  • A Plivo account (Sign up here).
  • Basic understanding of JavaScript, React, and Next.js concepts.
  • A text editor (like VS Code).
  • Access to a terminal or command prompt.

Compatibility: This guide uses Next.js 14+ with Pages Router. The App Router (Next.js 13+) requires minor adjustments to route structure (app/api/send-sms/route.js instead of pages/api/send-sms.js) but uses identical Plivo SDK code.

1. Setting Up Your Next.js Project with Plivo

Create a new Next.js project and install the necessary dependencies.

  1. Create a Next.js App: Open your terminal and run the following command. Replace plivo-nextjs-sms with your desired project name. Follow the prompts – selecting defaults works fine for this guide (using Pages Router for simplicity here, but concepts apply to App Router).

    bash
    npx create-next-app@latest plivo-nextjs-sms
  2. Navigate to Project Directory:

    bash
    cd plivo-nextjs-sms
  3. Install Plivo Node.js SDK: Add the Plivo helper library to your project dependencies.

    bash
    npm install plivo

    or using yarn:

    bash
    yarn add plivo
  4. Set Up Environment Variables: Securely store your Plivo credentials. Create a file named .env.local in the root of your project. Never commit this file to version control.

    plaintext
    # .env.local
    
    # Plivo Credentials – Get from Plivo Console Dashboard
    PLIVO_AUTH_ID=YOUR_PLIVO_AUTH_ID
    PLIVO_AUTH_TOKEN=YOUR_PLIVO_AUTH_TOKEN
    
    # Plivo Sender ID – A Plivo phone number you own or a registered Alphanumeric Sender ID
    # For US/Canada, MUST be a Plivo phone number in E.164 format (e.g., +14155551212)
    PLIVO_SENDER_ID=YOUR_PLIVO_SENDER_ID_OR_NUMBER
    • Purpose: Environment variables prevent hardcoding sensitive credentials directly into your codebase. .env.local is Next.js's standard way to load these variables during development and is included in .gitignore by default.
  5. Verify .gitignore: Ensure that .env*.local is listed in your project's .gitignore file (create-next-app usually adds this automatically). This prevents accidentally committing your secrets.

    plaintext
    # .gitignore (should include lines like this)
    # ... other entries
    .env*.local
    # ... other entries

Your basic project structure is now ready. You have Next.js set up, the Plivo SDK installed, and a secure way to manage API credentials.

Common Setup Issues:

  • npm install fails: Clear npm cache with npm cache clean --force and retry.
  • Port 3000 already in use: Kill the process using port 3000 (lsof -ti:3000 | xargs kill on macOS/Linux) or specify a different port with npm run dev -- -p 3001.
  • TypeScript errors: This guide uses JavaScript. If you selected TypeScript during setup, rename .js files to .ts or .tsx and add type annotations as needed.

2. Creating the SMS API Route

The core logic for sending SMS resides in a Next.js API route. This keeps your Plivo credentials and logic secure on the server-side.

  1. Create the API Route File: Inside the pages directory, create a folder named api. Inside api, create a file named send-sms.js.

    • Project Structure:
      plaintext
      plivo-nextjs-sms/
      ├── pages/
      │   ├── api/
      │   │   └── send-sms.js  <-- Your API endpoint
      │   ├── _app.js
      │   └── index.js
      ├── public/
      ├── styles/
      ├── .env.local
      ├── .gitignore
      ├── next.config.js
      ├── package.json
      └── README.md
  2. Implement the API Logic: Open pages/api/send-sms.js and add the following code:

    javascript
    // pages/api/send-sms.js
    import plivo from 'plivo';
    
    export default async function handler(req, res) {
      // 1. Ensure this is a POST request
      // Security: Restricts endpoint to POST method only, preventing unintended GET-based exposure
      if (req.method !== 'POST') {
        res.setHeader('Allow', ['POST']);
        return res.status(405).json({ error: `Method ${req.method} Not Allowed` });
      }
    
      // 2. Extract destination number and text from request body
      const { to, text } = req.body;
    
      // 3. Basic Input Validation
      // Security: Prevents empty or malformed requests from reaching Plivo API
      if (!to || !text) {
        return res.status(400).json({ error: 'Missing `to` or `text` field in request body' });
      }
      // Add more robust validation as needed (e.g., E.164 format check for 'to')
    
      // 4. Retrieve Plivo credentials and sender ID from environment variables
      // Security: Credentials remain server-side, never exposed to client browser
      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.');
        return res.status(500).json({ error: 'Server configuration error.' });
      }
    
      // 5. Initialize Plivo client
      const client = new plivo.Client(authId, authToken);
    
      // 6. Send the SMS using Plivo SDK
      try {
        const response = await client.messages.create({
          src: senderId, // Sender ID or Plivo number
          dst: to,       // Destination number in E.164 format
          text: text,    // Message content
        });
    
        console.log('Plivo API Response:', response);
    
        // 7. Return success response
        return res.status(200).json({
          message: 'SMS sent successfully!',
          plivoResponse: response, // Contains the actual response from Plivo API
        });
    
      } catch (error) {
        // 8. Handle Plivo API errors
        console.error('Error sending SMS via Plivo:', error);
    
        // Provide a more specific error message if possible
        let errorMessage = 'Failed to send SMS.';
        if (error.message) {
          errorMessage += ` Plivo Error: ${error.message}`;
        }
    
        // Determine appropriate status code (e.g., 400 for validation errors from Plivo, 500 for others)
        // Plivo errors often include a status code, but check SDK/API docs
        const statusCode = error.statusCode || 500;
    
        return res.status(statusCode).json({ error: errorMessage });
      }
    }

Code Explanation:

  • Line 1: Imports the installed plivo SDK.
  • Lines 4–8: Ensures the API route only accepts POST requests, a standard practice for actions that change state or send data.
  • Lines 11–16: Extracts the to (destination phone number) and text (message content) from the incoming JSON request body. Basic validation checks if they exist. Includes a note about further validation (like E.164).
  • Lines 19–25: Securely retrieves Plivo credentials and the sender ID from environment variables. Includes a check to ensure they're configured.
  • Line 28: Initializes the Plivo client with your credentials.
  • Lines 31–45: Uses a try...catch block to handle the SMS sending process.
    • client.messages.create() sends the actual request to Plivo.
      • src: Your Plivo number or registered Sender ID (from env vars).
      • dst: The recipient's number (from request body). Must be in E.164 format (e.g., +12025551234).
      • text: The message content (from request body).
    • Logs the Plivo response for debugging.
    • Returns a 200 status code with a success message and the raw Plivo API response upon success.
  • Lines 46–58: Catches any errors during the Plivo API call.
    • Logs the error for server-side debugging.
    • Constructs an informative error message, including Plivo's specific error if available.
    • Returns an appropriate HTTP status code (using Plivo's status code if provided, otherwise 500) and the error message to the client.

This API route now encapsulates the core SMS sending functionality securely on the server.

3. Building a Complete API Layer

The pages/api/send-sms.js file is your API layer for this simple application. Let's refine the documentation and testing aspects.

API Endpoint Documentation:

  • Endpoint: /api/send-sms
  • Method: POST
  • Content-Type: application/json
  • Authentication: None (for this basic example). In a real application, implement authentication (e.g., JWT, session cookies, API keys) to protect this endpoint.
  • Request Body (JSON):
    json
    {
      "to": "+1xxxxxxxxxx",
      "text": "Your message content here"
    }
    • to: Required. Destination phone number in E.164 format.
    • text: Required. The SMS message text.
  • Success Response (200 OK):
    json
    {
      "message": "SMS sent successfully!",
      "plivoResponse": {
        "message": "message(s) queued",
        "message_uuid": ["xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"],
        "api_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
      }
    }
    (Note: The plivoResponse object reflects the actual fields returned by the Plivo API, which typically use snake_case like message_uuid and api_id.)
  • Error Responses:
    • 400 Bad Request (Input Validation):
      json
      {
        "error": "Missing `to` or `text` field in request body"
      }
    • 405 Method Not Allowed:
      json
      {
        "error": "Method GET Not Allowed"
      }
    • 500 Internal Server Error (Configuration or Plivo API Error):
      json
      {
        "error": "Server configuration error."
      }
      or
      json
      {
        "error": "Failed to send SMS. Plivo Error: Invalid 'dst' parameter"
      }

Testing Your SMS API with curl:

Replace placeholders with your actual data and run this in your terminal while your Next.js development server runs (npm run dev).

bash
curl -X POST http://localhost:3000/api/send-sms \
-H "Content-Type: application/json" \
-d '{
  "to": "+1RECIPIENT_PHONE_NUMBER",
  "text": "Hello from Next.js and Plivo!"
}'

Replace +1RECIPIENT_PHONE_NUMBER with a valid phone number (if using a trial Plivo account, it must be a number verified in your Plivo console under Phone Numbers > Sandbox Numbers).

You should receive a JSON response indicating success or failure, and see logs in your Next.js development server console.

Frontend Integration Patterns:

Pattern 1: Direct Fetch from Client Component (Next.js Pages Router)

javascript
// pages/index.js or any component
async function sendSMS() {
  const response = await fetch('/api/send-sms', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ to: '+15551234567', text: 'Hello!' }),
  });
  const data = await response.json();
  console.log(data);
}

Pattern 2: Server-Side Trigger (Next.js Server Component or getServerSideProps)

javascript
// app/send/route.js (App Router) or inside getServerSideProps (Pages Router)
import plivo from 'plivo';

export async function POST(request) {
  // Same logic as pages/api/send-sms.js
  // Trigger SMS based on server-side event
}

Pattern 3: Background Job Queue (Production-Grade)

For high-volume or time-sensitive SMS, integrate a queue system like Bull or AWS SQS. Your API route adds jobs to the queue; worker processes consume and send messages asynchronously.

API Versioning Strategy:

This basic guide doesn't implement versioning. For production APIs expected to evolve, adopt a versioning strategy:

  • URL Versioning: /api/v1/send-sms, /api/v2/send-sms – Explicit and easy to route. Recommended for public APIs.
  • Header Versioning: Accept: application/vnd.yourapp.v1+json – Cleaner URLs but less visible.
  • Query Parameter: /api/send-sms?version=1 – Simple but pollutes URLs.

Maintain backward compatibility for at least one major version. Document deprecation timelines clearly.

4. Configuring Plivo Credentials and Authentication

Proper integration requires correctly obtaining and configuring your Plivo credentials.

  1. Sign Up/Log In: Go to the Plivo Console.

  2. Find Auth ID and Auth Token:

    • Navigate to the main dashboard after logging in.
    • Locate the "Account Info" or similar section. Your Auth ID and Auth Token appear prominently here.
    • Copy these values carefully.
  3. Obtain a Sender ID (Plivo Number):

    • To send SMS, especially to the US and Canada, you need a Plivo phone number.
    • Navigate to "Phone Numbers" > "Buy Numbers" in the Plivo Console.
    • Search for numbers based on country, capabilities (SMS), and type (Local, Toll-Free).
    • Purchase a number suitable for your needs.
    • Once purchased, the number appears under "Phone Numbers" > "Your Numbers". Copy the full number in E.164 format (e.g., +14155551212).
    • Note: In some countries outside the US/Canada, you might be able to register and use an Alphanumeric Sender ID (e.g., "MyCompany"). Check Plivo's documentation for country-specific rules. For this guide, assume a Plivo number is used.
  4. Update .env.local: Paste the copied Auth ID, Auth Token, and your Plivo Number (Sender ID) into the respective variables in your .env.local file.

    plaintext
    # .env.local
    PLIVO_AUTH_ID=MAXXXXXXXXXXXXXXXXXX  # Example Format
    PLIVO_AUTH_TOKEN=AbCdEfGhIjKlMnOpQrStUvWxYz0123456789 # Example Format
    PLIVO_SENDER_ID=+14155551212        # Example Format (Your actual Plivo number)
  5. Restart Development Server: Crucially, after modifying .env.local, stop (Ctrl+C) and restart your Next.js development server (npm run dev or yarn dev) for the changes to take effect.

Environment Variable Summary:

  • PLIVO_AUTH_ID: Your unique Plivo account identifier. Used for authenticating API requests. Obtain from Plivo Console Dashboard.
  • PLIVO_AUTH_TOKEN: Your secret Plivo API key. Used for authenticating API requests. Obtain from Plivo Console Dashboard. Treat this like a password.
  • PLIVO_SENDER_ID: The identifier messages originate from. For US/Canada, this must be a Plivo phone number you own, in E.164 format. Obtain by buying a number in the Plivo Console.

Security Best Practices for Credential Rotation:

Rotate your PLIVO_AUTH_TOKEN every 90 days to minimize exposure risk. Plivo allows generating a new Auth Token from the Console. After rotation:

  1. Update .env.local (development) immediately.
  2. Update environment variables in deployment platforms (Vercel, AWS, etc.) before the old token expires.
  3. Set a calendar reminder for the next rotation.

For production systems, use secret management tools like AWS Secrets Manager, HashiCorp Vault, or Doppler to automate rotation and centralize credential access.

5. Error Handling and Logging Best Practices

Our API route includes basic error handling and logging.

Error Handling Strategy:

  • Input Validation: Check for required fields (to, text) early and return a 400 Bad Request.
  • Configuration Errors: Check for missing environment variables and return a 500 Internal Server Error.
  • Plivo API Errors: Catch exceptions from the plivo.Client call. Log the detailed error server-side. Return an appropriate status code (Plivo's if available, otherwise 500) and a user-friendly error message (including Plivo's specific error if helpful) to the client.

Logging:

  • Currently using console.log for successful Plivo responses and console.error for configuration issues and Plivo API errors.
  • Production Logging: For production, integrate a dedicated logging service (e.g., Sentry, Datadog, Logtail, Axiom). These services provide structured logging, aggregation, search, and alerting capabilities. You would replace console.log/error with calls to your chosen logging library.

Retry Mechanisms:

  • Concept: For transient network issues or temporary Plivo service disruptions, implementing retries can improve reliability. A common strategy is exponential backoff (wait 1s, then 2s, then 4s, etc., before retrying).
  • Distinguishing Retriable vs Non-Retriable Errors:
    • Retriable (5xx errors, network timeouts): 503 Service Unavailable, 504 Gateway Timeout, network errors, rate limit errors (429).
    • Non-Retriable (4xx errors): 400 Bad Request (invalid phone number), 401 Unauthorized (bad credentials), 402 Payment Required (insufficient credit), 403 Forbidden.
  • Practical Implementation: Use the async-retry library to wrap Plivo API calls. Install with npm install async-retry.
javascript
// pages/api/send-sms.js (with retry logic)
import plivo from 'plivo';
import retry from 'async-retry';

export default async function handler(req, res) {
  // ... (validation and setup code unchanged) ...

  const client = new plivo.Client(authId, authToken);

  try {
    const response = await retry(
      async (bail) => {
        try {
          return await client.messages.create({
            src: senderId,
            dst: to,
            text: text,
          });
        } catch (error) {
          // Don't retry on non-retriable errors
          if (error.statusCode && error.statusCode >= 400 && error.statusCode < 500) {
            bail(error); // Stop retrying
            return;
          }
          throw error; // Retry on 5xx or network errors
        }
      },
      {
        retries: 3,        // Retry up to 3 times
        factor: 2,         // Exponential backoff factor
        minTimeout: 1000,  // Start with 1 second
        maxTimeout: 10000, // Cap at 10 seconds
      }
    );

    console.log('Plivo API Response:', response);
    return res.status(200).json({
      message: 'SMS sent successfully!',
      plivoResponse: response,
    });

  } catch (error) {
    console.error('Error sending SMS via Plivo:', error);
    let errorMessage = 'Failed to send SMS.';
    if (error.message) {
      errorMessage += ` Plivo Error: ${error.message}`;
    }
    const statusCode = error.statusCode || 500;
    return res.status(statusCode).json({ error: errorMessage });
  }
}

Testing Error Scenarios:

  • Bad Input: Send a curl request missing the to or text field. Expect a 400 response.
  • Invalid Credentials: Temporarily modify PLIVO_AUTH_ID or PLIVO_AUTH_TOKEN in .env.local, restart the server, and send a valid request. Expect a 401 or similar authentication error from Plivo (logged server-side, likely resulting in a 500 response to the client).
  • Invalid Destination: Send a request with an incorrectly formatted to number (e.g., "12345"). Expect a Plivo error (logged server-side, likely a 400 or 500 response to the client).
  • Trial Account Limit: If using a trial account, send an SMS to a non-verified number. Expect a specific Plivo error related to trial restrictions.

6. Database Schema and Data Layer

This specific guide focuses solely on the immediate action of sending an SMS via an API call and does not require a database.

If you were building a more complex application, you might use a database to:

  • Store message history (sent time, recipient, status, content).
  • Manage user accounts associated with SMS sending.
  • Queue messages for later delivery.
  • Track delivery statuses received via Plivo webhooks (for receiving messages or status updates).

When Database Integration Becomes Necessary:

  • Audit Trail Requirements: You need to log every SMS sent for compliance (HIPAA, GDPR, SOC 2).
  • User-Specific Quotas: Track per-user message limits to prevent abuse.
  • Scheduled Messaging: Store messages to send at future times (appointment reminders, scheduled campaigns).
  • Delivery Tracking: Store webhook responses from Plivo to track message delivery status.

Example Schema (PostgreSQL with Prisma):

prisma
model SmsMessage {
  id            String   @id @default(uuid())
  to            String
  from          String
  text          String
  status        String   @default("queued") // queued, sent, delivered, failed
  messageUuid   String?  @unique // Plivo's message_uuid
  errorMessage  String?
  createdAt     DateTime @default(now())
  deliveredAt   DateTime?
  userId        String?  // If user-specific
  user          User?    @relation(fields: [userId], references: [id])
}

Implementing a database would involve choosing a database (e.g., PostgreSQL, MongoDB), selecting an ORM or client library (e.g., Prisma, Mongoose, node-postgres), defining schemas/models, and writing data access logic. This is outside the scope of this basic sending guide.

7. Security Features and Phone Number Validation

While basic, security is crucial.

  • Environment Variables: As implemented, keeping credentials (PLIVO_AUTH_ID, PLIVO_AUTH_TOKEN) out of the codebase and in .env.local (and ensuring .env.local is in .gitignore) is the most critical security step.
  • Server-Side Logic: All interaction with the Plivo API happens within the Next.js API route, preventing exposure of credentials to the client-side browser.
  • Input Validation: The basic check for to and text prevents trivial errors.
    • Enhancement: Add more robust validation for the to field to ensure it resembles an E.164 formatted number before sending it to Plivo. Libraries like libphonenumber-js can help parse and validate phone numbers.
    javascript
    // Example using libphonenumber-js (install first: npm install libphonenumber-js)
    import { parsePhoneNumberFromString } from 'libphonenumber-js';
    // ... inside handler ...
    const phoneNumber = parsePhoneNumberFromString(to);
    if (!phoneNumber || !phoneNumber.isValid()) {
        return res.status(400).json({ error: 'Invalid `to` phone number format.' });
    }
    const formattedTo = phoneNumber.format('E.164'); // Use the validated/formatted number
    // ... use formattedTo in client.messages.create ...
    • Alternative Regex Validation: For a lightweight validation without external libraries, use this E.164 regex pattern: /^\+[1-9]\d{1,14}$/. This ensures the number starts with +, followed by a country code (1-9), and has a maximum of 15 digits total.
    javascript
    // Lightweight E.164 validation
    const e164Pattern = /^\+[1-9]\d{1,14}$/;
    if (!e164Pattern.test(to)) {
        return res.status(400).json({ error: 'Invalid phone number. Must be in E.164 format (+[country code][number], max 15 digits).' });
    }
  • Rate Limiting: To prevent abuse (e.g., a script spamming your endpoint), implement rate limiting on the API route.
    • Next.js Middleware: You can use middleware to apply rate limiting logic before the API handler executes.
    • Libraries: Packages like rate-limiter-flexible or Vercel's built-in helpers can be used.
    • Strategy: Limit requests per IP address or user identifier over a specific time window (e.g., 10 requests per minute).
    • Practical Example with rate-limiter-flexible:
javascript
// lib/rateLimiter.js
import { RateLimiterMemory } from 'rate-limiter-flexible';

const rateLimiter = new RateLimiterMemory({
  points: 10,      // 10 requests
  duration: 60,    // per 60 seconds
});

export default rateLimiter;

// pages/api/send-sms.js (with rate limiting)
import rateLimiter from '../../lib/rateLimiter';

export default async function handler(req, res) {
  // Apply rate limiting based on IP
  const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress;

  try {
    await rateLimiter.consume(ip);
  } catch (rateLimiterRes) {
    return res.status(429).json({ error: 'Too many requests. Try again later.' });
  }

  // ... rest of handler logic ...
}
  • Authentication/Authorization: As mentioned, a real-world application should protect this endpoint. Only authenticated and authorized users should be able to trigger SMS sending. Implement mechanisms like session cookies, JWTs, or API key checks depending on your application's needs.
  • HTTPS: Ensure your Next.js application is deployed and served over HTTPS to encrypt traffic between the client and your API route. Platforms like Vercel handle this automatically.
  • CORS Configuration: By default, Next.js API routes accept requests from the same origin. To allow specific external origins (e.g., a separate frontend), configure CORS headers:
javascript
// pages/api/send-sms.js (with CORS)
export default async function handler(req, res) {
  // Set CORS headers
  res.setHeader('Access-Control-Allow-Origin', 'https://yourdomain.com'); // Replace with your domain
  res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type');

  // Handle preflight OPTIONS request
  if (req.method === 'OPTIONS') {
    return res.status(200).end();
  }

  // ... rest of handler logic ...
}

For broader CORS needs, use the cors middleware package.

8. Understanding E.164 Format and Special Cases

  • E.164 Format: Plivo strictly requires the destination number (dst) to be in E.164 format (e.g., +14155551212). E.164 numbers start with +, followed by a country code (1-3 digits), and the subscriber number, with a maximum total of 15 digits. Ensure any user input is correctly formatted before sending to the API. The validation examples in section 7 help here.
  • Sender ID Restrictions: As noted, the US and Canada require using a Plivo-owned, SMS-enabled phone number as the src. Other countries may allow Alphanumeric Sender IDs, but these often require pre-registration and cannot receive replies. Trial accounts cannot use alphanumeric sender IDs. Always check Plivo's documentation for the target country's regulations.
  • Trial Account Limitations: Plivo trial accounts can only send messages to phone numbers verified within the Plivo console (Sandbox Numbers). Sending to other numbers results in an error. To remove this restriction, upgrade your account by adding a minimum of $25 credit. Trial accounts also have other limitations, such as no support for alphanumeric sender IDs.
  • Character Limits & Encoding: Standard SMS messages have character limits that depend on encoding:
    • GSM-7 Encoding: Up to 160 characters per message unit. Messages using only GSM 03.38 characters (basic Latin alphabet, numbers, common symbols) have a maximum total length of 1,600 characters. Longer messages split into units of 153 characters each (plus a 7-character User Data Header for concatenation).
    • UCS-2 Encoding (Unicode): Up to 70 characters per message unit. Messages containing any non-GSM characters (emojis, special symbols, non-Latin scripts) are encoded as UCS-2 and have a maximum total length of 737 characters. Longer messages split into units of 67 characters each (plus UDH).
    • Automatic Encoding: Plivo automatically detects which encoding to use. Be mindful of message length, as longer messages incur additional costs due to segmentation.
    • Intelligent Character Replacement: Plivo's intelligent encoding feature can detect easy-to-miss Unicode characters (like smart quotes " " or special dashes –) and replace them with similar GSM-encoded equivalents. This helps keep messages within the 160-character GSM limit and reduces costs by avoiding UCS-2 encoding when possible.
    • Cost Example: Sending "Hello 👋" (7 characters with emoji) uses UCS-2 encoding – 70 characters max per segment. If your message is 71 characters with emoji, it splits into 2 segments, doubling the cost.
  • Message Queuing: Plivo queues messages for delivery. The API response indicates the message was queued, not necessarily delivered. For delivery confirmation, set up Delivery Report Webhooks in your Plivo Console. Configure a webhook URL that Plivo will POST to with delivery status updates.
  • International SMS Considerations: Beyond sender ID rules, consider:
    • Pricing Variations: SMS costs vary significantly by country (e.g., $0.0065 in US, $0.10+ in some African nations).
    • Regulatory Compliance: Some countries require registration (India, Saudi Arabia) or restrict commercial SMS.
    • Time Zones: Schedule messages appropriately to avoid sending at night in recipient's time zone.
    • Language Support: Ensure proper Unicode encoding for non-English languages.

9. Performance Optimizations

For this simple API route, performance is typically bound by the Plivo API response time.

  • Keep Logic Server-Side: As implemented, keeping Plivo interactions server-side is essential for security and avoids exposing credentials.
  • Minimize Blocking: The code uses async/await, ensuring the Node.js event loop isn't blocked during the Plivo API call.
  • Connection Pooling (SDK): The Plivo SDK generally handles underlying HTTP connections efficiently.
  • Caching: Caching is not directly applicable to the action of sending a unique SMS. If you were retrieving data related to SMS (e.g., message logs), caching strategies (like Redis or in-memory caching with time-to-live) could be applied to API routes fetching that data.
  • Serverless Function Performance: When deployed on platforms like Vercel, consider cold starts for serverless functions. Frequent invocation keeps functions "warm," reducing latency. For very low-traffic, high-latency-sensitive applications, provisioned concurrency might be an option (platform-dependent).
  • Cold Start Mitigation Strategies:
    • Minimize Dependencies: Reduce package.json dependencies to decrease bundle size and initialization time.
    • Use Edge Functions: Vercel Edge Functions (or similar) deploy to edge locations globally with faster cold starts than traditional serverless functions.
    • Warm-Up Pings: Schedule periodic pings (every 5-10 minutes) to keep functions warm, though this adds cost.
    • Bundle Optimization: Enable Next.js production optimizations (next build) and tree-shaking to reduce function size.

10. Monitoring, Observability, and Analytics

  • Health Checks: The API route itself acts as a basic health check. If it returns a 2xx or expected error code (like 400 for bad input), the function is operational. You can set up external monitoring tools (e.g., UptimeRobot, Pingdom) to ping /api/send-sms (using a HEAD or GET request, though our current code only allows POST) or a dedicated /api/health endpoint.
  • Performance Metrics (Vercel Example): If deployed on Vercel, the Vercel Analytics feature provides insights into function invocation counts, duration, error rates, and more, requiring minimal setup.
  • Error Tracking: Integrate services like Sentry, Bugsnag, or Rollbar. These automatically capture unhandled exceptions and provide detailed stack traces, environment context, and alerting. Replace console.error with calls to your error tracking SDK.
  • Logging (Recap): Centralized logging (as mentioned in section 5) is crucial for observability. Searchable logs allow you to trace requests, diagnose errors, and monitor activity.
  • Plivo Logs: Utilize the Plivo Console's "Logs" section. It provides detailed records of all API requests, message statuses (queued, sent, failed, delivered – if webhooks are set up), associated costs, and error details. This is invaluable for debugging Plivo-specific issues.
  • Setting Up Alerts for Critical Failures:
    • Error Rate Alert: Configure Sentry/Datadog to alert when error rate exceeds 5% over 5 minutes.
    • Latency Alert: Alert when P95 response time exceeds 3 seconds (Plivo API typically responds in <1s).
    • Credit Balance Alert: Set up Plivo webhook alerts when account balance drops below $10 to prevent service interruption.
    • Failed Delivery Rate: If using delivery webhooks, alert when failed delivery rate exceeds 10% (indicates number validity issues).
  • Metric Thresholds and SLA Considerations:
    • Availability Target: 99.9% uptime (43 minutes downtime/month) – achievable with serverless deployments.
    • Latency Target: P95 < 2 seconds end-to-end (API route + Plivo API).
    • Error Rate Target: < 1% for 4xx errors (client issues), < 0.1% for 5xx errors (server issues).
    • Delivery Rate: > 95% successful delivery (measured via Plivo webhooks).

11. Common Issues and Troubleshooting

  • Error: Authentication Credentials Invalid:
    • Cause: PLIVO_AUTH_ID or PLIVO_AUTH_TOKEN in .env.local is incorrect or missing. Environment variables are not loaded correctly.
    • Solution: Double-check credentials in .env.local against the Plivo Console. Restart the Next.js server after any changes to .env.local. Ensure .env.local is in the project root. Verify process.env.PLIVO_AUTH_ID is accessible within the API route code (add temporary console.log).
  • Error: Missing to or text field:
    • Cause: The client request did not include the required fields in the JSON body, or they were not parsed correctly.
    • Solution: Verify the curl command or frontend code is sending a valid JSON body with both fields. Check the req.body object in the API route using console.log. Ensure Content-Type: application/json header is set in the request.
  • Error: Invalid dst parameter / Number requires + prefix:
    • Cause: The to number provided is not in the required E.164 format.
    • Solution: Ensure the client sends the number including the + and country code (e.g., +14155551212). Implement server-side validation (see section 7) to check the format before sending to Plivo.
  • Error: Message destination number prohibited by trial account:
    • Cause: Using a Plivo trial account and attempting to send SMS to a number not verified in the Sandbox Numbers section of the Plivo Console.
    • Solution: Add the destination number to your Sandbox Numbers list in the Plivo Console, or upgrade to a paid Plivo account (minimum $25 credit required).
  • Error: Insufficient credit:
    • Cause: Your Plivo account balance is too low to cover the cost of the SMS.
    • Solution: Add funds to your Plivo account.
  • Caveat: Environment Variables Loading: Changes to .env.local require restarting the Next.js development server. In production deployments (like Vercel), ensure environment variables are set correctly in the deployment platform's settings.
  • Caveat: Sender ID Capabilities: Ensure the Plivo number used as PLIVO_SENDER_ID is SMS-enabled for the destination country. Check capabilities in the Plivo Console under Phone Numbers > Your Numbers.

12. Deploying to Vercel with CI/CD

Deploy your Next.js application easily, especially with platforms like Vercel (the creators of Next.js).

Deploying to Vercel:

  1. Push to Git: Ensure your project is hosted on a Git provider like GitHub, GitLab, or Bitbucket. Make sure .env*.local is in your .gitignore.
  2. Import Project: Sign up or log in to Vercel. Click "Add New…" > "Project". Import your Git repository.
  3. Configure Project: Vercel usually detects Next.js automatically.
  4. Add Environment Variables: Navigate to the project settings in Vercel. Go to the "Environment Variables" section. Add PLIVO_AUTH_ID, PLIVO_AUTH_TOKEN, and PLIVO_SENDER_ID with their corresponding values. Ensure they are set for "Production" (and "Preview"/"Development" if needed). This is critical for the deployed application to work.
  5. Deploy: Click the "Deploy" button. Vercel builds and deploys your application.
  6. Access: Once deployed, Vercel provides a URL (e.g., your-project-name.vercel.app). Your API endpoint is available at https://your-project-name.vercel.app/api/send-sms.

CI/CD (Continuous Integration/Continuous Deployment):

  • Vercel's Git integration provides automatic CI/CD out-of-the-box.
  • When you push changes to your connected Git branch (e.g., main or master), Vercel automatically triggers a new build and deployment.
  • Preview deployments are created for pull requests, allowing testing before merging to production.

Environment-Specific Configuration Strategies:

Manage separate configurations for development, staging, and production:

  • Vercel Approach: Set environment variables with different values per environment (Production, Preview, Development) in project settings.
  • Multi-Environment Pattern:
    plaintext
    .env.local           # Local development (gitignored)
    .env.production      # Production values (gitignored, set in Vercel UI)
    .env.staging         # Staging values (gitignored, set in Vercel UI)
  • Feature Flags: Use services like LaunchDarkly or environment-based conditionals for environment-specific behavior without changing code.

Rollback Procedures:

Vercel keeps a history of deployments. In the Vercel dashboard for your project:

  1. Navigate to the "Deployments" tab.
  2. Locate the last known good deployment.
  3. Click the three-dot menu (⋮) next to that deployment.
  4. Select "Promote to Production."
  5. Vercel instantly makes that deployment the current production version.

This rollback process takes seconds and requires no code changes or redeploys.

13. Testing and Verification

Manual Verification:

  1. Deploy: Deploy the application to Vercel (or run locally npm run dev).
  2. Set Environment Variables: Ensure PLIVO_AUTH_ID, PLIVO_AUTH_TOKEN, and PLIVO_SENDER_ID are correctly set (in .env.local for local, Vercel dashboard for deployed).
  3. Send Test Request: Use curl (as shown in section 3) or a tool like Postman/Insomnia to send a POST request to your /api/send-sms endpoint (use the Vercel URL if deployed).
    • Use a valid, E.164 formatted destination number (a verified sandbox number if on a trial account).
    • Include a test message.
  4. Check Response: Verify the API returns a 200 OK status and the expected JSON success payload.
  5. Check Recipient: Confirm the SMS message is received on the destination phone.
  6. Check Plivo Logs: Log in to the Plivo Console and check the "Logs" section to see the record of the sent message, its status, and any potential errors.
  7. Check Server Logs: Check the logs from your Next.js server (local terminal or Vercel deployment logs) for the console messages and any errors.

Automated Testing (Examples):

While comprehensive testing setup is extensive, here are conceptual examples:

  • Unit Test (API Route Logic): Use a testing framework like Jest to test the API handler function. Mock the plivo SDK to avoid actual API calls during tests.

    javascript
    // Example using Jest (requires setup: npm install --save-dev jest @types/jest node-mocks-http)
    // pages/api/__tests__/send-sms.test.js
    import handler from '../send-sms';
    import { createMocks } from 'node-mocks-http';
    import plivo from 'plivo';
    
    // Mock the Plivo client and its methods
    jest.mock('plivo', () => {
      const mockMessagesCreate = jest.fn();
      return {
        Client: jest.fn().mockImplementation(() => ({
          messages: { create: mockMessagesCreate },
        })),
      };
    });
    
    describe('/api/send-sms handler', () => {
      let mockPlivoCreate;
    
      beforeEach(() => {
        // Reset mocks and setup environment variables for the test
        jest.clearAllMocks();
        // Ensure the mock constructor is called to get the instance for method mocking
        const mockPlivoClientInstance = new plivo.Client();
        mockPlivoCreate = mockPlivoClientInstance.messages.create;
    
        process.env.PLIVO_AUTH_ID = 'test-id';
        process.env.PLIVO_AUTH_TOKEN = 'test-token';
        process.env.PLIVO_SENDER_ID = '+15550001111';
      });
    
       afterEach(() => {
         // Clean up environment variables
         delete process.env.PLIVO_AUTH_ID;
         delete process.env.PLIVO_AUTH_TOKEN;
         delete process.env.PLIVO_SENDER_ID;
       });
    
      it('returns 405 if method is not POST', async () => {
        const { req, res } = createMocks({ method: 'GET' });
        await handler(req, res);
        expect(res._getStatusCode()).toBe(405);
        expect(res._getJSONData().error).toContain('Method GET Not Allowed');
      });
    
      it('returns 400 if `to` or `text` is missing', async () => {
        const { req, res } = createMocks({ method: 'POST', body: { to: '+123' } });
        await handler(req, res);
        expect(res._getStatusCode()).toBe(400);
        expect(res._getJSONData().error).toContain('Missing `to` or `text`');
      });
    
      it('calls Plivo client and returns 200 on success', async () => {
         const mockPlivoResponse = { message_uuid: ['some-uuid'], api_id: 'some-api-id' };
         mockPlivoCreate.mockResolvedValue(mockPlivoResponse);
    
        const { req, res } = createMocks({
          method: 'POST',
          body: { to: '+19998887777', text: 'Test message' },
        });
    
        await handler(req, res);
    
        expect(plivo.Client).toHaveBeenCalledWith('test-id', 'test-token');
        expect(mockPlivoCreate).toHaveBeenCalledWith({
          src: '+15550001111',
          dst: '+19998887777',
          text: 'Test message',
        });
        expect(res._getStatusCode()).toBe(200);
        expect(res._getJSONData().message).toBe('SMS sent successfully!');
        expect(res._getJSONData().plivoResponse).toEqual(mockPlivoResponse);
      });
    
       it('returns 500 if Plivo call fails', async () => {
         const plivoError = new Error('Plivo Error');
         mockPlivoCreate.mockRejectedValue(plivoError);
    
         const { req, res } = createMocks({
           method: 'POST',
           body: { to: '+19998887777', text: 'Test message' },
         });
    
         await handler(req, res);
    
         expect(res._getStatusCode()).toBe(500);
         expect(res._getJSONData().error).toContain('Failed to send SMS. Plivo Error: Plivo Error');
       });
    });

Integration Test Example (Testing Full API Flow):

javascript
// __tests__/integration/send-sms.integration.test.js
import { createServer } from 'http';
import { parse } from 'url';
import next from 'next';

describe('Integration: /api/send-sms', () => {
  let app;
  let server;
  let baseURL;

  beforeAll(async () => {
    // Set test environment variables
    process.env.PLIVO_AUTH_ID = 'test-id';
    process.env.PLIVO_AUTH_TOKEN = 'test-token';
    process.env.PLIVO_SENDER_ID = '+15550001111';

    // Start Next.js server
    app = next({ dev: false });
    const handle = app.getRequestHandler();
    await app.prepare();

    server = createServer((req, res) => {
      const parsedUrl = parse(req.url, true);
      handle(req, res, parsedUrl);
    });

    await new Promise((resolve) => {
      server.listen(0, () => {
        const { port } = server.address();
        baseURL = `http://localhost:${port}`;
        resolve();
      });
    });
  });

  afterAll(async () => {
    server.close();
    await app.close();
  });

  it('sends SMS successfully with valid input', async () => {
    const response = await fetch(`${baseURL}/api/send-sms`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ to: '+19998887777', text: 'Integration test' }),
    });

    expect(response.status).toBe(200);
    const data = await response.json();
    expect(data.message).toBe('SMS sent successfully!');
  });

  it('returns 400 with missing fields', async () => {
    const response = await fetch(`${baseURL}/api/send-sms`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ to: '+19998887777' }), // Missing text
    });

    expect(response.status).toBe(400);
    const data = await response.json();
    expect(data.error).toContain('Missing');
  });
});

Edge Cases to Test:

  • Empty string values: { to: "", text: "" } should return 400.
  • Very long messages: Test messages exceeding 1,600 characters (GSM) or 737 characters (Unicode).
  • Special characters: Test emojis, accented characters, Arabic/Chinese scripts to verify encoding.
  • International numbers: Test various country codes (+44, +91, +86, etc.).
  • Malformed JSON: Send invalid JSON to test parsing errors.

Now that you have a functional SMS sending API, consider exploring these related implementations:

For more advanced messaging features, explore our other guides on scheduling reminders, marketing campaigns, and WhatsApp integration.

Frequently Asked Questions

How do I handle SMS character limits with Plivo?

Standard SMS messages have a limit of 160 characters for GSM-7 encoding and 70 for UCS-2. Plivo automatically splits longer messages into segments, but be mindful of length as this affects cost. Plivo's smart encoding optimizes for these limitations.

How to send SMS with Next.js and Plivo?

Integrate the Plivo SMS API into a Next.js app by creating a serverless API route (/api/send-sms) that handles sending messages via the Plivo Node.js SDK. This route accepts POST requests with the recipient's number and message text, then uses your Plivo credentials to send the SMS through the Plivo API.

What is the Plivo Node.js SDK used for?

The Plivo Node.js SDK simplifies interaction with the Plivo API in your Next.js application. It provides convenient methods for sending SMS messages, making API calls, and handling responses, reducing the amount of boilerplate code you need to write.

Why does Next.js use API routes for sending SMS?

Next.js API routes keep your Plivo credentials and sending logic secure on the server-side, away from the client-side browser. This prevents exposing sensitive information and protects your API keys.

When should I use a Plivo phone number for sending SMS?

Using a Plivo phone number as your Sender ID is mandatory when sending SMS to the US and Canada. For other countries, check Plivo's documentation, as some allow pre-registered alphanumeric Sender IDs, but these may not be able to receive replies.

Can I use alphanumeric Sender IDs with Plivo?

While the US and Canada require Plivo numbers, some other countries permit alphanumeric Sender IDs (like "MyCompany"). However, these usually require prior registration with Plivo and might not be able to receive replies. Check Plivo's documentation for country-specific guidelines.

How to handle Plivo API errors in Next.js?

Implement a try...catch block around your Plivo API call in the Next.js API route to handle potential errors. Log the error details server-side, and return an appropriate HTTP status code and a user-friendly error message to the client, potentially including Plivo's specific error message.

What is the required phone number format for sending SMS with Plivo?

Plivo requires destination phone numbers to be in E.164 format, which includes a plus sign (+) followed by the country code, area code, and local number (e.g., +14155551212). Ensure proper formatting to avoid errors.

How to set up Plivo credentials in a Next.js project?

Store your Plivo Auth ID, Auth Token, and Sender ID in a .env.local file in your project's root directory. This file is automatically ignored by Git. Access these values in your code via process.env.VARIABLE_NAME.

How to test the Plivo SMS integration in my Next.js app?

Use tools like curl, Postman, or Insomnia to send test POST requests to your /api/send-sms endpoint. Check for the expected 200 OK response and verify SMS delivery on the recipient's phone. Also, examine Plivo's logs for message status details.

What are the security best practices when using Plivo in Next.js?

Store credentials securely in environment variables within a .env.local file (included in .gitignore), perform server-side API interactions, implement input validation, and add rate limiting to protect your endpoint from abuse.

How to troubleshoot "Authentication Credentials Invalid" error with Plivo?

Double-check that your Plivo Auth ID and Auth Token in .env.local match those in the Plivo Console. Restart your Next.js server after any changes to .env.local. Ensure .env.local is at the project root and process.env can access its values in your API route.

How can I deploy my Next.js Plivo integration to Vercel?

Push your project to a Git repository, import it into Vercel, configure the project, and add your Plivo credentials as environment variables in Vercel's project settings. Vercel's Git integration then enables automatic CI/CD for seamless deployments.

How to monitor the performance of my Plivo SMS integration in Next.js?

Utilize Vercel Analytics for function invocation details if deployed there. Integrate error tracking services and set up centralized logging. Plivo console logs offer insights into message status and API request details for debugging.