Sending Appointment Reminder Messages with ActiveMQ, Node.js, and Twilio Programmable SMS

September 01, 2020
Written by
Alex Baban
Contributor
Opinions expressed by Twilio contributors are their own
Reviewed by
AJ Saulsberry
Contributor
Opinions expressed by Twilio contributors are their own

appointment-reminders-ActiveMQ.png

With just a few lines of code that leverage Twilio's helper library for Node.js you can send SMS and MMS messages from your Node.js application. When your application makes an HTTP POST request to Twilio's Message resource, the recipient receives your message almost instantly.

Imagine that you’ve built an appointment reminder solution with Twilio. Every evening at 8:00 pm you run a Node.js application that reads appointment data from a .csv file and sends text message reminders to customers who have appointments the next day. It works great, customers are happy.

You want to enhance your application so that it sends a second reminder, let’s say, 15 minutes before the appointment. You could still run the application the night before but, somehow, the delivery of each message needs to be delayed until it’s the right time to be received by the customer.

This can be done in multiple ways, and in this post you’ll see the message broker way. Message brokers are software modules or services which allow distributed software applications to exchange information. The word “message” from “message broker” does not specifically refer to SMS text messages, but rather to chunks of information transferred between applications. Where necessary, this post will refer to these messages as application messages in order to distinguish them from SMS messages. If you’re not familiar with message brokers, pause for just a minute and read the Message broker Wikipedia page.

What are you going to learn

In this post you will learn basic things about message brokers. You’re going to see Apache ActiveMQ™, a popular open source message broker in action, learn how to write client applications for the broker, and learn how these clients interact with it. You’ll also see how to use Twilio Programmable SMS and the Twilio helper library for Node.js to send SMS messages.

What are you going to build

This tutorial provides complete instructions and code for building a Node.js application that reads appointment and customer data from a .csv file, then sends appointment reminders a few minutes before each appointment by scheduling the delivery of SMS text messages.

Prerequisites

You’ll need the following tools and resources to build the project described in this tutorial. For best results, fulfill these prerequisites in the order listed:

  • Java – version 8+, required by ActiveMQ
  • Node.js – The Node.js installer also installs npm, which you’ll need to install packages.
  • Visual Studio Code – or your preferred IDE or text editor
  • Twilio trial account – Sign up with this link to get a $10 credit when you upgrade your account.
  • Twilio phone number – Select a number with SMS capability.

This tutorial uses the Java SE runtime environment, but doesn’t require any Java programming knowledge. A working knowledge of JavaScript and the Node.js runtime environment will be helpful. No prior experience with the Twilio Programmable SMS API or the helper library for Node.js is necessary.

There is a companion repository for this post available on GitHub. It contains the complete source code for the project you’ll build in this tutorial.

Installing ActiveMQ

Verify that you have the required Java Runtime Environment. Open a terminal or a command window and run:

java -version

The output should show Java version 8 or greater and be similar to:

java version "1.8.0_211"
Java(TM) SE Runtime Environment (build 1.8.0_211-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.211-b12, mixed mode)

Download ActiveMQ from http://activemq.apache.org/. For the purpose of this tutorial, download the ActiveMQ 5 "Classic" edition. You can find detailed installation instructions for both Unix (including Mac) and Windows at http://activemq.apache.org/getting-started

In short, download the appropriate archive for your operating system and extract it on a location of your choice, preferably choose a path without spaces. For example the installation directory could be /Users/<account name>/Applications/apache-activemq-5.16.0 on Mac or C:\apache-activemq-5.16.0 on Windows.

Take a moment to look inside the ActiveMQ installation folder. The docs subfolder contains a few short notes about how to start and stop the broker and about the ActiveMQ web console. The conf subfolder contains configuration files. The bin subfolder has scripts to run and manage the broker.

Starting and stopping the ActiveMQ broker

For the purpose of this tutorial you’ll run the broker as a foreground process. Open a terminal or a command window and change directory to the ActiveMQ installation folder (apache-activemq-5.16.0). It's important to run the start command from within this folder.

  • On Mac start the broker with ./bin/activemq console
  • On Windows start the broker with .\bin\activemq.bat start

The broker process can be stopped on both Windows or Mac by typing Ctrl + C in the terminal or command window in which it is running. Go ahead and try Ctrl + C, then start the broker again and leave it running.

After you start the broker, the ActiveMQ administrative interface is available at http://127.0.0.1:8161/admin/ and you can open the link with your browser and login with the default credentials (username “admin” and password “admin”). After signing in, stay on the Home page.

ActiveMQ Home page screenshot

Sending SMS messages from ActiveMQ

Two of the key features supported by message brokers are queues and topics. Think about queues and topics as smart, structured storage made available and managed by the broker. A client application can publish or provide information to a queue or topic, another client application can subscribe and consume information from a queue or topic.

Queues are used in one-to-one or point-to-point type of integrations, where a message is received by exactly one consumer. Topics are used in one-to-many or publish-subscribe type of integrations where a message is distributed to all subscribers of the topic.

You can also publish application messages to a queue or topic by using a form from the ActiveMQ administrative interface. A client application can then connect to the broker and consume these messages.

In this step you’ll write a Node.js client application. The client connects to the broker, reads (consumes) application messages from a queue, then, based on the data retrieved, makes requests to Twilio's Programmable Messaging API and sends SMS messages.

Begin by creating a new project folder named apache-activemq-twilio. Next, create a new text file package.json and place the following code in it:

{
    "name": "apache-activemq-twilio",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "private": "true",
    "dependencies": {
        "@stomp/stompjs": "^5.4.4",
        "csvtojson": "^2.0.10",
        "dotenv": "^8.2.0",
        "moment": "^2.27.0",
        "twilio": "^3.48.2",
        "websocket": "^1.0.31"
    }
}

Open a new terminal or command window, change directory to the apache-activemq-twilio project folder and run:

npm install

This will add to your project and install the following Node.js modules:

  • @stomp/stompjs enables communication with ActiveMQ
  • csvtojson used to convert data from .csv file into a JSON object
  • dotenv used to read environment variables from .env files, if used
  • moment used to calculate the delay in seconds between two points in time (dates)
  • twilio used to communicate with Twilio’s API
  • websocket enables communication with ActiveMQ

When npm finishes installing, you can verify that all required modules have been installed if you run npm list --depth=0.

Create the following local environment variables by following the standard instructions provided by Twilio in the docs. Enter values to match your Twilio credentials and your Twilio phone number. The value you enter for TWILIO_NUMBER should be in E.164 format.

  • TWILIO_ACCOUNT_SID
  • TWILIO_AUTH_TOKEN
  • TWILIO_NUMBER

Alternatively, you can create a new text file named .env to store your Twilio credentials. This is unsecure, and should only be used if you’re unable to use environment variables.

The .env file should contain the following entries:

TWILIO_ACCOUNT_SID=AC...
TWILIO_AUTH_TOKEN=4f...
TWILIO_NUMBER=+1...

Replace the mock values with values to match your Twilio credentials. The value you enter for TWILIO_NUMBER should be in E.164 format. If you're going to use Git to version control your project, be sure to add .env to your .gitignore file.

Next, create a new text file named consumer.js. This file contains the code for the main consumer client application. Paste the following code in it:

// consumer.js

// Node.js packages used to communicate with ActiveMQ 
// utilising WebSocket and STOMP protocols
const StompJs = require('@stomp/stompjs');
Object.assign(global, { WebSocket: require('websocket').w3cwebsocket });

// Node.js package used to read environment variables
require('dotenv').config();

// creates a Twilio client
const twilio = require('twilio')(
    process.env.TWILIO_ACCOUNT_SID,
    process.env.TWILIO_AUTH_TOKEN
);

// create a STOMP client for ActiveMQ
const stompClient = new StompJs.Client({
    brokerURL: "ws://localhost:61614/ws"
});

// connect with the broker
stompClient.activate();
console.log("STOMP client activated...");

// on connect subscribe to queue and consume messages
stompClient.onConnect = (frame) => {
    console.log("STOMP client connected...");

    // the queue you're interested in is identified by "foo.bar"
    const queue = "/queue/foo.bar";
    const headers = { ack: 'auto' };

    stompClient.subscribe(
        queue,
        onMessageCallback,
        headers
    );

}

// invoked for each received message
const onMessageCallback = (jsonMessage) => {
    // expecting JSON
    try {
        const jsonObj = JSON.parse(jsonMessage.body);
        sendSMSWithTwilio(jsonObj.to, jsonObj.body);
    } catch (err) {
        console.log("Payload is not a JSON...");
    }
}

// sends the SMS
function sendSMSWithTwilio(to, body) {
    twilio.messages
        .create({
            to: to,
            from: process.env.TWILIO_NUMBER,
            body: body
        })
        .then((message) => {
            console.log(`SMS: ${message.sid}`);
        })
        .catch((err) => {
            console.error(err);
        });
}

Go back to your terminal or command window (you should still be in the apache-activemq-twilio project folder) and run:

node consumer.js

This starts the consumer client. The client connects to the broker and consumes any messages sent to the foo.bar queue.

The application messages must be in the following JSON format:

 {
   "to": "+1...", 
   "body": "Hey..., this is a message from ActiveMQ!"
}
The value for the `to` element is the phone number to receive SMS messages from your Twilio phone number and `body` is the message you want to be sent via SMS.

It's time to send a message using the Send page from the ActiveMQ administrative interface. In your browser, if it's not already opened, open http://127.0.0.1:8161/admin/. This should take you to the Home page. Next, click on the Send link from the top menu. This will bring up the Send a JMS Message page like the one shown below:

Look at the most relevant form fields in the Send a JMS Message page. Observe the following form data:

  • Destination field has the value "foo.bar", which is the name of the queue in which the message will go
  • Queue or Topic dropdown list has "Queue" selected
  • Message body contains some random text

For now, leave everything in the form fields as is, click the Send button, then look at the terminal or command window in which consumer.js is running. You should see "Payload is not a JSON…". That's because the Message body form field did not contain data in the expected JSON format.

To make it work, return to the ActiveMQ interface and click the Send menu again. This time, in the Message body field replace "Enter some text here for the message body…" with:

{
   "to": "+1...", 
   "body": "Hey..., this is a message from ActiveMQ!"
}

Replace the value of the to; element with the phone number of the mobile device you registered when you created your Twilio account. Twilio trial accounts can only send messages to registered numbers.

The user interface should look something like this:

ActiveMS Send page screenshot with JSON payload

Click the Send button. You should receive a text message on your phone. Also, you should see something like “SMS: SM123a4bc567890d1e2f34567abcd123ef” in the terminal or console window where consumer.js is running.

Congratulations, you just sent an SMS from the message broker.

Enabling the ActiveMQ Scheduler

The default ActiveMQ configuration file doesn't enable the scheduler service. You will have to do it yourself by editing the activemq.xml file located in the conf subfolder of the ActiveMQ installation folder.

Open activemq.xml with a text editor and find the line which has the following code:

<broker xmlns="http://activemq.apache.org/schema/core" brokerName="localhost" dataDirectory="${activemq.data}">

Add schedulerSupport="true" at the end so it looks like:

<broker xmlns="http://activemq.apache.org/schema/core" brokerName="localhost" dataDirectory="${activemq.data}" schedulerSupport="true">

Save the changes you have made to the activemq.xml file, then stop the broker and start it again as a foreground process

You can now try to send a delayed SMS. On the Send page of the ActiveMQ admin interface, enter a valid JSON in the Message body field. In the delay(ms) field enter “5000”.

ActiveMQ user interface screenshot showing a value of 5000 in the delay(ms) field.

Click the Send button. You should receive the SMS message on your phone, not immediately but only after 5 seconds have passed.

Scheduling the SMS messages to be sent minutes before the appointment

In this part you'll write the producer client application. The producer reads a .csv file, parses the appointments information, then, for each appointment publishes an application message to the foo.bar queue made available by the broker.

In the apache-activemq-twilio project folder create a new text file named producer.js. Place the following code in it:

// producer.js

// Node.js packages used to communicate with ActiveMQ 
// utilising WebSocket and STOMP protocols
const StompJs = require('@stomp/stompjs');
Object.assign(global, { WebSocket: require('websocket').w3cwebsocket });

// Node.js package used to read data from .csv files
const csv = require('csvtojson');

// Node.js package used to work with dates and times
const moment = require('moment');

let appointmentsData = {};

// send the reminder "x" minutes before the appointment time
const leadTimeInMinutes = 15;

// create a STOMP client for ActiveMQ
const stompClient = new StompJs.Client({
    brokerURL: "ws://localhost:61614/ws"
});

// import appointments data from .csv file
csv()
    .fromFile("./appointments.csv")
    .then((jsonObj) => {
        appointmentsData = jsonObj;
        stompClient.activate();
        console.log("STOMP client activated...");
    });

// once connected add messages to queue then disconnect
stompClient.onConnect = (frame) => {
    console.log("STOMP client connected...");
    publishToQueue(appointmentsData);
};

function publishToQueue(data) {

    // stop condition, all application messages added to queue
    if (data.length === 0) {
        stompClient.deactivate();
        console.log("STOMP client deactivated.");
        return;
    }

    let row = data.shift();
    let mqData = {
        to: row['Phone'],
                body: ("Hello " + row['Name'] +", you have an appointment with us in " + leadTimeInMinutes + " minutes. See you soon.")
    };

   // publish the current application message to the "foo.bar" queue
    // uses AMQ_SCHEDULED_DELAY, the time in milliseconds that a message will wait
    // must be a positive value
    if (toDelayFromDate(row['AppointmentDateTime']) > 0) {
        console.log(toDelayFromDate(row['AppointmentDateTime']));
        console.log("publish...");
        stompClient.publish({
            destination: '/queue/foo.bar',
            body: JSON.stringify(mqData),
            headers: {
                'content-type': 'application/json',
                AMQ_SCHEDULED_DELAY: toDelayFromDate(row['AppointmentDateTime'])
            }
        });
    }

    // recursive call until all messages are added to queue
    publishToQueue(data);

}

// utility function, returns milliseconds
// calculates the difference between the appointment time and the current time
function toDelayFromDate(dateTime) {
    let appointmentDateTime = new moment(dateTime);
    let now = new moment();
    const delay = (moment.duration(appointmentDateTime.diff(now)).as('milliseconds'));
    const leadTimeInMilliseconds = (leadTimeInMinutes * 60 * 1000);
    return (delay - leadTimeInMilliseconds);
}

In the apache-activemq-twilio project folder create a new file named appointments.csv. Place the following text in it:

Name,Phone,AppointmentDateTime
Louise,+1...,2020-08-20 20:53
Alex,+1...,2020-08-20 20:52
Johnny,+1...,2020-08-20 20:51

Replace +1... phone numbers with your phone number or the phone numbers where you want to receive the test SMS messages. (Note that you can only send SMS messages to a registered phone number with a Twilio trial account.) In production these numbers should be the phone numbers of your customers.

Replace dates and times. In production, dates and times should be the accurate dates and times of the appointments. While testing, use dates and times which are at least const leadTimeInMinutes = 15; minutes in the future.

Open a new terminal or command window, change directory to the apache-activemq-twilio project folder and run:

node producer.js

Congratulations, you just scheduled SMS messages to be delivered at a future time.

You can close the terminal or console window in which node producer.js was running, but keep the broker and node consumer.js terminals or console windows open. When the scheduled times you entered in the appointments.csv file roll around you should receive the SMS messages on your phone.

While waiting for the SMS messages to arrive, take a look at the Scheduled page in the ActiveMQ administrative interface. You should see a list with the pending messages.

ActiveMQ Scheduled page screenshot

Summary

You now have a basic idea about message brokers. It's fairly easy to install and run Apache ActiveMQ. You have created two Node.js applications as clients for the message broker. You have learned how to schedule requests to Twilio's Programmable SMS API by taking advantage of ActiveMQ's scheduler.

Next steps

Here are some suggestions for ways you can explore the capabilities of ActiveMQ and learn more:

  • Explore the ActiveMQ administrative interface. Click the links on the top menu to look at the other pages.
  • Start the ActiveMQ broker in the background or as a service. On Mac run ./bin/activemq start instead of ./bin/activemq console. If you're on Windows, run InstallService.bat as administrator then restart your PC. InstallService.bat can be found inside win32 or win64 subfolders from inside the bin subfolder of the ActiveMQ installation folder.
  • Run consumer.js in the background. There are plenty of options if you google “install node.js app as a windows service”. Choose the one which fits you best.
  • Install and run ActiveMQ and consumer.js on a virtual private server in the cloud. If you have a firewall in front of your VPS, you’ll need to open TCP ports 8161 and 61614.

Additional resources

Refer to the following resources for more in-depth information and learning opportunities:

IBM Cloud Learn Hub > Message Brokers – Learn about message brokers, message broker architectures, how to use them in cloud architectures and how they differ from APIs.

Apache ActiveMQ Delay and Schedule Message Delivery – Reference documentation for the persistent scheduler built into ActiveMQ.

STOMP.js – This Node.js package allows you to connect to a STOMP broker over WebSockets.

Using STOMP JS – Reference documentation for the STOMP.js Node.js package.

csvtojson – A Node.js package which parses .csv files and converts the data to a JSON object.

TwilioQuest – Are your JavaScript skills combat-ready? Help defeat the forces of legacy systems with missions inspired by the 16-bit golden age.

Alex Baban is a Romanian-born Canadian web and mobile developer and is a Twilio Champion. Alex has been working on web sites in various capacities for over 15 years. In addition to software development, Alex has a background in electronics, with a Bachelor of Science in Electrical Engineering from the Technical University of Cluj-Napoca, Romania. Alex can be reached on Twitter @alexbaban.

Updated 2020-10-02: formatting, tags