Send, Receive and Handle SMS Delivery Receipts with Next.js and Vonage
Published on January 9, 2024

Introduction

The Vonage SMS API enables you to send and receive text messages to and from users worldwide, using our REST APIs. In this tutorial, we will focus on sending and receiving SMS messages and handling delivery receipts with Next.js using the SMS API. Next.js is a React framework for creating full-stack web apps. It uses React Components for UI development and incorporates extra features and optimizations via Next.js. If you are interested in learning more, you can access the Vonage SMS API documentation.

tldr; If you would like to skip ahead and get right to deploying it, you can find all the code for the app on GitHub.

Table of Contents

  1. Prerequisites

  2. How to send an SMS with Next.js

  3. How to receive an SMS delivery receipts and handle SMS with Next.js

Prerequisites

This tutorial assumes you have a basic understanding of Git, Next.js, React and JavaScript. Before you begin, make sure you have the following:

  • Vonage API account - Access your Vonage API Dashboard to locate your API Key and API Secret, which can be found at the top of the page.

  • Vonage Virtual Number - Rent a virtual phone number to send or receive messages and phone calls.

  • Node.js 18.17 or later - Node.js is an open-source, cross-platform JavaScript runtime environment.

  • Deployment Platform such as Vercel, Netlify, etc. - We’ll set up webhooks for the URLs that receive inbound SMS and delivery receipts. This way, we’ll be able to receive SMS and handle delivery receipts.

How to Send an SMS with Next.js

For the initial section of this tutorial, we’ll explore how to send text messages using Next.js. We will use the Vonage SMS API to interact with Next.js. To send an SMS with Next.js and the Vonage SMS API, proceed with these instructions:

  1. How to Create a new Next.js project

  2. How to Declare environment variables

  3. How to Install the Vonage Node.js SDK

  4. How to Install the validation library Zod

  5. How to Create a server-only form

  6. How to Run the development server

  7. How to Deploy to Vercel

1. How to Create a New Next.js Project

To create a Next.js app, run:

npx create-next-app@latest

On installation, you’ll see the following prompts:

What is your project named? vonage-send-sms 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 (@/*)? No

In this tutorial, we’ll use the App Router and JavaScript, so you can choose the same answers as above. After the prompts, create-next-app will create a folder with your project name and install the required dependencies.

2. How to Declare Environment Variables

Retrieve your API key and API secret from your API settings, also get your virtual number from the your numbers page, and create a .env.local file with the following environment variables:

VONAGE_API_KEY=your-vonage-api-key
VONAGE_API_SECRET=your-api-secret
VONAGE_VIRTUAL_NUMBER=your-virtual-number

To rent a Vonage virtual number:

  1. Sign in to the developer dashboard.

  2. In the left-hand navigation menu, click Numbers then Buy numbers.

  3. Choose the attributes you need and then click Search.

    1. In this tutorial we only need SMS capability. But you can use the same number to add other voice functionality as well!

  4. Click the Buy button next to the number you want and validate your purchase.

  5. Your virtual number is now listed in Your numbers.

See rent a virtual number.

3. How to Install the Vonage Node.js SDK

We’ll use the Vonage Node.js SDK. It’s so easy to send an SMS with Next.js.

To install the Node.js SDK, run:

npm install @vonage/server-sdk

4. How to Install the Validation Library Zod

Zod is a TypeScript-first schema declaration and validation library. We will use it to check the values after the form is submitted. But it is not mandatory, you may skip this step if you want.

To install the Zod, run:

npm install zod

5. How to Create a Server-Only Form

Create a new folder called lib inside /app. Then, create a new send-sms.js file inside the lib folder with the following content:

"use server";

import { revalidatePath } from "next/cache";
import { Vonage } from "@vonage/server-sdk";

After importing the dependencies, initialize the Vonage node library installed earlier:

const vonage = new Vonage({
  apiKey: process.env.VONAGE_API_KEY,
  apiSecret: process.env.VONAGE_API_SECRET,
});

const from = process.env.VONAGE_VIRTUAL_NUMBER;

Then initialize the Vonage node library, we will create an async function called sendSMS:

export async function sendSMS(prevState, formData) {
  try {
    const vonage_response = await vonage.sms.send({
      to: formData.get("number"),
      from,
      text: formData.get("text"),
    }); 

    revalidatePath("/");
    return {
      response:
        vonage_response.messages[0].status === "0"
          ? `🎉 Message sent successfully.`
          : `There was an error sending the SMS. ${
              // prettier-ignore
              vonage_response.messages[0].error-text
            }`,
    };
  } catch (e) {
    return {
      response: `There was an error sending the SMS. The error message: ${e.message}`,
    };
  }
}

To send an SMS message using the SMS API, we’ll use the vonage.messages.send method of the Vonage Node.js library. This method accepts objects as parameters that contain information about the recipient, sender, and content. The SMS API has two types of response (vonage_response), one is message sent and the other is error. See SMS responses.

Example response for the Message Sent:

{
   "message-count": "1",
   "messages": [
      {
         "to": "447700900000",
         "message-id": "aaaaaaaa-bbbb-cccc-dddd-0123456789ab",
         "status": "0",
         "remaining-balance": "3.14159265",
         "message-price": "0.03330000",
         "network": "12345",
         "client-ref": "my-personal-reference",
         "account-ref": "customer1234"
      }
   ]
}

Example response for the Error:

{
   "message-count": "1",
   "messages": [
      {
         "status": "2",
         "error-text": "Missing to param"
      }
   ]
}

We’ll invalidate an entire route segment with revalidatePath, that allows you to purge cached data on-demand for a specific path. It does not return any value. See revalidatePath.

For more advanced server-side validation, use the Zod library. If you use it, the send-sms.js file should look like this:

"use server";

import { revalidatePath } from "next/cache";
import { Vonage } from "@vonage/server-sdk";
import { z } from "zod";

const vonage = new Vonage({
  apiKey: process.env.VONAGE_API_KEY,
  apiSecret: process.env.VONAGE_API_SECRET,
});

const from = process.env.VONAGE_VIRTUAL_NUMBER;

const schema = z.object({
  number: z
    .string()
    .regex(new RegExp(/^\d{10,}$|^(\d{1,4}-)?\d{10,}$/), "Invalid Number!"),
  text: z.string().min(1, "Type something, please!").max(140, "Too long text!"),
});

export async function sendSMS(prevState, formData) {
  try {
    const data = schema.parse({
      number: formData.get("number"),
      text: formData.get("text"),
    });

    const vonage_response = await vonage.sms.send({
      to: data.number,
      from,
      text: data.text,
    }); 

    revalidatePath("/");
    return {
      response:
        vonage_response.messages[0].status === "0"
          ? `🎉 Message sent successfully.`
          : `There was an error sending the SMS. ${
              // prettier-ignore
              vonage_response.messages[0].error-text
            }`,
    };
  } catch (e) {
    return {
      response: `There was an error sending the SMS. The error message: ${e.message}`,
    };
  }
}

For UI, create a new send-form.jsx file inside /app with the following content:

"use client";

import { sendSMS } from "@/app/lib/send-sms";
import { useFormStatus, useFormState } from "react-dom";

const initialState = {
  response: null,
};

function SubmitButton() {
  const { pending } = useFormStatus();

  return (
    <button type="submit" aria-disabled="{pending}" classname="border rounded-md hover:bg-slate-50 p-2 flex justify-center items-center">
      {pending ? (
        <>
          <div class="border-gray-300 h-5 w-5 animate-spin rounded-full border-2 border-t-blue-600 mr-2">
          Sending...
        
      ) : (
        "Send"
      )}
    </div></button>
  );
}

export function SendForm() {
  const [state, formAction] = useFormState(sendSMS, initialState);

  return (
    <form action="{formAction}" classname="flex flex-col gap-y-2">
      <label htmlfor="number">Phone number:</label>
      <input name="number" id="number" type="number" placeholder="909009009099" autocomplete="off" classname="border rounded p-2" required="">
      <label htmlfor="text">Message:</label>
      <textarea name="text" id="text" rows="{4}" cols="{40}" placeholder="Hello from Next.js App!" classname="border rounded p-2" required="">      <SubmitButton />
      <p aria-live="polite">{state?.response}</p>
    </form>
  );
}
</code></pre>
<p>You can use the <code>useFormStatus</code> hook to display a loading status when a form is being submitted to the server. Another hook, the <code>useFormState</code>, allows you to update state based on a form action result.</p>
<p>Finally, you can import the <code>send-form.jsx</code> file to the <code>page.js</code> inside /app.</p>
<pre><code class="language-javascript">import { SendForm } from "./send-form";

export default function Home() {
  return (
    <main className="mx-auto max-w-3xl my-3 p-3 border border-slate-300 shadow rounded-lg divide-y divide-solid">
      <header className="flex flex-row p-3 items-center justify-between">
        <img src="/vonage.svg" alt="Vonage" />
        <h2 className="text-lg font-medium">Send SMS with the Vonage APIs</h2>
      </header>
      <section className="pt-3">
        <SendForm />
      </section>
    </main>
  );
}
</code></pre>
<p><a href="https://hackmd.io/xZLg5zcoQZugNV1OZaY8eQ?utm_source=comment-card&utm_medium=icon#Note-You-can-find-the-vonagesvg-in-the-sample-repository" title="Note-You-can-find-the-vonagesvg-in-the-sample-repository"></a><strong>Note:</strong> You can find the vonage.svg file in the sample repository.</p>
<h2><a href="https://hackmd.io/xZLg5zcoQZugNV1OZaY8eQ?utm_source=comment-card&utm_medium=icon#6-Run-the-development-server" title="6-Run-the-development-server"></a>6. How to Run the Development Server</h2>
<p>In the first step of this section, we created a directory called <code>vonage-send-sms</code>. Now let’s <code>cd</code> into it through the terminal:</p>
<pre><code class="language-shell">cd vonage-send-sms
</code></pre>
<p>Then, run the following command:</p>
<pre><code class="language-shell">npm run dev
</code></pre>
<p>This command launches the development server for your Next.js application on port <strong>3000</strong>. To see your home page, open <a href="http://localhost:3000/">http://localhost:3000</a> from your browser. It should look like this:</p>
<p><img src="https://d226lax1qjow5r.cloudfront.net/blog/blogposts/send-receive-and-handle-sms-delivery-receipts-with-next-js-and-vonage/send-sms-nextjs-form.png" alt="Next.js test form to send SMS" title="Next.js test form to send SMS"></p>
<h2><a href="https://hackmd.io/xZLg5zcoQZugNV1OZaY8eQ?utm_source=comment-card&utm_medium=icon#7-Deploy-to-Vercel" title="7-Deploy-to-Vercel"></a>7. How to Deploy to Vercel</h2>
<p>We’ll use Vercel to deploy our app for this tutorial but you can also deploy your project with Netlify. Vercel has several options to deploy your project such as Git, Vercel CLI, etc. The most popular method for creating a deployment on Vercel is by pushing code to Git repositories, so we’ll use Git with GitHub. You can start using it for free.</p>
<h3><a href="https://hackmd.io/xZLg5zcoQZugNV1OZaY8eQ?utm_source=comment-card&utm_medium=icon#Create-a-GitHub-Account" title="Create-a-GitHub-Account"></a>Create a GitHub Account</h3>
<p>To begin using GitHub, create a free account on <a href="https://github.com/">GitHub.com</a> and confirm your email address.</p>
<h3><a href="https://hackmd.io/xZLg5zcoQZugNV1OZaY8eQ?utm_source=comment-card&utm_medium=icon#Push-your-project-to-GitHub" title="Push-your-project-to-GitHub"></a>Push your project to GitHub</h3>
<p>Before the deployment, let’s upload our Next.js application to GitHub. The repository can be shared with everyone or kept private. You don’t have to set it up with a README or any other files.</p>
<p>To push to GitHub, run these commands, replacing <code><username></code> with your GitHub username:</p>
<pre><code class="language-shell">git remote add origin https://github.com/<username>/vonage-send-sms.git
git push -u origin main
</code></pre>
<p>Check out <a href="https://docs.github.com/en/get-started/quickstart/create-a-repo">this guide</a> on GitHub if you need help pushing your app.</p>
<h3><a href="https://hackmd.io/xZLg5zcoQZugNV1OZaY8eQ?utm_source=comment-card&utm_medium=icon#Create-a-Vercel-Account" title="Create-a-Vercel-Account"></a>Create a Vercel Account</h3>
<p>To get started with Vercel, go to <a href="https://vercel.com/signup">https://vercel.com/signup</a> to create a Vercel account. Choose Continue with GitHub and go through the registration process.</p>
<p><img src="https://d226lax1qjow5r.cloudfront.net/blog/blogposts/send-receive-and-handle-sms-delivery-receipts-with-next-js-and-vonage/vercel-signup-page.png" alt="Vercel Sign Up Page" title="Vercel Sign Up Page"></p>
<h3><a href="https://hackmd.io/xZLg5zcoQZugNV1OZaY8eQ?utm_source=comment-card&utm_medium=icon#Import-your-vonage-send-sms-repository" title="Import-your-vonage-send-sms-repository"></a>Import your <code>vonage-send-sms</code> repository</h3>
<p>After you’re signed up to Vercel and you push your app to GitHub, you can <a href="https://vercel.com/new">import your repository</a> into Vercel. You just need to give it access to your repository, or All Repositories on this step. You can do it here: <a href="https://vercel.com/import/git">https://vercel.com/import/git</a>.</p>
<p><img src="https://d226lax1qjow5r.cloudfront.net/blog/blogposts/send-receive-and-handle-sms-delivery-receipts-with-next-js-and-vonage/vercel-new-project-page.png" alt="Vercel New Project Page" title="Vercel New Project Page"></p>
<p>You can also deploy the sample repository we created for you in this step in one step with the deploy button If you want. But don’t forget to define Environment Variable Keys.</p>
<p><a href="https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Femrecoban%2Fvonage-send-sms"><img src="https://vercel.com/button" alt="Deploy with Vercel"></a></p>
<p>When the deployment is complete, you will receive URLs. Click on one of the links to access the Vonage Send SMS form in its live version. It’s that easy!</p>
<p>Once you deploy your app, Vercel will deploy every push by default.</p>
<blockquote>
<p>In order to send SMS to some countries (like Turkey), virtual numbers must comply with country specific regulations. <a href="https://api.support.vonage.com/hc/en-us/sections/200622473-Country-Specific-Features-and-Restrictions">Checkout Country-Specific Features and Restrictions</a> for more information.</p>
</blockquote>
<h2>2. How to Receive SMS and SMS Delivery Receipts with Next.js</h2>
<p>When you successfully request the SMS API, it sends you an array of message objects, with each message having a status of <code>0</code> to indicate success. However, this does not guarantee that your recipients have received the message. To receive delivery receipts in our Next.js app, we must provide <strong>a webhook endpoint</strong> for Vonage to send them to.</p>
<ol>
<li>Create a webhook endpoint</li>
<li>Create a handler for POST requests</li>
<li>Add a response to the handler</li>
<li>Configure the API Settings</li>
<li>Receive an SMS and handle delivery receipts</li>
</ol>
<h3>1. Create a webhook endpoint</h3>
<p>Create a new folder called <code>webhook</code> inside <code>/app</code>. Then, create a new folder one more called <code>status</code> inside the <code>webhook</code> folder. After that, create a new <code>route.js</code> file inside the <code>status</code> folder. The folder structure should look like this:</p>
<pre><code class="language-text">.
└── vonage-send-sms
    └── app
        ├── webhook
        │   └── status
        │       └── route.js
        ├── page.js
        └── layout.js
</code></pre>
<p>Since we will do exactly the same to capture incoming SMS, we will create another folder called <code>inbound</code> with the <code>route.js</code> file under the <code>webhook</code> folder:</p>
<pre><code class="language-text">.
└── vonage-send-sms
    └── app
        ├── webhook
        │   ├── status
        │   │   └── route.js
        │   └── inbound
        │       └── route.js
        ├── page.js
        └── layout.js
</code></pre>
<h3><a href="https://hackmd.io/xZLg5zcoQZugNV1OZaY8eQ?utm_source=comment-card&utm_medium=icon#2-Create-a-handler-for-POST-requests" title="2-Create-a-handler-for-POST-requests"></a>2. Create a handler for POST requests</h3>
<p>We will create a handler for POST requests at <code>/webhook/status</code> to handle delivery receipts and at <code>/webhook/inbound</code> to handle incoming SMS messages. Then, we’ll log the body of the request to the console:</p>
<pre><code class="language-javascript">export async function POST(request) {
    const res = await request.json();
    console.log("The request from Vonage: ", JSON.stringify(res, null, 2));
}
</code></pre>
<p>Right now, you can see the message status on the logs and also add the message status to your database or something else.</p>
<h3><a href="https://hackmd.io/xZLg5zcoQZugNV1OZaY8eQ?utm_source=comment-card&utm_medium=icon#3-Add-a-response-to-the-handler" title="3-Add-a-response-to-the-handler"></a>3. Add a response to the handler</h3>
<p>Vonage will assume that you have not received the message and will keep resending it for the next 24 hours. This way, the webhook endpoint must send a <code>200 OK</code> or <code>204 No Content</code> response:</p>
<pre><code class="language-javascript">export async function POST(request) {
    const res = await request.json();
    console.log("The request from Vonage: ", JSON.stringify(res, null, 2));

    return new Response("ok", {
      status: 200,
    });
}
</code></pre>
<h3><a href="https://hackmd.io/xZLg5zcoQZugNV1OZaY8eQ?utm_source=comment-card&utm_medium=icon#4-Configure-the-API-Settings" title="4-Configure-the-API-Settings"></a>4. Configure the API Settings</h3>
<p>Your webhook endpoint is now deployable on either Vercel, Netlify or your own server. After deploying your app, fill out each field by appending <code>/webhook/inbound</code> and <code>/webhook/status</code> to the Inbound SMS URL and Delivery Receipts URL in the <a href="https://dashboard.nexmo.com/settings">API settings</a>.</p>
<p><img src="https://d226lax1qjow5r.cloudfront.net/blog/blogposts/send-receive-and-handle-sms-delivery-receipts-with-next-js-and-vonage/sms-settings-nextjs.png" alt="Configure SMS API Setting" title="Configure SMS API Setting"></p>
<h3>5. Receive an SMS and handle delivery receipts</h3>
<p>To see delivery receipts and incoming SMS messages on Vercel:</p>
<ol>
<li>Choose your Next.js App from the <a href="https://vercel.com/dashboard">Vercel Dashboard</a>.</li>
<li>Go to your project overview and select the Logs tab.</li>
<li>From here, you can see, filter, and search the runtime logs.</li>
</ol>
<p><img src="https://d226lax1qjow5r.cloudfront.net/blog/blogposts/send-receive-and-handle-sms-delivery-receipts-with-next-js-and-vonage/vercel-logs-display.png" alt="Vercel Runtime Logs" title="Vercel Runtime Logs"></p>
<p>Now, send an SMS message through your Next.js application and you should be able to see the requests from Vonage on the runtime logs.</p>
<p>The response from Vonage will look something like this:</p>
<pre><code class="language-javascript">{
  "msisdn": "905423247231",
  "to": "***9512387",
  "network-code": "28603",
  "messageId": "4a3cf988-570f-4cdb-95be-179f89c64498",
  "price": "0.02380000",
  "status": "failed",
  "scts": "2311031929",
  "err-code": "1",
  "api-key": "******",
  "message-timestamp": "2023-11-03 19:29:32"
}
</code></pre>
<p>See <a href="https://developer.vonage.com/en/messaging/sms/guides/delivery-receipts#understanding-the-delivery-receipt">Understanding the delivery receipt</a>.</p>
<p>To see how incoming SMS messages appear in the console log, send an SMS message from your phone to your Vonage number:</p>
<pre><code class="language-javascript">{
  "msisdn": "905423247231",
  "to": "***9512387",
  "messageId": "2B00000018726238",
  "text": "Hi! This is test message from Emre!",
  "type": "text",
  "keyword": "HI!",
  "api-key": "******",
  "message-timestamp": "2023-11-03 19:55:15"
}
</code></pre>
<p>See <a href="https://developer.vonage.com/en/messaging/sms/guides/inbound-sms#anatomy-of-an-inbound-message">Anatomy of an Inbound Message</a>.</p>
<h1>Conclusion</h1>
<p>Now that you know how to send and receive SMS messages and get delivery receipts with the Vonage SMS API and Next.js. Consider expanding this project by replying to incoming SMS messages or adding more complex, interactive elements.</p>
<p>As always, feel free to reach out on the <a href="https://developer.vonage.com/en/community/slack">Vonage Developer Slack</a> or <a href="https://twitter.com/emreshepherd">Twitter</a> for any questions or feedback. Thank you for reading, and I look forward to connecting with you next time.</p>
<p>If you liked this post, reach out to Emre! He has been looking for a job for a long time :)</p>
<h1>Further Resources</h1>
<ul>
<li><a href="https://developer.vonage.com/en/blog/enable-video-call-for-a-next-js-application-using-vonage-video-api">Enable Video Call for a Next.js Application Using Vonage Video API</a></li>
<li><a href="https://developer.vonage.com/en/blog/show-sms-notifications-in-the-browser-with-next-js-ably-and-vercel">Show SMS Notifications in the Browser with Next.JS, Ably, and Vercel</a></li>
<li>Next.js <a href="https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations">Server Actions and Mutations</a></li>
<li>Next.js <a href="https://nextjs.org/docs/app/building-your-application/routing/route-handlers">Route Handlers</a></li>
</ul>
</textarea></form>
Emre CobanGuest Author

Emre is a software developer focusing on Next.js and React. He is passionate about learning new things about programming and helping others learn programming language. In the past, he has experimented with different programming languages including Classic ASP, Java, and Python.

Ready to start building?

Experience seamless connectivity, real-time messaging, and crystal-clear voice and video calls-all at your fingertips.