code examples
code examples
Vonage SMS Next.js Guide: Send SMS, Handle Delivery Receipts & Webhooks
Build a production-ready Next.js application with Vonage SMS API. Send messages, receive delivery receipts via webhooks, handle inbound SMS with TypeScript support. Includes webhook security, error handling, and deployment guide.
This guide provides a step-by-step walkthrough for building a Next.js application capable of sending SMS messages via the Vonage API, receiving delivery receipts (DLRs), and handling inbound SMS messages using webhooks.
Build a simple web form to send SMS messages and set up API routes (route handlers in Next.js App Router) to act as webhook endpoints for receiving status updates and incoming messages from Vonage. This enables real-time tracking of message delivery and allows your application to react to messages sent to your Vonage number.
<!-- GAP: Missing overview of architecture/data flow diagram opportunity (Type: Enhancement, Priority: Medium) -->Technologies Used:
<!-- DEPTH: Technologies section lacks specific version requirements and compatibility information (Priority: High) -->- Next.js (App Router): A React framework for building full-stack web applications. Uses the App Router for modern routing and server-side capabilities.
- Vonage SMS API: Sends SMS messages programmatically.
- Vonage Node.js SDK: Simplifies interaction with the Vonage APIs from a Node.js environment. Latest version 3.24.1 includes TypeScript support for improved code completion.
- Vercel (or similar platform): Deploys the Next.js application and obtains publicly accessible URLs required for Vonage webhooks.
- Zod (Optional): A library for schema declaration and validation, useful for validating form input.
Citation1. From source: https://www.npmjs.com/package/@vonage/server-sdk and https://github.com/Vonage/vonage-node-sdk, Title: Vonage Server SDK for Node.js Latest Version and Features 2024, Text: The latest version of the Vonage Server SDK for Node.js is 3.24.1. The SDK provides support for various Vonage APIs including SMS, Voice, Messages, Meetings, and more. Version 3 uses TypeScript to help with code completion in your preferred IDE. Most methods that interact with the Vonage API use Promises, which you can either resolve yourself or use await to wait for a response. Installation: npm i @vonage/server-sdk. Full API documentation available at developer.vonage.com.
Outcome:
By the end of this guide, you will have a functional Next.js application that can:
- Send SMS messages using a web form.
- Receive and log delivery status updates for sent messages.
- Receive and log inbound SMS messages sent to your Vonage number.
Prerequisites:
<!-- DEPTH: Prerequisites section lacks cost estimates for Vonage services (Priority: Medium) -->- Vonage API Account: Sign up at Vonage.com. Obtain your API Key and API Secret from the API Dashboard.
- Vonage Virtual Number: Rent a Vonage phone number with SMS capabilities via the Dashboard.
- Node.js: Version 18.17 or later installed. Download from nodejs.org.
- npm or yarn: Node.js package manager.
- Git: For version control and deployment.
- Basic understanding: Familiarity with JavaScript, React, Next.js, and terminal commands.
- Deployment Platform Account: A Vercel (vercel.com) or similar account to deploy your application.
1. Setting Up the Project
Initialize the Next.js project and configure the necessary environment variables and dependencies.
<!-- GAP: Missing project structure overview or file organization diagram (Type: Enhancement, Priority: Medium) -->-
Create a New Next.js Project: Open your terminal and run the following command to create a new Next.js project using the App Router:
bashnpx create-next-app@latest vonage-sms-nextjsWhen prompted, select the following options (or adjust as needed, but this guide assumes these settings):
textWhat is your project named? vonage-sms-nextjs Would you like to use TypeScript? No Would you like to use ESLint? Yes Would you like to use Tailwind CSS? Yes Would you like to use `src/` directory? No Would you like to use App Router? (recommended) Yes Would you like to customize the default import alias (@/*)? NoThis creates a new directory named
vonage-sms-nextjswith the basic project structure. -
Navigate into Project Directory:
bashcd vonage-sms-nextjs -
Declare Environment Variables: Create a file named
.env.localin the root of your project. This file stores your sensitive credentials and configuration. Never commit this file to Git.- Go to your Vonage API Dashboard. Your API Key and Secret are displayed at the top.
- Go to the Numbers > Your numbers section to find the Vonage virtual number you rented.
Add the following lines to `.env.local`, replacing the placeholder values with your actual credentials:
```.env.local
VONAGE_API_KEY=YOUR_VONAGE_API_KEY
VONAGE_API_SECRET=YOUR_VONAGE_API_SECRET
VONAGE_VIRTUAL_NUMBER=YOUR_VONAGE_VIRTUAL_NUMBER
```
* `VONAGE_API_KEY`: Your Vonage API key.
* `VONAGE_API_SECRET`: Your Vonage API secret.
* `VONAGE_VIRTUAL_NUMBER`: The Vonage phone number you rented, used as the sender ID (ensure it's in the format required by Vonage, usually E.164 format *without* the leading `+`).
4. Install Dependencies: Install the Vonage Node.js SDK to interact with the API and optionally Zod for input validation.
```bash
npm install @vonage/server-sdk zod
```
* `@vonage/server-sdk`: The official Vonage library for Node.js.
* `zod`: Used for validating the phone number and message text before sending.
5. Configure .gitignore:
Ensure .env.local is included in your .gitignore file (create-next-app usually adds it by default) to prevent accidentally committing your secrets.
```.gitignore
# ... other entries
.env.local
```
2. Implementing SMS Sending Functionality
Create a server action to handle sending the SMS securely on the server and a form component for the user interface.
<!-- GAP: Missing rate limiting considerations for SMS sending (Type: Substantive, Priority: High) -->-
Create the Server Action (
send-sms.js): Server Actions allow us to run server-side code directly in response to UI interactions, without needing to manually create API routes for simple form submissions.Create a directory
app/liband inside it, create a file namedsend-sms.js:javascript// app/lib/send-sms.js 'use server'; // Directive to mark this module's exports as Server Actions import { revalidatePath } from 'next/cache'; import { Vonage } from '@vonage/server-sdk'; import { z } from 'zod'; // Initialize Vonage client using environment variables const vonage = new Vonage({ apiKey: process.env.VONAGE_API_KEY, apiSecret: process.env.VONAGE_API_SECRET, }); const from = process.env.VONAGE_VIRTUAL_NUMBER; // Define a schema for validation using Zod const schema = z.object({ // Basic digits check (7-15 digits) // Note: This regex is basic. For stricter validation (e.g., E.164), // consider a more specific regex or a dedicated library. number: z.string().regex(/^\d{7,15}$/, 'Invalid phone number format (digits only, 7-15 length).'), text: z.string().min(1, 'Message text cannot be empty.').max(1600, 'Message text too long.'), // Max length for concatenated SMS });
// Define the Server Action function
export async function sendSMS(prevState, formData) {
try {
// Validate the form data against the schema
const validatedData = schema.parse({
number: formData.get('number'),
text: formData.get('text'),
});
// Send the SMS using the Vonage SDK
const vonageResponse = await vonage.sms.send({
to: validatedData.number,
from: from, // Your Vonage virtual number
text: validatedData.text,
});
console.log('Vonage API Response:', vonageResponse);
// Check the response from Vonage
// Status '0' indicates success for that specific message part
const messageStatus = vonageResponse.messages[0].status;
let responseMessage;
if (messageStatus === '0') {
responseMessage = `_ Message sent successfully to ${validatedData.number}. Message ID: ${vonageResponse.messages[0]['message-id']}`;
} else {
responseMessage = `_ There was an error sending the SMS. Status: ${messageStatus}, Error: ${vonageResponse.messages[0]['error-text']}`;
}
// Revalidate the page path if needed (useful if displaying sent messages)
revalidatePath('/');
// Return state to the form
return {
response: responseMessage,
};
} catch (error) {
console.error('Error sending SMS:', error);
// Handle Zod validation errors specifically
if (error instanceof z.ZodError) {
return {
response: `_ Validation Error: ${error.errors.map((e) => e.message).join(', ')}`,
};
}
// Handle general errors (API issues, network problems)
return {
response: `_ An unexpected error occurred: ${error.message}`,
};
}
}
```
<!-- EXPAND: Could benefit from code comments explaining message-id usage for tracking (Type: Enhancement, Priority: Low) -->
**Explanation:**
* `'use server'`: Marks this module for Server Actions.
* **Vonage Initialization:** Creates a Vonage client instance using credentials from `.env.local`.
* **Zod Schema:** Defines validation rules for the phone number (`number`) and message content (`text`). The regex comment highlights its basic nature.
* **`sendSMS` Function:** This is the core server action.
* It receives `prevState` (previous state from the form hook) and `formData`.
* `schema.parse`: Validates the incoming form data. Throws an error if validation fails.
* `vonage.sms.send`: Calls the Vonage API to send the SMS. `to`, `from`, and `text` are required parameters.
* **Response Handling:** Checks the `status` field in the `vonageResponse`. A status of `'0'` usually means the message was accepted for delivery. Other statuses indicate errors. Note that for long (concatenated) SMS messages, this only reflects the status of the *first part* of the message. Handling DLRs (Delivery Receipts) via webhooks is necessary for final delivery confirmation.
* `revalidatePath('/')`: Clears the cache for the homepage, useful if you were displaying data that needs updating after the action.
* **Error Handling:** Includes a `try...catch` block to handle validation errors (from Zod) and other potential errors during the API call.
2. Create the Form Component (send-form.jsx):
This client component provides the UI for entering the phone number and message. It uses React hooks (useFormState, useFormStatus) to manage form submission state and display responses from the server action.
Create a file named `app/send-form.jsx`:
```jsx
// app/send-form.jsx
'use client'; // This component runs on the client
import { useFormState, useFormStatus } from 'react-dom';
import { sendSMS } from '@/app/lib/send-sms'; // Import the server action
const initialState = {
response: null, // Initial state for the response message
};
// Custom submit button component to show pending state
function SubmitButton() {
const { pending } = useFormStatus(); // Hook to check if the form is submitting
return (
<button
type=""submit""
aria-disabled={pending}
disabled={pending} // Disable button while submitting
className=""mt-4 px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed flex justify-center items-center""
>
{pending ? (
<>
<div className=""border-gray-300 h-5 w-5 animate-spin rounded-full border-2 border-t-white mr-2""></div>
Sending...
</>
) : (
'Send SMS'
)}
</button>
);
}
// The main form component
export function SendForm() {
// useFormState links the form action to state management
const [state, formAction] = useFormState(sendSMS, initialState);
return (
<form action={formAction} className=""flex flex-col gap-y-4"">
<div>
<label htmlFor=""number"" className=""block text-sm font-medium text-gray-700"">
Phone Number (e.g., 447700900000):
</label>
<input
name=""number""
id=""number""
type=""tel"" // Use 'tel' for phone numbers
placeholder=""Enter destination phone number""
autoComplete=""tel""
required
className=""mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm""
/>
</div>
<div>
<label htmlFor=""text"" className=""block text-sm font-medium text-gray-700"">
Message:
</label>
<textarea
name=""text""
id=""text""
rows={4}
placeholder=""Enter your message here""
required
className=""mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm""
/>
</div>
<!-- EXPAND: Could add character counter for SMS length tracking (Type: Enhancement, Priority: Medium) -->
<SubmitButton />
{/* Display the response message from the server action */}
{state?.response && (
<p aria-live=""polite"" className={`mt-2 text-sm ${state.response.startsWith('_') ? 'text-red-600' : 'text-green-600'}`}>
{state.response}
</p>
)}
</form>
);
}
```
**Explanation:**
* `'use client'`: Marks this as a Client Component.
* **`useFormStatus`:** Provides the `pending` state, used in `SubmitButton` to show loading feedback.
* **`useFormState`:** Connects the `sendSMS` server action to the component's state (`state`). When the form is submitted, `sendSMS` runs, and its return value updates `state`, causing the UI to re-render and display the `response`.
* **Form Inputs:** Standard HTML inputs for phone number (`type=""tel""`) and message (`textarea`). `required` attribute ensures fields are not empty.
* **`aria-live=""polite""`:** Improves accessibility by announcing the response message when it appears.
3. Integrate Form into the Page (page.js):
Import and use the SendForm component in your main page file.
Update `app/page.js`:
```javascript
// app/page.js
import { SendForm } from './send-form'; // Import the form component
export default function Home() {
return (
<main className=""mx-auto max-w-xl my-8 p-6 border border-gray-200 shadow-md rounded-lg"">
<header className=""mb-6 pb-4 border-b border-gray-200"">
{/* You can add a Vonage logo here if desired */}
{/* <img src=""/vonage.svg"" alt=""Vonage"" className=""h-8 mb-2"" /> */}
<h1 className=""text-2xl font-semibold text-gray-800"">
Send SMS with Vonage & Next.js
</h1>
<p className=""text-sm text-gray-500"">
Enter a phone number and message to send an SMS.
</p>
</header>
<section>
<SendForm /> {/* Render the form component */}
</section>
</main>
);
}
```
4. Run the Development Server: Start your Next.js development server to test the sending functionality locally.
```bash
npm run dev
```
<!-- GAP: Missing local testing limitations and ngrok usage guidance (Type: Substantive, Priority: High) -->
Open your browser to `http://localhost:3000`. You should see the form. Try sending an SMS to your own phone number. Check the form's response message and confirm if you receive the SMS. Note that webhooks (delivery receipts, inbound messages) won't work locally without extra tooling like ngrok, which is why deployment is necessary for the next steps.
3. Receiving Delivery Receipts & Inbound SMS (Webhooks)
Vonage uses webhooks to push data to your application asynchronously. Deploy publicly accessible endpoints (URLs) in your application for Vonage to send delivery receipt status updates and inbound SMS messages.
Citation2. From source: https://api.support.vonage.com/hc/en-us/articles/226572227, Title: Vonage Webhook Timeout and Retry Policy, Text: Vonage has a 3-second timeout for establishing the HTTP connection and a 15-second timeout for receiving a response once the HTTP session has been set up. If Vonage establishes the connection but doesn't receive a 200 OK within 15 seconds, they will disconnect and try again 60 seconds later. Vonage will retry Inbound Message and Delivery Receipt callbacks every minute for 24 hours. A timeout will show as a '-1' response on the Dashboard Debug Tools.
- Explain Webhooks:
When you send an SMS, the initial API response (
status: 0) only confirms Vonage accepted the message. The actual delivery status (delivered, failed, etc.) comes later via a webhook. Similarly, when someone sends an SMS to your Vonage number, Vonage forwards it to your application via an inbound SMS webhook. Expose specific HTTP POST endpoints for these webhooks.
**Important:** Vonage expects a 200 OK response within 15 seconds. If not received, Vonage retries every 60 seconds for up to 24 hours. Make your webhook handlers **idempotent** (safe to process the same webhook multiple times) to handle potential duplicate deliveries.
2. Create Route Handlers for Webhooks:
Create API endpoints within the app directory using Next.js Route Handlers.
* **Delivery Receipt Handler:** Create the directory structure `app/webhook/status` and add a file named `route.js`:
```javascript
// app/webhook/status/route.js
import { NextResponse } from 'next/server';
// Handles POST requests from Vonage for Delivery Receipts (DLRs)
export async function POST(request) {
try {
const data = await request.json();
console.log('Received Delivery Receipt:', JSON.stringify(data, null, 2));
<!-- GAP: Missing concrete implementation examples for database operations (Type: Substantive, Priority: High) -->
// --- Business Logic Placeholder ---
// TODO: Process the delivery receipt data
// - Find the original message in your database using 'messageId'.
// - Update the message status based on 'data.status'.
// - Handle different statuses ('delivered', 'failed', 'expired', 'rejected', etc.).
// - Log errors ('err-code') for failed messages.
// Example: updateMessageStatus(data.messageId, data.status, data['err-code']);
// IMPORTANT: Implement idempotency checks to prevent duplicate processing
// Check if this messageId + status combination was already processed
// Example: if (await isAlreadyProcessed(data.messageId, data.status)) return Response 200
// ---------------------------------
// Crucially, return a 200 OK response to Vonage IMMEDIATELY
// Failure to do so will cause Vonage to retry every 60 seconds for 24 hours.
// Respond within 15 seconds to avoid timeouts.
return new Response('OK', { status: 200 });
} catch (error) {
console.error('Error processing delivery receipt webhook:', error);
// Return an error status, but Vonage might still retry if it's not a 2xx
return new Response('Error processing webhook', { status: 500 });
}
}
// Optionally handle GET requests for verification or health checks
export async function GET(request) {
return NextResponse.json({ message: 'Status webhook endpoint is active.' });
}
```
Citation3. From source: https://developer.vonage.com/en/messaging/sms/guides/delivery-receipts, Title: SMS Delivery Receipt Status Codes and Error Codes, Text: Delivery receipts include status and err-code fields. Status values: 'accepted' (accepted for delivery), 'delivered' (successfully delivered), 'buffered' (buffered for later delivery), 'expired' (could not be delivered within expiry time), 'failed' (not delivered), 'rejected' (carrier refused delivery), 'unknown' (no useful information). Error codes: 0 (delivered successfully), 1 (unknown error), 2 (absent subscriber temporary - retry), 3 (absent subscriber permanent - remove from database), 4 (call barred by user), 5 (portability error), 6 (anti-spam rejection), 7 (handset busy - retry), 8 (network error - retry), 9-17 (various errors), 20-23 (Fraud Defender rejections), 39 (illegal sender address for US), 41 (daily limit surpassed), 50-54 (regulation errors), 99 (general error). For concatenated SMS, you should receive a carrier DLR for each part. Handset DLRs for concatenated messages are delayed.
<!-- EXPAND: Could include table of all status codes and recommended handling strategies (Type: Enhancement, Priority: High) -->* **Inbound SMS Handler:** Create the directory structure `app/webhook/inbound` and add a file named `route.js`:
```javascript
// app/webhook/inbound/route.js
import { NextResponse } from 'next/server';
// Handles POST requests from Vonage for Inbound SMS messages
export async function POST(request) {
try {
const data = await request.json();
console.log('Received Inbound SMS:', JSON.stringify(data, null, 2));
// --- Business Logic Placeholder ---
// TODO: Process the inbound message
// - Identify the sender ('msisdn').
// - Store the message content ('text').
// - Check for keywords ('keyword').
// - Potentially trigger automated replies or application logic.
// Example: processInboundMessage(data.msisdn, data.text, data.messageId);
// ---------------------------------
<!-- GAP: Missing examples of automated reply patterns or keyword-based routing (Type: Substantive, Priority: Medium) -->
// Crucially, return a 200 OK response to Vonage
return new Response('OK', { status: 200 });
} catch (error) {
console.error('Error processing inbound SMS webhook:', error);
return new Response('Error processing webhook', { status: 500 });
}
}
// Optionally handle GET requests for verification or health checks
export async function GET(request) {
return NextResponse.json({ message: 'Inbound webhook endpoint is active.' });
}
```
**Explanation:**
* **`POST` Function:** This handles the incoming HTTP POST requests from Vonage.
* **`request.json()`:** Parses the JSON payload sent by Vonage.
* **`console.log`:** Logs the received data. In a production app, you'd replace this with proper logging and database operations.
* **Business Logic Placeholder:** Comments indicate where you would add your application-specific logic (e.g., updating database records, triggering replies).
* **`return new Response('OK', { status: 200 })`:** This is **critical**. You *must* return a `200 OK` (or `204 No Content`) response quickly to acknowledge receipt of the webhook. If Vonage doesn't receive a 2xx response, it will assume the delivery failed and retry sending the webhook, potentially leading to duplicate processing.
* **`GET` Function (Optional):** Provides a simple way to check if the endpoint is deployed and reachable via a browser or health check tool.
3. Deploy the Application: Webhooks require a publicly accessible URL. Deploy your application to a platform like Vercel.
<!-- DEPTH: Missing alternative deployment platform comparisons (Priority: Low) -->* **Push to GitHub/GitLab/Bitbucket:**
* Initialize a Git repository if you haven't already: `git init`
* Add files: `git add .`
* Commit changes: `git commit -m ""Initial commit with SMS sending and webhook handlers""`
* Create a repository on your preferred Git provider (e.g., GitHub).
* Add the remote origin: `git remote add origin <your-repo-url>`
* Push your code: `git push -u origin main` (or your default branch name)
* **Deploy on Vercel:**
* Sign up or log in to [Vercel](https://vercel.com/).
* Click ""Add New..."" > ""Project"".
* Import the Git repository you just pushed.
* **Configure Environment Variables:** Before deploying, navigate to the project settings in Vercel, find the ""Environment Variables"" section, and add `VONAGE_API_KEY`, `VONAGE_API_SECRET`, and `VONAGE_VIRTUAL_NUMBER` with their respective values. Ensure they are available for all environments (Production, Preview, Development).
* Click ""Deploy"". Vercel will build and deploy your application, providing you with public URLs (e.g., `https://your-project-name.vercel.app`).
4. Configure Vonage Webhook URLs: Now, tell Vonage where to send the delivery receipts and inbound messages.
<!-- GAP: Missing screenshots or visual guide for dashboard configuration (Type: Enhancement, Priority: Medium) -->* Go to your [Vonage API Dashboard Settings](https://dashboard.nexmo.com/settings).
* Find the ""SMS Settings"" section (or similar).
* **Delivery receipts (DLR) / Webhook URL for Status:** Enter the full URL of your deployed status webhook handler. Example: `https://your-project-name.vercel.app/webhook/status`
* **Inbound messages / Webhook URL for Inbound:** Enter the full URL of your deployed inbound webhook handler. Example: `https://your-project-name.vercel.app/webhook/inbound`
* Set the **HTTP Method** for both to `POST`.
* Save the settings.
4. Verification and Testing
With the application deployed and Vonage configured, verify everything works.
<!-- DEPTH: Lacks comprehensive testing checklist and validation steps (Priority: High) -->-
Test Sending:
- Navigate to your deployed application URL (e.g.,
https://your-project-name.vercel.app). - Use the form to send an SMS to your personal phone number.
- Confirm you receive the SMS.
- Check the success/error message displayed on the form.
- Navigate to your deployed application URL (e.g.,
-
Test Delivery Receipt Webhook:
- After sending the SMS, wait a few seconds or minutes (delivery time varies).
- Go to your Vercel dashboard, select your project, and navigate to the "Logs" or "Functions" tab.
- Look for logs corresponding to requests made to
/webhook/status. Inspect the logged JSON payload containing the delivery status (delivered,failed,expired, etc.) for the message you sent.
*Example DLR Payload (Failed):*
```json
{
"msisdn": "447700900001",
"to": "447700900000",
"network-code": "23410",
"messageId": "aaaaaaaa-bbbb-cccc-dddd-0123456789ab",
"price": "0.03330000",
"status": "failed",
"scts": "2310261030",
"err-code": "6",
"api-key": "YOUR_API_KEY",
"message-timestamp": "2023-10-26 10:30:55"
}
```
3. Test Inbound SMS Webhook:
* Using your personal phone, send an SMS message to your Vonage virtual number.
* Return to your Vercel logs.
* Look for logs corresponding to requests made to /webhook/inbound. Inspect the logged JSON payload containing the message text, sender number (msisdn), and other details.
*Example Inbound SMS Payload:*
```json
{
""msisdn"": ""447700900002"",
""to"": ""447700900000"",
""messageId"": ""bbbbbbbb-cccc-dddd-eeee-0123456789ac"",
""text"": ""Hello from my phone!"",
""type"": ""text"",
""keyword"": ""HELLO"",
""api-key"": ""YOUR_API_KEY"",
""message-timestamp"": ""2023-10-26 10:35:10""
}
```
5. Error Handling and Logging
<!-- DEPTH: Lacks production-grade error handling patterns and monitoring setup (Priority: High) -->- Server Action Errors: The
sendSMSfunction includestry...catchblocks to handle Zod validation errors and general API/network errors, returning informative messages to the UI. - Webhook Errors: The webhook route handlers include basic
try...catchblocks. In production, implement more robust error handling:- Use a dedicated logging service (e.g., Sentry, Logtail, Datadog) instead of
console.log. - Log detailed error information (stack trace, request body).
- Set up monitoring and alerting for webhook failures.
- Use a dedicated logging service (e.g., Sentry, Logtail, Datadog) instead of
- Vonage Errors: Pay attention to the
statusanderr-codefields in delivery receipts to understand why messages might fail. Consult the Vonage SMS API error codes documentation.
6. Security Considerations
<!-- DEPTH: Security section needs implementation code examples, not just descriptions (Priority: High) -->- API Credentials: Never hardcode API keys or secrets in your frontend or backend code. Use environment variables (
.env.locallocally, platform settings in deployment) and ensure.env.localis in.gitignore. - Input Validation: The Zod schema in
send-sms.jsprovides basic validation. Sanitize or validate all user input thoroughly to prevent injection attacks or unexpected behavior. - Webhook Security: Vonage SMS API supports message signature verification to ensure webhooks originate from Vonage and haven't been tampered with. Implement this in production:
- Enable Message Signing: Contact Vonage support (support@nexmo.com) to enable message signatures on your account.
- Get Signature Secret: Access your signature secret from the API Dashboard settings page. Select SHA-256 HMAC as the algorithm.
- Verify Signatures: When a webhook arrives, it includes a
sig,nonce, andtimestampfield. Use your signature secret to recreate the signature and verify it matches the receivedsigvalue. - Implementation: Use the Vonage SDK's signature verification methods or manually hash the concatenated form fields + secret using SHA-256 HMAC.
- Use HTTPS for your webhook URLs (Vercel provides this automatically).
- Implement a secret token system: Generate a unique secret, append it as a query parameter to the webhook URL in Vonage settings (
/webhook/status?token=YOUR_SECRET), and verify this token in your handler. - Rate Limiting: Protect your webhook endpoints from abuse by implementing rate limiting.
Citation4. From source: https://developer.vonage.com/en/blog/using-message-signatures-to-ensure-secure-incoming-webhooks-dr, Title: Vonage SMS Webhook Message Signature Verification, Text: Message signature verification is available on the Vonage SMS API. New accounts must contact support@nexmo.com to enable message signatures. Once enabled, access the signature secret from the API Dashboard settings page and select SHA-256 HMAC algorithm. When SMS webhooks arrive with message signing enabled, they include sig, nonce, and timestamp fields. A shared secret is a key shared between Vonage and your application, never transmitted but used by both to ensure data originates from expected server and hasn't been changed. To verify, get form fields in alphabetical order, concatenate into string of keys and values, append signature secret to the end, hash using SHA-256 HMAC algorithm. The resulting hash should match the sig value. Server SDKs provide signature verification methods.
Citation5. From source: https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations and https://nextjs.org/docs/app/guides/forms, Title: Next.js 15 Server Actions with useFormState and useFormStatus, Text: Next.js forms work with React Server Components and Server Actions using useFormState and useFormStatus hooks. The useFormStatus hook shows loading indicator while action executes and can only be used as a child of a form element using a Server Action. This requires creating a separate component to render the loading indicator. The useFormStatus hook retrieves a boolean pending value indicating if the form is pending. The useFormState hook manages state returned from Server Actions. These hooks enable form handling with progressive enhancement and server-side validation.
<!-- GAP: Missing signature verification implementation code example (Type: Critical, Priority: High) -->7. Troubleshooting and Caveats
- Environment Variables Not Loaded: Ensure
.env.localis correctly formatted and placed in the project root. On deployment platforms like Vercel, configure environment variables in the project settings. Restart your local dev server after changing.env.local. - Webhooks Not Receiving Data:
- Check URLs: Double-check the webhook URLs configured in the Vonage dashboard match your deployed application's URLs exactly (including
https://and the correct path/webhook/statusor/webhook/inbound). - Deployment Status: Ensure your application is successfully deployed and running. Check Vercel deployment logs for build or runtime errors.
- Return 200 OK: Verify your webhook handlers always return a
200 OKresponse quickly. Check Vercel function logs for errors within your handlers. If your logic takes too long, Vonage might time out and retry. Process webhook data asynchronously (e.g., push to a queue). - Firewall Issues: If self-hosting, ensure your server's firewall allows incoming POST requests from Vonage's IP ranges.
- Check URLs: Double-check the webhook URLs configured in the Vonage dashboard match your deployed application's URLs exactly (including
- Incorrect Vonage Number Format: Ensure the
tonumber insendSMSand yourVONAGE_VIRTUAL_NUMBERare in the correct format (usually E.164 without leading+). - Vonage Number Capabilities: Confirm your rented Vonage number is SMS-enabled for the region you're sending to/receiving from.
- Country-Specific Restrictions: Sending SMS to certain countries may require pre-registration or adherence to specific regulations (e.g., sender ID rules). Check Vonage Country-Specific Features and Restrictions.
- Zod Validation Errors: Check the specific error message returned by Zod (logged or sent to the UI) to identify which validation rule failed (e.g., invalid phone format, empty text).
8. Deployment and CI/CD (Vercel Example)
Vercel provides seamless CI/CD via Git integration.
- Connect Git Repository: Complete during the initial deployment setup.
- Environment Variables: Configure in Vercel project settings. Set separate variables for Production, Preview, and Development branches if needed.
- Automatic Deployments: By default, Vercel automatically deploys:
- New commits to the production branch (e.g.,
main) to your production URL. - New commits to other branches or pull requests to unique preview URLs.
- New commits to the production branch (e.g.,
- Rollbacks: The Vercel dashboard allows instant rollbacks to previous deployments if issues occur. Navigate to the "Deployments" tab, find a previous successful deployment, and promote it to production.
9. Complete Code Repository
A complete, working example of this project can be found on GitHub:
https://github.com/vonage-community/vonage-nextjs-sms-guide
Frequently Asked Questions
<!-- DEPTH: FAQ section could benefit from more technical depth and edge cases (Priority: Medium) -->How do Vonage SMS delivery receipts work?
Delivery receipts (DLRs) are webhooks sent by Vonage after an SMS is delivered. The initial API response (status: 0) only confirms Vonage accepted the message. The actual delivery status (delivered, failed, expired, rejected) arrives later via a webhook to your configured endpoint. DLRs include status and err-code fields for tracking delivery outcomes.
What is the timeout for Vonage webhooks?
Vonage has a 3-second timeout for establishing HTTP connections and a 15-second timeout for receiving responses. If Vonage doesn't receive a 200 OK within 15 seconds, it retries every 60 seconds for up to 24 hours. Always respond within 15 seconds to avoid timeouts and implement idempotent webhook handlers.
How do I secure Vonage SMS webhooks?
Enable message signature verification by contacting support@nexmo.com. Once enabled, webhooks include sig, nonce, and timestamp fields. Access your signature secret from the API Dashboard, select SHA-256 HMAC algorithm, and verify signatures by hashing form fields + secret. Use HTTPS, implement rate limiting, and optionally add secret tokens to webhook URLs.
What are the main Vonage SMS delivery receipt error codes?
Error code 0 means delivered successfully. Common errors: 2 (absent subscriber temporary - retry), 3 (absent subscriber permanent - remove), 6 (anti-spam rejection), 8 (network error - retry), 20-23 (Fraud Defender rejections), 39 (illegal sender for US), 50-54 (regulation errors). Check err-code and status fields in DLRs for troubleshooting.
How do Next.js Server Actions work with Vonage SMS?
Server Actions run server-side code in response to form submissions without manual API routes. Use 'use server' directive to mark functions as Server Actions. Combine with useFormState for state management and useFormStatus for loading indicators. Server Actions keep API credentials secure on the server and provide progressive enhancement.
Why are my Vonage webhooks being called multiple times?
Vonage retries webhooks every 60 seconds for 24 hours if it doesn't receive a 200 OK response. Implement idempotent webhook handlers that check if a messageId + status combination was already processed before updating your database. Always return 200 OK immediately, even if processing fails internally.
What is the latest Vonage Node.js SDK version?
The latest version is 3.24.1, which includes TypeScript support for improved IDE code completion. Install with npm i @vonage/server-sdk. Version 3 uses Promises for async operations and supports SMS, Voice, Messages, Meetings, and other Vonage APIs. Full documentation at developer.vonage.com.
How do I handle concatenated SMS delivery receipts?
For long SMS messages split into multiple parts, you receive a carrier DLR for each part. Handset DLRs for concatenated messages are delayed because the target handset must process all parts before acknowledging receipt. Track all message parts using the messageId and handle partial delivery scenarios in your webhook handler.
What environment variables does Vonage require for Next.js?
Store VONAGE_API_KEY, VONAGE_API_SECRET, and VONAGE_VIRTUAL_NUMBER in .env.local for local development. Never commit .env.local to Git. On deployment platforms like Vercel, configure these in project settings environment variables. Ensure they're available for Production, Preview, and Development environments.
How do I deploy a Vonage SMS Next.js app to Vercel?
Push your code to GitHub, connect the repository to Vercel, configure environment variables (VONAGE_API_KEY, VONAGE_API_SECRET, VONAGE_VIRTUAL_NUMBER) in project settings, and deploy. Vercel provides HTTPS URLs automatically. Configure webhook URLs in Vonage Dashboard: https://your-project.vercel.app/webhook/status and /webhook/inbound. Set HTTP method to POST.
Conclusion
You have successfully built a Next.js application that leverages the Vonage SMS API to send messages and uses webhooks to receive delivery receipts and handle inbound SMS. This guide covered project setup, server actions for sending, route handlers for webhooks, deployment, and Vonage configuration.
<!-- EXPAND: Could include next steps, advanced features, and production checklist (Type: Enhancement, Priority: Medium) -->This forms a solid foundation for building more complex SMS-based features, such as two-factor authentication, appointment reminders, customer support chat via SMS, or automated responses to inbound messages. Implement robust error handling, logging, and security measures for production applications.
Frequently Asked Questions
How to send SMS with Next.js and Vonage?
You can send SMS messages by creating a Next.js server action that uses the Vonage Node.js SDK. This action handles sending messages securely on the server-side and interacts with a form component on the front-end. The form collects the recipient's phone number and the message text, which are then sent to the Vonage API via the SDK.
What is a Vonage delivery receipt (DLR)?
A Vonage Delivery Receipt (DLR) is a status update sent via webhook to your application, confirming the delivery status of an SMS message. It provides information on whether the message was successfully delivered, failed, or expired. The DLR is crucial for tracking message delivery beyond the initial 'accepted' status.
How to handle inbound SMS messages in Next.js?
Inbound SMS messages are handled using a webhook route handler in your Next.js application. When someone sends a message to your Vonage number, Vonage forwards it to this endpoint as a POST request. The handler processes the message content and sender number, allowing your application to respond or trigger actions.
Why does Vonage use webhooks for delivery receipts?
Vonage uses webhooks for delivery receipts because message delivery is an asynchronous process. The initial API response only confirms message acceptance, not final delivery. Webhooks provide real-time status updates as they become available without requiring your application to constantly poll the API.
When should I use Zod in my Next.js SMS application?
Zod is beneficial for validating user input in your Next.js SMS application. By defining schemas, you ensure data integrity and prevent invalid phone numbers or message text from being processed. This helps avoid unexpected errors and improves the reliability of your application.
What are the prerequisites for using Vonage SMS API?
Prerequisites include a Vonage API account, a rented Vonage virtual number with SMS capabilities, Node.js and npm or yarn, Git, and a deployment platform account (like Vercel). Basic understanding of JavaScript, React, Next.js, and terminal commands is also recommended.
How to set up environment variables in Next.js for Vonage?
Create a `.env.local` file in the root of your Next.js project and add your `VONAGE_API_KEY`, `VONAGE_API_SECRET`, and `VONAGE_VIRTUAL_NUMBER`. This file should be added to your `.gitignore` to prevent sensitive information from being committed to version control. On platforms like Vercel, set environment variables within the project's settings.
What Next.js version and features are used in this guide?
The guide uses the latest version of Next.js with the App Router, which provides improved routing and server-side capabilities. This simplifies the process of setting up API routes and handling webhook requests.
Can I use a local development environment for Vonage webhooks?
Testing webhooks locally requires tools like ngrok to create a public tunnel to your local server, as Vonage needs a publicly accessible URL. The guide emphasizes deployment for proper webhook functionality due to this requirement.
How to set up Vonage webhook URLs for SMS?
Configure webhook URLs in your Vonage API Dashboard settings. Provide the full public URLs of your deployed Next.js route handlers for delivery receipts (DLRs) and inbound messages, ensuring the HTTP method is set to POST. These URLs are where Vonage will send webhook data.
How to troubleshoot Vonage webhook delivery issues?
To troubleshoot webhook issues, double-check URL accuracy in the Vonage dashboard, verify successful deployment, ensure your handler returns a 200 OK response, and check firewalls if self-hosting. Inspect your Vercel function logs for errors, and ensure correct Vonage number format and SMS capabilities.
What security measures are recommended for a Next.js Vonage app?
Key security measures include using environment variables for API credentials, implementing thorough input validation with tools like Zod, and potentially incorporating secret tokens or rate limiting for webhooks to prevent misuse or unauthorized access.
Where to find a complete code example for the Next.js Vonage SMS guide?
A complete, working code repository can be found on GitHub at: [https://github.com/vonage-community/vonage-nextjs-sms-guide](https://github.com/vonage-community/vonage-nextjs-sms-guide). This allows you to explore a fully functional implementation of the concepts discussed in the guide.