Frequently Asked Questions
Use the Vonage API and the Node.js Server SDK. Set up an Express API endpoint that accepts the recipient's number and message, then uses the SDK to send the SMS via Vonage.
The Vonage API, a CPaaS platform, provides APIs for SMS, voice, video, and other communication services. In this Node.js application, we use it for sending text messages programmatically.
Dotenv loads environment variables from a .env
file into process.env
. It's essential for securely managing sensitive credentials like your Vonage API key and secret, keeping them out of your codebase.
Always validate phone numbers, especially in production. While the example provides a basic regex check, use a robust library like libphonenumber-js
for accurate international validation and to prevent errors.
This tutorial focuses solely on sending SMS messages. Receiving messages requires setting up webhooks and is covered in separate Vonage documentation.
Install Express, the Vonage Server SDK, and dotenv. Create an endpoint (e.g., '/send') that accepts a POST request with 'phone' and 'message', then uses the SDK to send the SMS.
It's the phone number purchased or assigned within your Vonage account that SMS messages will be sent from. For trial accounts, use registered test numbers instead.
Separating the Vonage interaction (lib.js) from the server logic (index.js) improves code organization, testability, and makes swapping services or adding features easier.
Implement try...catch
blocks to handle errors from the Vonage SDK. Log the errors and return appropriate error responses to the client. Consider retry mechanisms for transient errors in production.
If you need to store message history, user data, or schedule messages for later delivery, you'll need a database (e.g., PostgreSQL, MongoDB) and a data access layer.
Use input validation, rate limiting (express-rate-limit
), authentication/authorization, and HTTPS. Manage API secrets securely via environment variables and never commit them to code.
E.164 is an international standard phone number format. It includes a '+' followed by the country code, area code, and subscriber number (e.g., +14155550100). Use this format for consistency and to avoid ambiguities.
This typically occurs with Vonage trial accounts. You're trying to send to a number not added to your verified Test Numbers list in the Vonage Dashboard.
Use platforms like Heroku, Vercel, or AWS. Configure environment variables directly in the platform, never commit your .env file. Use a process manager like PM2 for reliability.
Build Two-Way SMS in Next.js with Plivo, NextAuth & Webhooks
Learn how to receive and respond to inbound SMS messages in Next.js using Plivo webhooks, NextAuth authentication, and Prisma for message storage. This comprehensive tutorial shows you how to build a production-ready two-way SMS messaging system that enables real-time conversations, automated replies, and complete message history tracking.
Implement bidirectional SMS communication for customer support, appointment confirmations, interactive surveys, or chatbot workflows. This guide covers webhook configuration, XML response generation, secure authentication, database schema design, and deployment strategies for building sophisticated SMS applications with Plivo and Next.js.
What Is Two-Way SMS Messaging?
Two-way SMS messaging enables your application to send and receive text messages, creating interactive conversations with users. Unlike one-way SMS sending, two-way messaging allows users to reply to your messages, ask questions, provide feedback, or trigger automated workflows. Plivo delivers webhooks within 1–3 seconds of message receipt, with a 10-second timeout for your endpoint response.
Common Use Cases:
How Plivo Webhooks Enable Two-Way Messaging:
When someone sends an SMS to your Plivo number, Plivo sends an HTTP POST request (webhook) to a URL you configure. This webhook contains the message details: sender's phone number (
From
), your Plivo number (To
), message text (Text
), and metadata. Your Next.js API route processes this webhook, stores the message in your database, and sends an automated reply by returning XML in the response.Key Benefits:
Source: Plivo Blog – Receive and Respond to SMS in Node.js
Project Overview and Architecture
What You'll Build:
/api/sms/webhook
) to receive Plivo webhooksTechnologies Used:
Compatibility Note: Next.js 14+ requires Node.js 18.17 or higher. NextAuth v5 beta is required for App Router support.
System Architecture:
Prerequisites:
Estimated Setup Time: 45–60 minutes for first-time implementation
Final Outcome:
1. Setting Up the Project
Create a new Next.js project with TypeScript_ install dependencies_ and configure your development environment.
1.1 Create Next.js Application:
This scaffolds a Next.js 14+ project with TypeScript_ Tailwind CSS_ ESLint_ App Router_ and a
src/
directory structure.1.2 Install Required Dependencies:
Package Purposes:
plivo
: Official Plivo Node.js SDK for sending SMSnext-auth@beta
: NextAuth.js v5 (Auth.js) for authentication (App Router compatible)prisma
&@prisma/client
: Database ORM and clientbcryptjs
: Password hashing for admin authentication@types/bcryptjs
: TypeScript definitionsPeer Dependency Note:
next-auth@beta
requires React 18+ and Next.js 14+. Ensurepackage.json
includes"react": "^18.0.0"
and"next": "^14.0.0"
.1.3 Initialize Prisma:
This creates:
prisma/schema.prisma
: Database schema definition.env
: Environment variables file (automatically added to.gitignore
)1.4 Configure Environment Variables:
Open
.env
and add your configuration:How to Obtain Plivo Credentials:
Generate NextAuth Secret:
Validate Environment Variables:
1.5 Create Project Structure:
2. Database Schema and Prisma Configuration
Define your database schema to store messages, users, and conversation metadata. This schema supports 10,000+ messages with sub-100ms query times using indexed lookups.
2.1 Update
prisma/schema.prisma
:Schema Design Rationale:
fromNumber
,toNumber
, andcreatedAt
(typical query time < 50ms)Trade-off: No foreign key between Message and Conversation to avoid cascade delete complexity. Use application-level referential integrity checks.
2.2 Create and Run Migration:
If Migration Fails:
2.3 Create Prisma Client Singleton (
src/lib/db.ts
):This singleton pattern prevents "Too many Prisma Client instances" errors during Next.js hot reloading in development.
3. How to Configure Plivo Webhooks for Inbound SMS
Initialize the Plivo SDK and configure your Plivo number to send webhooks to your Next.js application for receiving inbound messages.
3.1 Create Plivo Client (
src/lib/plivo.ts
):3.2 Configure Plivo Number Webhook URL:
https://your-ngrok-url.ngrok.io/api/sms/webhook
POST
For Local Development (ngrok):
Install and start ngrok to expose your local server:
Copy the ngrok HTTPS URL (e.g.,
https://abc123.ngrok.io
) and update your Plivo number's Message URL tohttps://abc123.ngrok.io/api/sms/webhook
.Important: Each time you restart ngrok, the URL changes (on free tier). Update your Plivo configuration accordingly or use a paid ngrok plan for a persistent domain.
Alternatives to ngrok:
4. Implementing the Webhook API Route to Receive SMS
Create a Next.js API route to receive and process Plivo webhooks for inbound SMS messages.
4.1 Create Webhook Endpoint (
src/app/api/sms/webhook/route.ts
):Key Implementation Details:
application/x-www-form-urlencoded
data, so userequest.formData()
MessageUUID
to prevent duplicate processing during webhook retries<Message>
element sends SMSupsert()
to automatically update conversation metadataAuto-Reply Configuration: Extract
generateAutoReply()
to a configuration file or database table for runtime updates without code deployment.Source: Plivo Blog – Receive SMS Node.js
5. Setting Up NextAuth for Admin Authentication
Configure NextAuth.js to secure your admin dashboard with session-based authentication.
5.1 Create Auth Configuration (
src/auth.ts
):Security Hardening:
/api/auth/signin
to prevent brute force attacks5.2 Create API Route Handlers (
src/app/api/auth/[...nextauth]/route.ts
):5.3 Create Admin User Seed Script (
prisma/seed.ts
):⚠️ Production Security: Change the default password immediately after first deployment. Consider using environment variable for initial password or requiring password change on first login.
Update
package.json
:Run seed:
6. Building the Admin Dashboard for SMS Management
Create a protected admin interface to view conversations and send manual replies.
6.1 Create Middleware (
src/middleware.ts
):Security Enhancement: Add CSRF protection using NextAuth's built-in CSRF tokens and configure security headers (CSP, X-Frame-Options, etc.) via
next.config.js
.6.2 Create Messages Dashboard (
src/app/dashboard/messages/page.tsx
):Performance Optimization: For large conversation lists (1,000+), implement cursor-based pagination using Prisma's
cursor
andskip
options. Add search functionality with full-text search indexes onphoneNumber
andmessageText
.6.3 Create Message List Component (
src/app/dashboard/messages/MessageList.tsx
):Real-Time Updates: Add WebSocket support using
pusher-js
or Server-Sent Events (SSE) to automatically refresh message list when new messages arrive. Alternatively, implement polling withsetInterval()
every 5–10 seconds.7. How to Send Manual SMS Replies in Next.js
Create an API route and form component for sending manual SMS replies from the dashboard.
7.1 Create Send SMS API Route (
src/app/api/sms/send/route.ts
):Opt-Out Compliance: The route now checks
conversation.status
before sending to prevent messaging users who have opted out. This ensures TCPA/GDPR compliance.7.2 Create Reply Form Component (
src/app/dashboard/messages/[phone]/ReplyForm.tsx
):Enhancement Ideas: Add message templates dropdown, emoji picker, contact tagging, and scheduled sending for business hours.
8. Implementing Security Best Practices
Secure your two-way SMS system against common vulnerabilities and abuse.
8.1 Webhook Signature Verification:
Plivo signs webhook requests using HMAC-SHA256. Verify signatures to ensure webhooks come from Plivo.
Update
src/app/api/sms/webhook/route.ts
:Source: Plivo Webhook Security – ngrok Documentation
8.2 Rate Limiting:
Implement rate limiting to prevent abuse of your SMS sending endpoints.
Install
@upstash/ratelimit
:Update
src/app/api/sms/send/route.ts
:Configuration Guidelines:
8.3 Input Validation & Sanitization:
Always validate and sanitize user inputs, especially phone numbers and message text.
8.4 Environment Variable Protection:
Never expose sensitive credentials in client-side code or commit them to version control.
Update
.gitignore
:Security Checklist:
.gitignore
9. Testing Your Two-Way SMS System
Validate your implementation with comprehensive testing strategies.
9.1 Local Development Testing:
Start Next.js Dev Server:
Start ngrok Tunnel:
Update Plivo Webhook URL:
https://abc123.ngrok.io
)https://abc123.ngrok.io/api/sms/webhook
Send Test SMS:
Verify Database Storage:
Message
table for inbound and outbound entriesConversation
table updatedWebhook Debugging Tips:
http://127.0.0.1:4040
for webhook requests9.2 Webhook Payload Testing:
Use curl to simulate Plivo webhooks locally:
9.3 Admin Dashboard Testing:
http://localhost:3000/login
admin@example.com
/admin123
/dashboard/messages
Unit Test Example (Jest + Prisma):
10. How to Deploy Your Two-Way SMS Application
Deploy your Next.js application to production with persistent webhook URLs.
10.1 Deploy to Vercel (Recommended):
Alternative Hosting Platforms:
@netlify/plugin-nextjs
10.2 Update Plivo Production Webhook:
After deployment:
https://your-app.vercel.app
)https://your-app.vercel.app/api/sms/webhook
10.3 Configure Production Database:
Use a managed PostgreSQL provider:
Zero-Downtime Deployment Strategy:
npx prisma migrate deploy
Run migrations on production database:
10.4 Monitor Production Webhooks:
Check Plivo webhook logs:
Monitoring Tools:
Production Checklist:
Frequently Asked Questions
How do I test Plivo webhooks locally without ngrok?
While ngrok is the most common solution, alternatives include Cloudflare Tunnel (free persistent domains), localtunnel (
npm install -g localtunnel && lt --port 3000
), or VS Code Port Forwarding (built-in for GitHub Codespaces). Cloudflare Tunnel offers better reliability for production testing with persistent URLs.What happens if my webhook endpoint returns an error?
Plivo retries failed webhooks up to 5 times with exponential backoff (1s, 2s, 4s, 8s, 16s). To prevent duplicate message processing, always return
200 OK
immediately upon receiving the webhook, then process the message asynchronously. Store theMessageUUID
to detect and ignore duplicate deliveries.How do I handle SMS delivery receipts (DLRs)?
Configure a Delivery URL in your Plivo number settings pointing to
/api/sms/delivery
. Create a corresponding API route that updates yourMessage
record's status field based on theStatus
parameter:Can I send MMS (images) with Plivo in Next.js?
Yes. Use
plivoClient.messages.create()
with amedia_urls
parameter containing an array of publicly accessible image URLs (HTTPS required). Plivo supports JPEG, PNG, and GIF formats up to 5 MB per MMS. Note that MMS pricing is 3–5× higher than SMS.How do I implement conversation threading by phone number?
Query messages with
db.message.findMany({ where: { fromNumber: phoneNumber }, orderBy: { createdAt: 'asc' } })
to display a threaded conversation view. Use WebSocket or polling for real-time updates in the admin dashboard. TheConversation
model aggregates metadata for efficient thread listing.What's the best way to handle opt-outs (STOP messages)?
When receiving "STOP", "UNSUBSCRIBE", or similar keywords, immediately update the
Conversation
status to"opted_out"
and cease sending messages to that number:Comply with TCPA (US) and GDPR (EU) by honoring opt-outs within seconds. Send confirmation: "You've been unsubscribed. Reply START to re-subscribe."
Compliance Requirements by Jurisdiction:
How do I secure my Plivo webhooks against spoofing?
Always verify Plivo's webhook signature using the
X-Plivo-Signature-V3
header with HMAC-SHA256 validation (implementation shown in Section 8.1). Usecrypto.timingSafeEqual()
for timing-safe comparison. Additionally, whitelist Plivo's IP ranges in your firewall or Vercel/Netlify security rules for defense-in-depth.Can I use Plivo with the Next.js Pages Router instead of App Router?
Yes. Convert API routes to the Pages Router format: create
pages/api/sms/webhook.ts
withexport default function handler(req, res)
instead of Next.js 13+ route handlers. NextAuth setup differs slightly—useNextAuth(authOptions)
inpages/api/auth/[...nextauth].ts
. Database and Plivo integration remain identical.How do I handle international phone numbers and country codes?
Always use E.164 format (
+[country code][number]
) for phone numbers. Installlibphonenumber-js
for robust validation:Plivo supports 190+ countries with varying SMS pricing and regulations. Check country-specific carrier guidelines in the Plivo Console.
What's the cost of sending SMS with Plivo?
Plivo pricing varies by destination country. US/Canada SMS typically costs $0.0040–$0.0075 per message segment (160 characters). International rates range from $0.01–$0.50 per segment. Check current pricing at plivo.com/pricing.
Cost Estimation Examples:
Monitor usage via Plivo Console dashboard. Set spending limits to prevent unexpected bills. Volume discounts available for enterprise accounts (500K+ messages/month).