Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for Guide to sending SMS with the Twilio API
Sanity.io profile imageJoseph Udonsak
Joseph Udonsak forSanity.io

Posted on • Originally published atsanity.io

     

Guide to sending SMS with the Twilio API

Introduction

In a fast-paced environment, it is becoming increasingly important that software systems keep customers abreast with the latest information - whether or not they are actively interacting with the system. This is especially true in situations where the customer needs to be notified of events that weren't a direct result of their activity on the system (such as an order being made for a product or payment being received for a delivered service).

These days, notifications can either be sent via email, SMS, or even via popular messaging platforms like WhatsApp or Telegram. However, SMS notifications top the bunch because, unlike the other notification mediums, the customer is not required to have an internet connection in order to receive SMS messages. Additionally, SMS messages will be more accessible since there is the chance that more than a few customers may not even have a WhatsApp or Telegram account to start with.

Twilio is a platform that allows you to connect with customers everywhere they want to interact with you in real-time —from SMS messages to emails, phone calls to video, intelligent chatbots, and back. With helper libraries for popular programming languages, Twilio makes managing/dispatching programmable SMS a breeze.

In this tutorial, we'll build an e-commerce web application using Next.js and implement an SMS notification feature. We'll also learn aboutReact Context which will be used to implement the cart functionality. The content for the e-commerce site will be hosted (and served) using Sanity.io. A customer can make a selection of items and purchase them. Once a customer has purchased an item, a text message will be sent to the phone number provided by the customer, confirming the order details and the expected delivery date in the message body. The application we will build can be previewed here. The code for the complete project is available on Github should you get stuck at any point.

Prerequisites

To keep up with the concepts that will be introduced in this article, you will need a fair grasp of JavaScript ES6. While we won't delve too deep into React and Next.js, it will be helpful to have some understanding of both.

You will also need to have the following installed on your workstation:

  1. Node Package Manager (NPM)
  2. Sanity CLI
  3. AGithub account
  4. ATwilio account
  5. A code editor of your choice

Getting started

Setting up Sanity

For this article, we'll be using theNext.js E-commerce official starter to build our web application. To get started, open thestarter page in your browser. A screenshot of the starter page is shown below

Next.js E-commerce official starter page

Follow the instructions on the starter page to set up the project. In addition to receiving an email with the details of the Vercel deployment and newly created Sanity project, a Github repository will be created for you to clone and start working with. Clone the repository using the following command:

git clone https://github.com/<YOUR_GITHUB_ID>/next-js-e-commerce.git sanity-twiliocdsanity-twilio
Enter fullscreen modeExit fullscreen mode

This command pulls the repository into a folder namedsanity-twilio. Before we start adding code to the project, let's set our environment variables. To do this, make a copy of the.env.test file named.env.

cp .env.test .env
Enter fullscreen modeExit fullscreen mode

In the.env file, replace theSANITY_STUDIO_API_PROJECT_ID andNEXT_PUBLIC_SANITY_PROJECT_ID with your sanity project id.

We also need to provide the project details for our sanity studio project. In thestudio directory, edit theapi node insanity.json as follows:

"api":{"projectId":"INSERT_YOUR_PROJECT_ID","dataset":"production"},
Enter fullscreen modeExit fullscreen mode

Next, install the project dependencies using npm

npminstall
Enter fullscreen modeExit fullscreen mode

Start the application using NPM.

npm run dev
Enter fullscreen modeExit fullscreen mode

By default, the application will run on port 3000. Navigate tohttp://localhost:3000/ to see the application.

Index page of Next.js E-commerce web application

Next, we need to set up a messaging service on our Twilio account. To do this, head to yourTwilio console and click theCreate Messaging Service button.

Twilio console with 'Messaging Services' selected

We'll name this serviceSanity E-commerce and our use case is to notify the users. Complete the form as shown below and clickCreate.

Modal form to create messaging service

Next, we need to add a sender to our service. In our case, we need a phone number. ClickAdd Senders and selectPhone Number then clickContinue.

Modal displayed when 'Add sender' is clicked

Finally, you will need to add at least one phone number to this service. If you don't have one (or need a new one), you canbuy more numbers. Select a phone number and clickAdd Phone Numbers to complete the set up.

Form to add sender phone number

Click on theProperties menu option to view theMessaging Service SID. Make a copy of this as we will use it to integrate our e-commerce web application with Twilio.

Properties of the newly created messaging service

In addition to the Messaging Service ID, you will also need your Twilio phone number,ACCOUNT SID andAUTH TOKEN to integrate with Twilio. You can find these on yourdashboard.

Twilio dashboard showing 'ACCOUNT SID' and 'AUTH TOKEN'

As a security precaution, yourAUTH TOKEN will not be shown on screen, click on the copy icon to copy it.

Making the 'Cart' work

At the moment, the cart feature of our application doesn't work. We can't add or remove items to our cart. We can't even see it. Let's fix that before we implement the checkout feature.

To manage the cart in this application, we'll take advantage of theReact Context API. To get started, create a file namedcontext.js in theutils directory. This context will control the visibility of the cart and the items in the cart. It will also provide helper functions that will allow us to add or remove items from the cart, or update the quantity of items in the cart. Finally, it will also provide a hook that allows us to access exactly what we need from the context in each component.

touchutils/context.js
Enter fullscreen modeExit fullscreen mode

Add the following tocontext.js

// utils/context.jsimport{createContext,useContext,useState}from"react";constCartContext=createContext();constCartContextProvider=({children})=>{const[cart,setCart]=useState([]);const[cartOpen,setCartVisibility]=useState(false);constremoveAllItemsFromCart=()=>{setCart([]);};constshowCart=()=>{setCartVisibility(true);};consthideCart=()=>{setCartVisibility(false);};consttoggleCartVisibility=()=>{setCartVisibility(oldVisibility=>!oldVisibility);};constfindOrderInCart=productId=>cart.find(({id})=>id===productId);constupdateOrderQuantity=(productId,newQuantity)=>{constpredicate=({id})=>id===productId;setCart(oldCart=>{constorder=oldCart.find(predicate);order.quantity=newQuantity;constorderIndex=oldCart.findIndex(predicate);constnewCart=[...oldCart];newCart[orderIndex]=order;returnnewCart;});};constaddOrderToCart=order=>{constorderInCart=findOrderInCart(order.id);if(!orderInCart){setCart(oldCart=>[...oldCart,order]);}else{constnewOrderQuantity=orderInCart.quantity+order.quantity;updateOrderQuantity(order.id,newOrderQuantity);}showCart();};constremoveOrderFromCart=productId=>{setCart(oldCart=>oldCart.filter(({id})=>id!==productId));showCart();};return(<CartContext.Providervalue={{cart,addOrderToCart,findOrderInCart,removeOrderFromCart,updateOrderQuantity,cartOpen,showCart,hideCart,toggleCartVisibility,removeAllItemsFromCart}}>{children}</CartContext.Provider>);};exportconstuseCartContext=()=>{constcontext=useContext(CartContext);if(context===undefined){thrownewError("useCartContext must be used within a CartContextProvider");}returncontext;};exportdefaultCartContextProvider;
Enter fullscreen modeExit fullscreen mode

Next, we need to wrap the root component of our application with theCartContextProvider. To do this, openpages/_app.js and update it to match the following:

// pages/_app.jsimport"../styles/index.css";importLayoutfrom"../components/Layout";importCartContextProviderfrom"../utils/context";functionMyApp({Component,pageProps}){return(<CartContextProvider><Layout><Component{...pageProps}/></Layout></CartContextProvider>);}exportdefaultMyApp;
Enter fullscreen modeExit fullscreen mode

Next, we need a component that will render a single item in our cart. An item in our cart will be represented as an object comprising of the following:

  1. The product id.
  2. The title (name) of the product.
  3. The slug for the product. This will be used to create a link to the product page.
  4. The price for a single unit of the product.
  5. The number of units the customer intends to order.
  6. The product image.

In thecomponents directory, create a new file calledCartItem.js.

touchcomponents/CartItem.js
Enter fullscreen modeExit fullscreen mode

InCartItem.js add the following:

// components/CartItem.jsimport{urlFor}from"../utils/sanity";importLinkfrom"next/link";import{useCartContext}from"../utils/context";constCartItem=({id,title,slug,price,quantity,mainImage})=>{const{removeOrderFromCart,updateOrderQuantity}=useCartContext();consthandleRemoveButtonClick=()=>{removeOrderFromCart(id);};constreduceOrderQuantity=()=>{if(quantity>1){updateOrderQuantity(id,quantity-1);}else{removeOrderFromCart(id);}};constincreaseOrderQuantity=()=>{updateOrderQuantity(id,quantity+1);};return(<divclassName="flex justify-between mt-6"><divclassName="flex"><buttononClick={handleRemoveButtonClick}className="text-gray-600 focus:outline-none mx-2"><svgclassName="h-5 w-5"fill="none"strokeLinecap="round"strokeLinejoin="round"strokeWidth="2"viewBox="0 0 24 24"stroke="currentColor"><pathd="M6 18L18 6M6 6l12 12"/></svg></button><imgclassName="h-20 w-20 object-cover rounded"src={urlFor(mainImage).auto("format").fit("crop").width(750).quality(80)}alt=""/><divclassName="mx-3"><Linkhref={`/products/${slug.current}`}><a><h3className="text-sm text-gray-600">{title}</h3></a></Link><divclassName="flex items-center mt-2"><buttononClick={increaseOrderQuantity}className="text-gray-500 focus:outline-none focus:text-gray-600"><svgclassName="h-5 w-5"fill="none"strokeLinecap="round"strokeLinejoin="round"strokeWidth="2"viewBox="0 0 24 24"stroke="currentColor"><pathd="M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z"/></svg></button><spanclassName="text-gray-700 mx-2">{quantity}</span><buttononClick={reduceOrderQuantity}className="text-gray-500 focus:outline-none focus:text-gray-600"><svgclassName="h-5 w-5"fill="none"strokeLinecap="round"strokeLinejoin="round"strokeWidth="2"viewBox="0 0 24 24"stroke="currentColor"><pathd="M15 12H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z"/></svg></button></div></div></div><spanclassName="text-gray-600">${quantity*price}</span></div>);};exportdefaultCartItem;
Enter fullscreen modeExit fullscreen mode

The cart item provided as a prop to theCartItem is destructured to get its contents. Then we use theuseCartContext hook to get helper functions for removing items from the cart and updating the ordered quantity for the cart item. Using the helper functions, we add functionality for the buttons in the component. Clicking on the+ icon should increase the number of units to be ordered while clicking the- icon should reduce the number. We also add a button to remove the item entirely from the cart. Finally, we return the JSX for theCartItem component.

The next thing we need to do is fix ourCart component. At the moment, theCart component is hardcoded to render 3 items. It also takes two props - acartOpen flag, which determines whether the cart is displayed or collapsed. It also takes ahandleOpen function as a prop. This function toggles thecartOpen flag to display or collapse the cart. Since we have a context that manages this for us, we no longer need these props. Update theCart component to match the following:

// components/Cart.jsimport{useCartContext}from"../utils/context";importCartItemfrom"./CartItem";functionCart(){const{cartOpen,toggleCartVisibility,cart}=useCartContext();constorderTotal=cart.reduce((accumulator,{price,quantity})=>(price*quantity)+accumulator,0);const[phoneNumber,setPhoneNumber]=useState("");constupdatePhoneNumber=event=>{setPhoneNumber(event.target.value);};return(<divclassName={`${cartOpen?"translate-x-0 ease-out":"translate-x-full ease-in"}           fixed right-0 top-0 max-w-xs w-full h-full px-6 py-4 transition           duration-300 transform overflow-y-auto           bg-white border-l-2 border-gray-300`}><divclassName="flex items-center justify-between"><h3className="text-2xl font-medium text-gray-700">Your cart</h3><buttononClick={toggleCartVisibility}className="text-gray-600 focus:outline-none"><svgclassName="h-5 w-5"fill="none"strokeLinecap="round"strokeLinejoin="round"strokeWidth="2"viewBox="0 0 24 24"stroke="currentColor"><pathd="M6 18L18 6M6 6l12 12"/></svg></button></div><hrclassName="my-3"/>{cart.map(order=><CartItemkey={order.id}{...order}/>)}<divclassName="mt-8"><formclassName="flex items-center justify-center"><labelhtmlFor="phoneNumber"className="hidden">Phone Number</label><inputid="phoneNumber"className="form-input w-48"type="text"placeholder="Enter phone number"value={phoneNumber}onInput={updatePhoneNumber}/></form></div><hrclassName="my-3"/><spanclassName="text-l font-medium text-gray-700 mr-48">Total</span><span>${orderTotal}</span><aclassName="flexitems-centerjustify-centermt-4px-3py-2bg-blue-600text-whitetext-smuppercasefont-mediumroundedhover:bg-blue-500focus:outline-nonefocus:bg-blue-500"><span>Checkout</span><svgclassName="h-5 w-5 mx-2"fill="none"strokeLinecap="round"strokeLinejoin="round"strokeWidth="2"viewBox="0 0 24 24"stroke="currentColor"><pathd="M17 8l4 4m0 0l-4 4m4-4H3"/></svg></a></div>);}exportdefaultCart;
Enter fullscreen modeExit fullscreen mode

In our newly modified component, we retrieve thecartOpen flag from ouruseContext hook, along with the items that have been added to the cart and thecartOpen flag. We also calculate the total amount of all the orders in the cart before returning the JSX for theCart component.

Now that our cart is functional, we can update the cart from the products page. In thecomponents directory, openProductPage.js and update it to match the following:

// components/ProductPage.jsimport{useState}from"react";import{urlFor,PortableText}from"../utils/sanity";import{useCartContext}from"../utils/context";functionProductPage(props){const{title,defaultProductVariant,mainImage,body,id:productId,slug,}=props;const{findOrderInCart,addOrderToCart,removeOrderFromCart,updateOrderQuantity,showCart,toggleCartVisibility,}=useCartContext();letorderInCart=findOrderInCart(productId);const[count,setCount]=useState(orderInCart?.quantity||1);consthandleCount=(value)=>!(count===0&&value===-1)?setCount(count+value):count;consthandleOrderButtonClick=()=>{if(count===0&&orderInCart){removeOrderFromCart(productId);orderInCart=undefined;}if(!orderInCart&&count>0){addOrderToCart({title,slug,id:productId,price:defaultProductVariant?.price,quantity:count,mainImage,});}if(orderInCart){updateOrderQuantity(productId,count);}showCart();};return(<divclassName="container mx-auto px-6"><divclassName="md:flex md:items-center"><divclassName="w-full h-64 md:w-1/2 lg:h-96"><imgclassName="h-full w-full rounded-md object-cover max-w-lg mx-auto"src={urlFor(mainImage).auto("format").width(1051).fit("crop").quality(80)}alt={mainImage?.alt||`Photo of${title}`}/></div><divclassName="w-full max-w-lg mx-auto mt-5 md:ml-8 md:mt-0 md:w-1/2"><h3className="text-gray-700 uppercase text-lg">{title}</h3><spanclassName="text-gray-500 mt-3">            ${defaultProductVariant?.price}</span><hrclassName="my-3"/><divclassName="mt-2"><labelclassName="text-gray-700 text-sm"htmlFor="count">              Count:</label><divclassName="flex items-center mt-1"><buttononClick={()=>handleCount(1)}className="text-gray-500 focus:outline-none focus:text-gray-600"><svgclassName="h-5 w-5"fill="none"strokeLinecap="round"strokeLinejoin="round"strokeWidth="2"viewBox="0 0 24 24"stroke="currentColor"><pathd="M129v3m00v3m0-3h3m-30H9m120a99011-18099001180z"/></svg></button><spanclassName="text-gray-700 text-lg mx-2">{count}</span><buttononClick={()=>handleCount(-1)}className="text-gray-500 focus:outline-none focus:text-gray-600"><svgclassName="h-5 w-5"fill="none"strokeLinecap="round"strokeLinejoin="round"strokeWidth="2"viewBox="0 0 24 24"stroke="currentColor"><pathd="M15 12H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z"/></svg></button></div></div><divclassName="flex items-center mt-6"><buttononClick={handleOrderButtonClick}className="px-8py-2bg-indigo-600text-whitetext-smfont-mediumroundedhover:bg-indigo-500focus:outline-nonefocus:bg-indigo-500">              Order Now</button><buttononClick={toggleCartVisibility}className="mx-2text-gray-600borderrounded-mdp-2hover:bg-gray-200focus:outline-none"><svgclassName="h-5 w-5"fill="none"strokeLinecap="round"strokeLinejoin="round"strokeWidth="2"viewBox="0 0 24 24"stroke="currentColor"><pathd="M33h2l.42M713h10l4-8H5.4M713L5.45M713l-2.2932.293c-.63.63-.1841.707.7071.707H17m00a2201004220000-4zm-82a22011-402200140z"/></svg></button></div></div></div><divclassName="mt-16 md:w-2/3"><h3className="text-gray-600 text-2xl font-medium">Description</h3>{body&&<PortableTextblocks={body?.en}className="text-gray-600"/>}</div></div>);}exportdefaultProductPage;
Enter fullscreen modeExit fullscreen mode

On theProductPage component, we use the helper functions provided by theuseCartContext hook to handle the functionality for adding the rendered product to the cart (if it is not already in the cart). Additionally, we can increase or decrease the units we wish to purchase.

On theproducts page, we see that on each product, there is a button to directly add the product to the cart. At the moment, it doesn't work. Clicking the cart icon also takes us to the product page which we don't really want. To fix that, opencomponents/ProductCard.js and update it to match the following:

// components/ProductCard.jsimportLinkfrom"next/link";import{urlFor}from"../utils/sanity";import{useCartContext}from"../utils/context";functionProductCard({_id,title,mainImage,slug,defaultProductVariant}){const{addOrderToCart}=useCartContext();consthandleCartButtonClick=()=>{constorder={title,slug,id:_id,price:defaultProductVariant?.price,quantity:1,mainImage,};addOrderToCart(order);};return(<divclassName="w-full max-w-sm mx-auto rounded-md shadow-md overflow-hidden"><divclassName="flex items-end justify-end h-56 w-full bg-cover"style={{backgroundImage:`url('${urlFor(mainImage).auto("format").fit("crop").width(750).quality(80)}`,}}><buttononClick={handleCartButtonClick}className="p-2rounded-fullbg-blue-600text-whitemx-5-mb-4hover:bg-blue-500focus:outline-nonefocus:bg-blue-500"><svgclassName="h-5 w-5"fill="none"strokeLinecap="round"strokeLinejoin="round"strokeWidth="2"viewBox="0 0 24 24"stroke="currentColor"><pathd="M33h2l.42M713h10l4-8H5.4M713L5.45M713l-2.2932.293c-.63.63-.1841.707.7071.707H17m00a2201004220000-4zm-82a22011-402200140z"/></svg></button></div><divclassName="px-5 py-3"><Linkhref={`/products/${slug?.current}`}><a><h3className="text-gray-700 uppercase">{title}</h3></a></Link><spanclassName="text-gray-500 mt-2">          ${defaultProductVariant?.price}</span></div></div>);}exportdefaultProductCard;
Enter fullscreen modeExit fullscreen mode

Here we use theaddOrderToCart function provided by theuseCartContext hook to add the product to the cart (or increase the units ordered if it has already been added). We also refactor the JSX such that the user is only taken to the project page by clicking the product title.

In order to see the changes we have made, we need to render theCart component. If you look at lines 122–124 incomponents/Layout.js, you'll see that theCart component is commented out. We can go ahead to uncomment those lines and remove the props that are being passed to theCart component since those are being handled via the Context API. Updatecomponents/Layout.js to match the following code:

omponents/Layout.jsimport{useState}from"react";importLinkfrom"next/link";importCartfrom"./Cart";import{useCartContext}from"../utils/context";functionLayout({children}){const[menuOpen,setMenuOpen]=useState(false);consthandleMenu=()=>setMenuOpen(!menuOpen);const{toggleCartVisibility}=useCartContext();return(<divclassName="bg-white"><header><divclassName="container mx-auto px-6 py-3"><divclassName="flex items-center justify-between"><divclassName="hidden w-full text-gray-600 md:flex md:items-center"><svgclassName="h-5 w-5"viewBox="0 0 24 24"fill="none"xmlns="http://www.w3.org/2000/svg"><pathfillRule="evenodd"clipRule="evenodd"d="M16.272110.2721C16.272112.481314.481314.272112.272114.2721C10.06314.27218.2721412.48138.2721410.2721C8.272148.0629810.0636.2721212.27216.27212C14.48136.2721216.27218.0629816.272110.2721ZM14.272110.2721C14.272111.376713.376712.272112.272112.2721C11.167612.272110.272111.376710.272110.2721C10.27219.1675511.16768.2721212.27218.27212C13.37678.2721214.27219.1675514.272110.2721Z"fill="currentColor"/><pathfillRule="evenodd"clipRule="evenodd"d="M5.7941716.5183C2.1942413.09092.054387.394095.481783.79417C8.909180.19424314.60590.05438318.20593.48178C21.80586.9091821.945712.605918.518316.2059L12.312422.7241L5.7941716.5183ZM17.069814.8268L12.24319.8965L7.1732415.0698C4.373312.4044.264527.973186.930285.17324C9.596032.373314.02682.2645216.82684.93028C19.62677.5960319.735512.026817.069814.8268Z"fill="currentColor"/></svg><spanclassName="mx-1 text-sm">NY</span></div><divclassName="w-fulltext-gray-700md:text-centertext-2xlfont-semibold">              Pulp Inc.</div><divclassName="flex items-center justify-end w-full"><buttononClick={toggleCartVisibility}className="text-gray-600 focus:outline-none mx-4 sm:mx-0"><svgclassName="h-5 w-5"fill="none"strokeLinecap="round"strokeLinejoin="round"strokeWidth="2"viewBox="0 0 24 24"stroke="currentColor"><pathd="M33h2l.42M713h10l4-8H5.4M713L5.45M713l-2.2932.293c-.63.63-.1841.707.7071.707H17m00a2201004220000-4zm-82a22011-402200140z"/></svg></button><divclassName="flex sm:hidden"><buttononClick={handleMenu}type="button"className="text-gray-600hover:text-gray-500focus:outline-nonefocus:text-gray-500"aria-label="toggle menu"><svgviewBox="0 0 24 24"className="h-6 w-6 fill-current"><pathfillRule="evenodd"d="M45h16a1100102H4a110110-2zm06h16a1100102H4a110010-2zm06h16a1100102H4a110010-2z"/></svg></button></div></div></div><navclassName={`${menuOpen?"":"hidden"} sm:flex sm:justify-center sm:items-center mt-4`}><divclassName="flex flex-col sm:flex-row"><Linkhref="/"><aclassName="mt-3 text-gray-600 hover:underline sm:mx-3 sm:mt-0">                  Home</a></Link><Linkhref="/products"><aclassName="mt-3 text-gray-600 hover:underline sm:mx-3 sm:mt-0">                  Shop</a></Link><Linkhref="/about"><aclassName="mt-3 text-gray-600 hover:underline sm:mx-3 sm:mt-0">                  About</a></Link></div></nav><divclassName="relative mt-6 max-w-lg mx-auto"><spanclassName="absolute inset-y-0 left-0 pl-3 flex items-center"><svgclassName="h-5 w-5 text-gray-500"viewBox="0 0 24 24"fill="none"><pathd="M2121L1515M1710C1713.86613.866171017C6.1340117313.866310C36.134016.134013103C13.8663176.134011710Z"stroke="currentColor"strokeWidth="2"strokeLinecap="round"strokeLinejoin="round"/></svg></span><inputclassName="w-fullborderrounded-mdpl-10pr-4py-2focus:border-blue-500focus:outline-nonefocus:shadow-outline"type="text"placeholder="Search"/></div></div></header>{/* // This Cart works… well sort of! */}<Cart/><mainclassName="my-8">{children}</main><footerclassName="bg-gray-200"><divclassName="containermx-autopx-6py-3flexjustify-betweenitems-center"><ahref="#"className="text-xl font-bold text-gray-500 hover:text-gray-400">            Pulp Inc.</a><pclassName="py-2 text-gray-500 sm:py-0">All rights reserved</p></div></footer></div>);}exportdefaultLayout;
Enter fullscreen modeExit fullscreen mode

Implementing the Checkout Feature

At the moment, clicking on theCheckout button does nothing. For this tutorial, we'll create a simple endpoint that expects a HTTP Post request containing the customer's phone number, the ordered items and the order total. When we send a request to this endpoint, it will send an SMS to the customer and return the content of the message as a response. We can do this in our current project as Next.js provides asolution for us to build APIs with it.

Before we build this endpoint, let's add the Twilio Javascript SDK to our project.

npminstalltwilio
Enter fullscreen modeExit fullscreen mode

This library is only for back end applications running on Node.js.

Next, update the.env file as follows:

# For Twilio LocallyTWILIO_MESSAGING_SID="your_twilio_messaging_sid"TWILIO_AUTH_TOKEN="your_twilio_auth_token"TWILIO_ACCOUNT_SID="your_twilio_account_sid"TWILIO_PHONE_NUMBER="your_twilio_phone_number"
Enter fullscreen modeExit fullscreen mode

Next, in theutils directory, create a new file calledtwilio.js.

touchutils/twilio.js
Enter fullscreen modeExit fullscreen mode

In theutils/twilio.js file add the following:

utils/twilio.jsconsttwilio=require("twilio");constclient=newtwilio(process.env.TWILIO_ACCOUNT_SID,process.env.TWILIO_AUTH_TOKEN);consttwilioPhoneNumber=process.env.TWILIO_PHONE_NUMBER;constmessagingServiceSid=process.env.TWILIO_MESSAGING_SID;constsendSMS=async(recipient,message)=>{returnawaitclient.messages.create({body:message,to:recipient,from:twilioPhoneNumber,messagingServiceSid,});};exportdefaultsendSMS;
Enter fullscreen modeExit fullscreen mode

ThesendSMS function will be used by our API to send text messages. It takes two parameters, the recipient's phone number and the message to be sent. In this function, a Twilio client is instantiated using ourTWILIO_ACCOUNT_SID andTWILIO_AUTH_TOKEN. We also retrieve our twilio phone number and messaging_sid from the.env file. In addition to the recipient's phone number and message content, these are added to the options for the request to the Twilio rest API.

Next, create the endpoint to handle orders from the front-end. In thepages directory, create a new directory calledapi.

mkdirpages/api
Enter fullscreen modeExit fullscreen mode

In thepages/api directory, create a new file calledorder.js.

touchpages/api/order.js
Enter fullscreen modeExit fullscreen mode

Inpages/api/order.js, add the following code:

// pages/api/order.jsimportsendSMSfrom"../../utils/twilio";constgetMessage=(cart,orderTotal)=>{constorderId=Math.random().toString(36).substring(2,9);return`Congratulations, your order (${orderId}) worth $${orderTotal}\   for${cart.length} items has been processed successfully. The items will be \  delivered within 3 working days.`;};consthandler=async(request,response)=>{switch(request.method){case"POST":const{cart,orderTotal,phoneNumber}=request.body;constmessage=getMessage(cart,orderTotal);awaitsendSMS(phoneNumber,message);response.status(200).json({message});break;default:response.status(405).end("This method is not allowed for this route.");}};exportdefaulthandler;
Enter fullscreen modeExit fullscreen mode

Here, we declare an async function namedhandler which will handle the requests made to the API. Since we only want to handlePOST requests, we return a405 response for all other request methods. When aPOST request is received, we retrieve the cart (ordered items), the total amount of the order, and the customer's phone number from the request body. Using thesendSMS helper function we created earlier, we send a request to the Twilio API and trigger the dispatch of a text message to the customer's phone number. We use theasync/await keyword so that our API does not send the response before the Twilio API responds to our request.

To test this, we'll update ourCart component to make an API request and display the response message in a modal. But before we do that, let's create a component for the modal. Create a file namedModal.js in thecomponents directory.

touchcomponents/Modal.js
Enter fullscreen modeExit fullscreen mode

InModal.js, add the following code:

// components/Modal.jsimportReactfrom"react";constModal=({title,message,showModal,closeModal})=>{return(showModal&&(<divclassName="absoluteinset-0bg-gray-300text-gray-900bg-opacity-25overflow-x-hidden"><divclassName="relativepx-4min-h-screenmd:flexmd:items-centermd:justify-center"><divclassName="bg-blackopacity-25w-fullh-fullabsolutez-10inset-0"></div><divclassName="bg-whiterounded-lgmd:max-w-mdmd:mx-autop-4fixedinset-x-0bottom-0z-50mb-4mx-4md:relative"><divclassName="md:flex items-center"><divclassName="rounded-fullborderborder-gray-300flexitems-centerjustify-centerw-16h-16flex-shrink-0mx-auto"><svgxmlns="http://www.w3.org/2000/svg"className="h-6 w-6"fill="none"viewBox="0 0 24 24"stroke="currentColor"><pathstrokeLinecap="round"strokeLinejoin="round"strokeWidth={2}d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg></div><divclassName="mt-4md:mt-0md:ml-6text-centermd:text-left"><pclassName="font-bold">{title}</p><pclassName="text-sm text-gray-700 mt-1">{message}</p></div></div><divclassName="text-centermd:text-rightmt-4md:flexmd:justify-end"><buttononClick={closeModal}className="flexitems-centerjustify-centermt-4px-3py-2bg-blue-600text-whitetext-smuppercasefont-mediumroundedhover:bg-blue-500focus:outline-nonefocus:bg-blue-500">                Close</button></div></div></div></div>));};exportdefaultModal;
Enter fullscreen modeExit fullscreen mode

Update theCart component to match the following:

// components/Cart.jsimport{useCartContext}from"../utils/context";importCartItemfrom"./CartItem";importReact,{useState}from"react";importModalfrom"./Modal";functionCart(){const{cartOpen,toggleCartVisibility,cart,removeAllItemsFromCart,}=useCartContext();constorderTotal=cart.reduce((accumulator,{price,quantity})=>price*quantity+accumulator,0);const[showModal,setShowModal]=useState(false);const[modalTitle,setModalTitle]=useState(null);const[modalContent,setModalContent]=useState(null);const[phoneNumber,setPhoneNumber]=useState("");constupdatePhoneNumber=(event)=>{setPhoneNumber(event.target.value);};constcloseModal=()=>{removeAllItemsFromCart();setPhoneNumber("");setShowModal(false);};constmakeOrder=()=>{fetch("/api/order",{method:"POST",headers:{"Content-Type":"application/json",},body:JSON.stringify({cart,orderTotal,phoneNumber}),}).then((response)=>response.json()).then((data)=>{setModalContent(data.message);setModalTitle("Success");setShowModal(true);});};return(<><Modaltitle={modalTitle}message={modalContent}showModal={showModal}closeModal={closeModal}/><divclassName={`${cartOpen?"translate-x-0 ease-out":"translate-x-full ease-in"}           fixed right-0 top-0 max-w-xs w-full h-full px-6 py-4 transition           duration-300 transform overflow-y-auto           bg-white border-l-2 border-gray-300`}><divclassName="flex items-center justify-between"><h3className="text-2xl font-medium text-gray-700">Your cart</h3><buttononClick={toggleCartVisibility}className="text-gray-600 focus:outline-none"><svgclassName="h-5 w-5"fill="none"strokeLinecap="round"strokeLinejoin="round"strokeWidth="2"viewBox="0 0 24 24"stroke="currentColor"><pathd="M6 18L18 6M6 6l12 12"/></svg></button></div><hrclassName="my-3"/>{cart.map((order)=>(<CartItemkey={order.id}{...order}/>))}<divclassName="mt-8"><formclassName="flex items-center justify-center"><inputclassName="form-input w-48"type="text"placeholder="Enter phone number"value={phoneNumber}onInput={updatePhoneNumber}/></form></div><hrclassName="my-3"/><spanclassName="text-l font-medium text-gray-700 mr-48">Total</span><span>${orderTotal}</span><buttononClick={makeOrder}className="flexitems-centerjustify-centermt-4px-3py-2bg-blue-600text-whitetext-smuppercasefont-mediumroundedhover:bg-blue-500focus:outline-nonefocus:bg-blue-500"><span>Checkout</span><svgclassName="h-5 w-5 mx-2"fill="none"strokeLinecap="round"strokeLinejoin="round"strokeWidth="2"viewBox="0 0 24 24"stroke="currentColor"><pathd="M17 8l4 4m0 0l-4 4m4-4H3"/></svg></button></div></>);}exportdefaultCart;
Enter fullscreen modeExit fullscreen mode

Here, we add functionality to theCheckout button by calling themakeOrder function when it is clicked. This function makes a HTTP request to theapi/order route which will be handled by thehandler function we declared inpages/api/order.js. Upon a successful response, the modal is rendered letting the customer know that the order was processed successfully as shown below.

Success modal shown when 'checkout' button is clicked

An SMS will also be sent to the specified phone number as shown below.

Text message sent via Twilio when 'Checkout' button is clicked

With that, our e-commerce website is ready for launch, Congratulations!!!

Conclusion

In this article, we've seen how programmable messaging can be used to provide a better customer experience by sending notifications in the form of text messages to customers with Twilio SMS as a case study. Using Twilio's JavaScript SDK, we were able to trigger an outbound SMS request to the Twilio API. The API reference as well as code samples in Javascript and other languages (PHP, Python, Java to name a few) can be foundhere

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Build with Structured Content.

npm i -g @sanity/cli && sanity init

More fromSanity.io

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp