code examples
code examples
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:
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.
-
Create a Next.js App: Open your terminal and run the following command. Replace
plivo-nextjs-smswith 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).bashnpx create-next-app@latest plivo-nextjs-sms -
Navigate to Project Directory:
bashcd plivo-nextjs-sms -
Install Plivo Node.js SDK: Add the Plivo helper library to your project dependencies.
bashnpm install plivoor using yarn:
bashyarn add plivo -
Set Up Environment Variables: Securely store your Plivo credentials. Create a file named
.env.localin 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.localis Next.js's standard way to load these variables during development and is included in.gitignoreby default.
- Purpose: Environment variables prevent hardcoding sensitive credentials directly into your codebase.
-
Verify
.gitignore: Ensure that.env*.localis listed in your project's.gitignorefile (create-next-appusually 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 --forceand retry. - Port 3000 already in use: Kill the process using port 3000 (
lsof -ti:3000 | xargs killon macOS/Linux) or specify a different port withnpm run dev -- -p 3001. - TypeScript errors: This guide uses JavaScript. If you selected TypeScript during setup, rename
.jsfiles to.tsor.tsxand 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.
-
Create the API Route File: Inside the
pagesdirectory, create a folder namedapi. Insideapi, create a file namedsend-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
- Project Structure:
-
Implement the API Logic: Open
pages/api/send-sms.jsand 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
plivoSDK. - 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) andtext(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...catchblock 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):
(Note: Thejson
{ "message": "SMS sent successfully!", "plivoResponse": { "message": "message(s) queued", "message_uuid": ["xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"], "api_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" } }plivoResponseobject reflects the actual fields returned by the Plivo API, which typically use snake_case likemessage_uuidandapi_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):
orjson
{ "error": "Server configuration error." }json{ "error": "Failed to send SMS. Plivo Error: Invalid 'dst' parameter" }
- 400 Bad Request (Input Validation):
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).
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)
// 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)
// 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.
-
Sign Up/Log In: Go to the Plivo Console.
-
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.
-
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.
-
Update
.env.local: Paste the copied Auth ID, Auth Token, and your Plivo Number (Sender ID) into the respective variables in your.env.localfile.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) -
Restart Development Server: Crucially, after modifying
.env.local, stop (Ctrl+C) and restart your Next.js development server (npm run devoryarn 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:
- Update
.env.local(development) immediately. - Update environment variables in deployment platforms (Vercel, AWS, etc.) before the old token expires.
- 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 a400 Bad Request. - Configuration Errors: Check for missing environment variables and return a
500 Internal Server Error. - Plivo API Errors: Catch exceptions from the
plivo.Clientcall. 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.logfor successful Plivo responses andconsole.errorfor 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/errorwith 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.
- Retriable (5xx errors, network timeouts):
- Practical Implementation: Use the
async-retrylibrary to wrap Plivo API calls. Install withnpm install async-retry.
// 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
curlrequest missing thetoortextfield. Expect a 400 response. - Invalid Credentials: Temporarily modify
PLIVO_AUTH_IDorPLIVO_AUTH_TOKENin.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
tonumber (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):
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.localis 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
toandtextprevents trivial errors.- Enhancement: Add more robust validation for the
tofield to ensure it resembles an E.164 formatted number before sending it to Plivo. Libraries likelibphonenumber-jscan 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).' }); } - Enhancement: Add more robust validation for the
- 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-flexibleor 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:
// 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:
// 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/healthendpoint. - 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.errorwith 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_IDorPLIVO_AUTH_TOKENin.env.localis incorrect or missing. Environment variables are not loaded correctly. - Solution: Double-check credentials in
.env.localagainst the Plivo Console. Restart the Next.js server after any changes to.env.local. Ensure.env.localis in the project root. Verifyprocess.env.PLIVO_AUTH_IDis accessible within the API route code (add temporaryconsole.log).
- Cause:
- Error: Missing
toortextfield:- Cause: The client request did not include the required fields in the JSON body, or they were not parsed correctly.
- Solution: Verify the
curlcommand or frontend code is sending a valid JSON body with both fields. Check thereq.bodyobject in the API route usingconsole.log. EnsureContent-Type: application/jsonheader is set in the request.
- Error: Invalid
dstparameter / Number requires + prefix:- Cause: The
tonumber 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.
- Cause: The
- 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.localrequire 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_IDis 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:
- Push to Git: Ensure your project is hosted on a Git provider like GitHub, GitLab, or Bitbucket. Make sure
.env*.localis in your.gitignore. - Import Project: Sign up or log in to Vercel. Click "Add New…" > "Project". Import your Git repository.
- Configure Project: Vercel usually detects Next.js automatically.
- Add Environment Variables: Navigate to the project settings in Vercel. Go to the "Environment Variables" section. Add
PLIVO_AUTH_ID,PLIVO_AUTH_TOKEN, andPLIVO_SENDER_IDwith their corresponding values. Ensure they are set for "Production" (and "Preview"/"Development" if needed). This is critical for the deployed application to work. - Deploy: Click the "Deploy" button. Vercel builds and deploys your application.
- Access: Once deployed, Vercel provides a URL (e.g.,
your-project-name.vercel.app). Your API endpoint is available athttps://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.,
mainormaster), 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:
- Navigate to the "Deployments" tab.
- Locate the last known good deployment.
- Click the three-dot menu (⋮) next to that deployment.
- Select "Promote to Production."
- 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:
- Deploy: Deploy the application to Vercel (or run locally
npm run dev). - Set Environment Variables: Ensure
PLIVO_AUTH_ID,PLIVO_AUTH_TOKEN, andPLIVO_SENDER_IDare correctly set (in.env.localfor local, Vercel dashboard for deployed). - 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-smsendpoint (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.
- Check Response: Verify the API returns a
200 OKstatus and the expected JSON success payload. - Check Recipient: Confirm the SMS message is received on the destination phone.
- 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.
- 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
plivoSDK 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):
// __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.
Next Steps and Related Tutorials
Now that you have a functional SMS sending API, consider exploring these related implementations:
- Two-Factor Authentication (2FA): Implement OTP-based authentication using SMS
- Bulk SMS Broadcasting: Send messages to multiple recipients efficiently
- Delivery Status Tracking: Set up webhooks to monitor SMS delivery
- Inbound SMS Handling: Process incoming messages for two-way communication
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.