Confirming SMS Message Delivery with RxJS Observables, Node.js, and Twilio Programmable SMS

April 09, 2020
Written by
Maciej Treder
Contributor
Opinions expressed by Twilio contributors are their own

confirming-sms-message-delivery-rxjs.png

SMS messages can reach over 4.5 billion text-enabled devices to notify people about upcoming appointments, emergencies, traffic disruptions, or commercial promotions. Sending SMS messages programmatically enables you to reach many people almost simultaneously. With Twilio Programmable SMS you can create a Node.js application that sends many messages and reports when each of them has been delivered—or not.

Twilio Programmable SMS includes a helper library for Node.js that makes it easy to interact with the SMS API without having to create and manipulate connections to the API endpoints. Once an SMS message request is created with the helper library its status is emitted as a JavaScript Promise. While that is great for creating a request asynchronously, Promises only resolve once, and the message request will pass through more than one state as it makes its way—or not—to the recipient.

You can create a mechanism for monitoring and reporting the status of a message request using ReactiveX for JavaScript (RxJS) Observables, which can emit new results asynchronously as the data from an underlying source changes. By creating an Observable wrapper around each message request and calling the helper library’s status method periodically, you can get message delivery information for each number and asynchronously report the status for each number as it changes.

Important compliance note: There are rules for using SMS messaging and they vary between countries. Familiarize yourself with the rules of the countries in which you’ll be sending messages with the Twilio Regulatory Guidelines for SMS.

Understanding the tutorial project

This tutorial will show you how to create a Node.js application that sends an SMS message to a list of phone numbers and reports the status of each message. You’ll use Twilio Programmable SMS to send the messages by using the Twilio Helper Library for Node.js in your program. You’ll create a RxJS wrapper, a mechanism that enhances existing code with new functionality, around the SMS functionality in the helper library.

You’ll learn how to create an Observable from scratch and how to emit the status of an SMS request with an Observable. You’ll find out how to use the RxJS operators map, distinct, merge, tap, and catchError. You’ll also create a timeout mechanism using the RxJS timer function to abandon status polling when a timeout interval elapses.

Prerequisites

To accomplish the task in this post you will need the following:

You should also have a working knowledge of the core elements of JavaScript, asynchronous JavaScript mechanics, and ReactiveX programming.

There is a companion repository for this post available on GitHub.

Getting your Twilio account credentials

To use the Twilio CLI and interact with the Twilio APIs you’ll need two essential pieces of information from your Twilio Console Dashboard: Account SID and Auth Token. You can find them on the top right hand-side of the dashboard.

These are user secrets, so be sure to store them in a safe place.

To use your credentials in development, you’ll want to store them as environment variables or in a .env file. If you choose to store them in a file, be sure that the filename is included in your .gitignore file so you don’t inadvertently check them into a public repository. For the purposes of this tutorial, you’ll store your secrets as environment variables.

If you are using a Unix-based operating system, such as Linux or macOS, you can set environment variables using the following commands:

export TWILIO_ACCOUNT_SID=<your account sid>
export TWILIO_AUTH_TOKEN=<your authentication token>

If you are a Windows user, use the following commands:

setx TWILIO_ACCOUNT_SID <your account sid>
setx TWILIO_AUTH_TOKEN <your authentication token>

Getting a Twilio Phone Number

Twilio Programmable SMS messages are sent using Twilio phone numbers, which provide instant access to local, national, mobile, and toll-free phone numbers in more than 100 countries with a developer-friendly API. You can get a Twilio phone number for free as part of your trial account.

Once you’ve created a Twilio account you can get a Twilio phone number using the Twilio CLI.

Note: The Twilio CLI is in beta status as of the date of this post. If you’ve previously installed it, be sure you have the latest version by executing the following command:

npm update -g twilio-cli

If you’ve stored your Twilio credentials as environment variables, the Twilio CLI will use them automatically. If you’ve stored them in some other way, you’ll have to login first using the following command:

twilio login

To list the phone numbers available for registration, use the following command, substituting the appropriate ISO 3166 alpha-2 country code for “US”, if necessary:

twilio api:core:available-phone-numbers:local:list --country-code US

You should see a list similar to the following output:

Phone Number  Region  ISO Country  Address Requirements
+13852101305  UT      US           none                
+14077922414  FL      US           none                
+16033712156  NH      US           none                
+16036367116  NH      US           none                
+18312751816  CA      US           none                
+14693316717  TX      US           none                
+18312751822  CA      US           none    

Copy one of the numbers from the list and register it to your Twilio account using the following command:

twilio api:core:incoming-phone-numbers:create --phone-number="+13852101305"

If your registration attempt is successful, you should see the following:

SID                                 Phone Number  Friendly Name 
PN3ef900000000000000000000000000d9  +13852101305  (385) 210-1305

Once registered, the phone number is available for your use (until you release it using the CLI or Twilio Console). Note that the SID associated with the phone number is a user secret and should be handled securely. Store registered phone number under the TWILIO_PHONE_NUMBER environment variable in E.164 format.

If you use Linux, Unix, or macOS:

export TWILIO_PHONE_NUMBER=+1234567890

If you’re Windows user:

setx TWILIO_PHONE_NUMBER +1234567890

You can verify the number has been successfully added to your account by sending a test SMS to an SMS-enabled phone number:

twilio api:core:messages:create --from $TWILIO_PHONE_NUMBER --to "SMS receiver phone number" --body "Hello world"

Note that with a trial account you can only send messages to phone numbers you’ve previously registered to your account. The SMS-enabled phone number you used to sign up for your Twilio account is the first number you’ve registered.

The API will return a response similar to the below output to indicate that SMS message has been successfully received and is queued to be sent:

SID                                 From          To            Status  Direction     Date Sent
SM4a447328e80a43ceb8e61dda9f3d4cb6  +13852101305  +16463974810  queued  outbound-api  null   

Within a short time you should receive an SMS message on your phone:

Mobile phone lock screen with SMS message

You can check status of message creation request using the following CLI command:

twilio api:core:messages:fetch --sid SM4a447328e80a43ceb8e61dda9f3d4cb6

As a result, you will see:

SID                                 From          To            Status     Direction     Date Sent                    
SM4a447328e80a43ceb8e61dda9f3d4cb6  +13852101305  +16463974810  delivered  outbound-api  Mar 13 2020 14:17:20 GMT+0100

Initializing the Node.js project

Once you’ve registered and tested the phone number, you can initialize the project and its Git repository with a series of command-line instructions.

Open a console window and execute the following instructions in the directory where you want to create the project directory:

mkdir twilio-sms-rxjs
cd twilio-sms-rxjs
git init
npx license mit > LICENSE
npx gitignore node
npm init -y
git add -A
git commit -m "Initial commit"

You can learn more about initializing Node.js projects in this post by Twilio's Phil Nash.

Install the dependencies you are going to use:

npm install esm rxjs twilio

Sending your first message with Twilio SMS and Node.js

Create a sms-basics.js file in the project’s root directory and place the following JavaScript code  inside:

import twilio from 'twilio';
const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
client.messages.create({
  body: 'Hello World!',
  from: process.env.TWILIO_PHONE_NUMBER,
  to: '+16463974810' //replace this with your registered phone number
})
.then(result => {
  console.log(result);
  return result.sid;
})
.then(sid => client.messages(sid).fetch())
.then(console.log);

The program flow for the code above is depicted in the following diagram:

RxJS Observable chaining in the program

The program starts with the initialization of the Twilio client using your Twilio Account SID and Auth Token loaded from the environment variables. Once the client is initialized you can send a message with Twilio Programmable SMS API. To do that you’ve called a client.messages.create method and passed to it an object containing the message body body, recipient to and sender from.

The client.messages.create method of the Twilio helper library returns a Promise object which resolves with the results of the SMS creation request sent to the Twilio Programmable SMS API, shown in green on the diagram. You’ve sent it to the console with the first console.log statement:

{ accountSid: 'xxxxxx',
  apiVersion: '2010-04-01',
  body: 'Hello World!',
  dateCreated: 2020-02-27T21:22:57.000Z,
  dateUpdated: 2020-02-27T21:22:57.000Z,
  dateSent: null,
  direction: 'outbound-api',
  errorCode: null,
  errorMessage: null,
  from: '+xxxxxxxxx',
  messagingServiceSid: null,
  numMedia: '0',
  numSegments: '1',
  price: null,
  priceUnit: 'USD',
  sid: 'SM926d88f994c946f3872011524e20272b',
  status: 'queued',
  subresourceUris:
   { media:
      '/2010-04-01/Accounts/ACe52e8ee9a0993322ce10707c978721fa/Messages/SM926d88f994c946f3872011524e20272b/Media.json' },
  to: '+16463974810',
  uri:
   '/2010-04-01/Accounts/ACe52e8ee9a0993322ce10707c978721fa/Messages/SM926d88f994c946f3872011524e20272b.json' }

From the above data, you’ve picked up a sid value, which is a unique identifier of your request, and passed it to the next link in the promise chain. In the chained function you’ve invoked the client.messages.fetch method, which returns another Promise. That Promise resolves with the current status of the SMS creation request, shown in blue on the diagram. In the last then you’ve printed that result to the console.

Ensure you’ve set up the environment variables TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, and TWILIO_PHONE_NUMBER and run the program:

node -r esm sms-basics.js

Your console output should be similar to:

{ accountSid: 'ACe52e8ee9a0993322ce10707c978721fa',
  apiVersion: '2010-04-01',
  body: 'Hello World!',
  dateCreated: 2020-03-22T12:54:49.000Z,
  dateUpdated: 2020-03-22T12:54:49.000Z,
  dateSent: null,
  direction: 'outbound-api',
  errorCode: null,
  errorMessage: null,
  from: '+14252797117',
  messagingServiceSid: null,
  numMedia: '0',
  numSegments: '1',
  price: null,
  priceUnit: 'USD',
  sid: 'SM01a388f69dda4b0da4cffb24fb17b290',
  status: 'queued',
  subresourceUris:
   { media:
      '/2010-04-01/Accounts/ACe52e8ee9a0993322ce10707c978721fa/Messages/SM01a388f69dda4b0da4cffb24fb17b290/Media.json' },
  to: '+16463974810',
  uri:
   '/2010-04-01/Accounts/ACe52e8ee9a0993322ce10707c978721fa/Messages/SM01a388f69dda4b0da4cffb24fb17b290.json' }
{ accountSid: 'ACe52e8ee9a0993322ce10707c978721fa',
  apiVersion: '2010-04-01',
  body: 'Hello World!',
  dateCreated: 2020-03-22T12:54:49.000Z,
  dateUpdated: 2020-03-22T12:54:49.000Z,
  dateSent: 2020-03-22T12:54:49.000Z,
  direction: 'outbound-api',
  errorCode: null,
  errorMessage: null,
  from: '+14252797117',
  messagingServiceSid: null,
  numMedia: '0',
  numSegments: '1',
  price: '-0.00750',
  priceUnit: 'USD',
  sid: 'SM01a388f69dda4b0da4cffb24fb17b290',
  status: 'sent',
  subresourceUris:
   { media:
      '/2010-04-01/Accounts/ACe52e8ee9a0993322ce10707c978721fa/Messages/SM01a388f69dda4b0da4cffb24fb17b290/Media.json',
     feedback:
      '/2010-04-01/Accounts/ACe52e8ee9a0993322ce10707c978721fa/Messages/SM01a388f69dda4b0da4cffb24fb17b290/Feedback.json' },
  to: '+16463974810',
  uri:
   '/2010-04-01/Accounts/ACe52e8ee9a0993322ce10707c978721fa/Messages/SM01a388f69dda4b0da4cffb24fb17b290.json' }

If you haven’t been following along with the coding and want to catch up to this step using the code from the GitHub repository, execute the following commands in the directory where you’d like to create the project directory:

git clone https://github.com/maciejtreder/twilio-sms-rxjs.git
cd twilio-sms-rxjs
git checkout step1
npm install

Sending SMS messages to multiple phone numbers and monitoring message status with an RxJS Observables wrapper

Now that you’ve got Twilio SMS basics under your belt you can create an RxJS wrapper for the Twilio SMS API which is a part of the twilio library.

Create a new file called rxjs-twilio.js in the project root directory. Start coding from introducing import statements and initializing the Twilio client by inserting the following code:

import { Subject, timer } from 'rxjs';
import { distinct } from 'rxjs/operators';
import twilio from 'twilio';

const client = twilio(process.env.ACCOUNT_SID, process.env.AUTH_TOKEN);

Define the sendSMS function, which is an entry point of the rxjs-twilio file. Insert the following JavaScript code at the bottom of the rxjs-twilio.js file:

export function sendSMS(phoneNumber, body) {
   const subject = new Subject();

   client.messages.create({
       body: body,
       from: process.env.TWILIO_PHONE_NUMBER,
       to: phoneNumber
   })
   .then(msg => {
       subject.next(msg);
       pollStatus(msg.sid, subject, 20); // to be done
   }).catch(error => {
       subject.error(error);
   });
   return subject.pipe(distinct(response => response.status));
}

The sendSMS function accepts two parameters: the recipient’s phone number and the message body. A constant subject represents the Observable that is returned by the function. Subject is one of the implementations of the Observable interface.

The client.messages.create function creates an SMS message creation request through the Twilio API. When the Promise returned by the client.messages.create function resolves, the status of the creation request is emitted through the subject Observable.

The pollStatus method, which you’ll implement next, monitors the state of the Observable returned by the Twilio helper library. If the message creation request sent to the Twilio SMS API fails, the error is emitted through the subject Observable inside the catch block. 

The sendSMS function uses the distinct operator to avoid emitting a duplicate status value. If the message status has not changed from one invocation of pollStatus to the next.

Insert the following code at the bottom of the rxjs-twilio.js file:

const messageDeliveryStatuses = [`delivered`, `undelivered`, `failed`];
let stopPolling = [];

function pollStatus(sid, subject, timeout = 20, watchdog = null) {
   if (!watchdog) {
       watchdog = timer(timeout * 1000).subscribe(() => stopPolling[sid] = true);
   }
  
   client.messages(sid).fetch().then(response => { 
       subject.next(response);
       if (messageDeliveryStatuses.includes(response.status) || stopPolling[sid]) {
           subject.complete();
           watchdog.unsubscribe();
           stopPolling.splice(sid);
       } else {
           pollStatus(sid, subject, null, watchdog);
       }
   });
}

The constant messageDeliveryStatuses is an array of strings representing the Finalized Message Delivery Status values for a message.

The stopPolling array will contain a dictionary of flags indicating if the polling timeout has elapsed for each pending SMS message request. This is necessary because the “sent” status can indicate either that the message is in the process of being delivered or it was delivered and Twilio did not receive a delivery confirmation, or other status, from the carrier.

The pollStatus function accepts up to four parameters, the last two are optional:

  • sid – the ID of the message creation request to poll
  • subject – the status of the message creation request
  • timeout – the time, in seconds, after which polling should stop
  • watchdog – the subscription of an Observable created with the timer method, which determine when to flip the flag in the stopPolling array to true for the associated message ID

If the status of your message creation request changes to one of the messageDeliveryStatuses values or the stopPolling[sid] flag is set to true, the unsubscribe method abandons the watchdog and cleans up the callback queue and  the splice method removes the entry from the stopPolling dictionary to avoid creating a memory leak.

If the request status is not one of the finalized delivery status values the pollStatus function is called recursively. To avoid setting up a new watchdog for each recursive execution of the function in the inner pollStatus invocations, the existing watchdog is passed as an argument in the else clause.

The client.messages(sid).fetch() method returns a Promise that resolves with an object representing the state of each SMS request. When the Promise resolves, the result is emitted through the subject Observable, which was passed as a parameter from the sendSMS function.

The wrapper flow is depicted in the diagram below:

sendSMS program flow

Testing the RxJS Observables wrapper

To put the Observables wrapper to use and see how it handles an unpredictable sequence of responses from the SMS API, you’ll need a program to iterate through a series of phone numbers and gather the Observables from each number into a collection. Then you can report on the status of each SMS message each time an Observable in the collection emits new information indicating a change in the status of the associated message request.

Create a file called sms.js in the project root and insert the following JavaScript code:

import { sendSMS } from './rxjs-twilio';
import { of, merge } from 'rxjs';
import { map, catchError, tap } from 'rxjs/operators';

const phoneNumbers = [
   '+15017122661', // Use your registered mobile phone number here
   'non-existing',
   '+484110677' // Use a landline phone number here
];

const requests = [];

phoneNumbers.forEach(number => {
   requests.push(
       sendSMS(number, `Hello world!`).pipe(
           map(response => {
               return { number: number, status: response.status };
           }),
           catchError(error => of({number: number, status: 'error', details: error})),
           map(response => {
               response.time = new Date();
               return response;
           })
       )
   );
});

merge(...requests).pipe(
   tap(() => console.log(`\n-------------------------------\n`))
).subscribe(console.log);

The code creates an array of phone numbers to send an SMS message. It iterates through each element of the array, invoking the sendSMS function and passing a “Hello World!” message to it along with the phone number.

For testing purposes, use phone numbers that can accept SMS messages, like your registered number. Also include numbers, like landlines, that are valid numbers but can’t accept SMS messages. Include values that aren’t valid phone numbers to test how the program and the API handle invalid data.

The Observable returned by the sendSMS is piped to the following operators:

  • map removes some information from the Twilio API response, leaving only the phone number and current status
  • catchError catches errors that eventually occur and returns an Observable emitting the identity of the request that failed and the failure reason
  • map adds a timestamp to the response.

Each Observable returned by sendSMS is pushed to the requests array, which is then passed as a parameter to the merge method to consolidate the array into one Observable.

The consolidated Observable is piped through the tap function, which performs a side action every time a new result is emitted by the Observable, creating a line in the output between each set of results as one of the SMS message requests is updated.

The action performed by the subscribe operator sends the results emitted by the Observable to the console.

Run the program:

node -r esm sms.js

The output should be similar to:

{ number: 'non-existing',
  status: 'error',
  details:
   { Error: The 'To' number  is not a valid phone number.
     status: 400,
     message: 'The \'To\' number  is not a valid phone number.',
     code: 21211,
     moreInfo: 'https://www.twilio.com/docs/errors/21211',
     detail: undefined },
  time: 2020-04-02T18:52:55.532Z }

-------------------------------

{ number: '+484110677',
  status: 'error',
  details:
   { Error: The 'To' number +484110677 is not a valid phone number.
     status: 400,
     message: 'The \'To\' number +484110677 is not a valid phone number.',
     code: 21211,
     moreInfo: 'https://www.twilio.com/docs/errors/21211',
     detail: undefined },
  time: 2020-04-02T18:52:55.538Z }

-------------------------------

{ number: '+15017122661',
  status: 'queued',
  time: 2020-04-02T18:52:55.593Z }

-------------------------------

{ number: '+15017122661',
  status: 'sent',
  time: 2020-04-02T18:52:56.194Z }

-------------------------------

{ number: '+15017122661',
  status: 'delivered',
  time: 2020-04-02T18:52:56.818Z }

The output shows the status of three message creation requests to the Twilio Programmable SMS API through the helper library for Node.js. Whenever the status of any of the requests changes, new output is produced.

Two of the phone numbers used in the sample code are not able to receive SMS, so the API will return error messages. Those types of errors will usually be returned right away.

For phone numbers capable of receiving SMS, the Observable will usually emit data  three times because the message request will have three states: queued, sent, and delivered. The exception to this will be when the message can’t be delivered, such as when the phone is turned off, or the carrier doesn’t return a response to Twilio. In those cases the final state will be “sent”.

If you haven’t been following along with the coding and want to catch up to this step using the code from the GitHub repository, execute the following commands in the directory where you’d like to create the project directory:

git clone https://github.com/maciejtreder/twilio-sms-rxjs.git
cd twilio-sms-rxjs
git checkout step2
npm install

Summary

This post introduced you to using Twilio Programmable SMS with RxJS Observables in a Node.js application. You’ve learned how to perform repetitive API calls and emit responses with Observables. You saw how to combine three Observables to create a new one that emits data whenever one of the inner Observable emits data. Using these techniques you can get updates on the delivery status of SMS messages sent with Twilio Programmable SMS.

Additional Resources

Twilio Programmable SMS – Send and receive text messages globally with the API that over a million developers depend on.

ReactiveX introductions and tutorials – The ReactiveX website has an extensive list of resources for learning more about Reactive Programming and the various language implementations. They’re 3rd-party productions, so watch and read at your own discretion.

RxJS – The RxJS website is the place for canonical information about the ReactiveX library for JavaScript.

Which Operator do I use? – A helpful tool for choosing the best Observables operator for a desired action.

If you want to learn more about JavaScript Promises, which are a better asynchronous tool for some programming situations, check out the following posts:

If you’d like to learn more about RxJS Observables, check out this post:

Ready for more messaging excitement? Try some Programmable SMS missions in TwilioQuest!

Maciej Treder is a Senior Software Development Engineer at Akamai Technologies. He is also an international conference speaker and the author of @ng-toolkit, an open source toolkit for building Angular progressive web apps (PWAs), serverless apps, and Angular Universal apps. Check out the repo to learn more about the toolkit, contribute, and support the project. You can learn more about the author at https://www.maciejtreder.com. You can also contact him at: contact@maciejtreder.com or @maciejtreder on GitHub, Twitter, StackOverflow, and LinkedIn.

Gabriela Rogowska contributed to this post.