Skip to contentSkip to navigationSkip to topbar
Rate this page:
On this page

Send Appointment Reminders with Node.js and Express


(information)

Info

Ahoy! We now recommend you build your appointment reminders with Twilio's built in Message Scheduling functionality. Head on over to the Message Scheduling documentation to learn more about scheduling messages!

This Node.js Express(link takes you to an external page) web application sends out reminders for future appointments that customers can create through the application as well. This is done through a background job that runs every minute.

On this tutorial we'll point out the key bits of code that make this application work. Check out the project README on GitHub(link takes you to an external page) to see how to run the code yourself.

Check out how Yelp uses SMS to confirm restaurant reservations for diners.(link takes you to an external page)

Let's get started! Click the button below to get started.


Configure the application to use Twilio

configure-the-application-to-use-twilio page anchor

Before we can use the Twilio API to send reminder text messages we need to configure our account credentials. These can be found on your Twilio Console. You'll also need an SMS-enabled phone number - you can find or purchase a new one here.

Configure the application to use Twilio|.env

In order to send an appointment reminder we need to have an appointment first!


Create a new appointment

create-a-new-appointment page anchor

On the controller we input the information required (a customer's name and phone number, plus a time and date for the appointment) by saving it on an Appointment model.

We use mongoose(link takes you to an external page) in this application to store our model in MongoDB(link takes you to an external page).


_10
var AppointmentSchema = new mongoose.Schema({
_10
name:String,
_10
phoneNumber: String,
_10
notification : Number,
_10
timeZone : String,
_10
time : {type : Date, index : true}
_10
});

Create a new appointment

create-a-new-appointment-1 page anchor

routes/appointments.js


_96
'use strict';
_96
_96
const express = require('express');
_96
const momentTimeZone = require('moment-timezone');
_96
const moment = require('moment');
_96
const Appointment = require('../models/appointment');
_96
const router = new express.Router();
_96
_96
_96
const getTimeZones = function() {
_96
return momentTimeZone.tz.names();
_96
};
_96
_96
// GET: /appointments
_96
router.get('/', function(req, res, next) {
_96
Appointment.find()
_96
.then(function(appointments) {
_96
res.render('appointments/index', {appointments: appointments});
_96
});
_96
});
_96
_96
// GET: /appointments/create
_96
router.get('/create', function(req, res, next) {
_96
res.render('appointments/create', {
_96
timeZones: getTimeZones(),
_96
appointment: new Appointment({name: '',
_96
phoneNumber: '',
_96
notification: '',
_96
timeZone: '',
_96
time: ''})});
_96
});
_96
_96
// POST: /appointments
_96
router.post('/', function(req, res, next) {
_96
const name = req.body.name;
_96
const phoneNumber = req.body.phoneNumber;
_96
const notification = req.body.notification;
_96
const timeZone = req.body.timeZone;
_96
const time = moment(req.body.time, 'MM-DD-YYYY hh:mma');
_96
_96
const appointment = new Appointment({name: name,
_96
phoneNumber: phoneNumber,
_96
notification: notification,
_96
timeZone: timeZone,
_96
time: time});
_96
appointment.save()
_96
.then(function() {
_96
res.redirect('/');
_96
});
_96
});
_96
_96
// GET: /appointments/:id/edit
_96
router.get('/:id/edit', function(req, res, next) {
_96
const id = req.params.id;
_96
Appointment.findOne({_id: id})
_96
.then(function(appointment) {
_96
res.render('appointments/edit', {timeZones: getTimeZones(),
_96
appointment: appointment});
_96
});
_96
});
_96
_96
// POST: /appointments/:id/edit
_96
router.post('/:id/edit', function(req, res, next) {
_96
const id = req.params.id;
_96
const name = req.body.name;
_96
const phoneNumber = req.body.phoneNumber;
_96
const notification = req.body.notification;
_96
const timeZone = req.body.timeZone;
_96
const time = moment(req.body.time, 'MM-DD-YYYY hh:mma');
_96
_96
Appointment.findOne({_id: id})
_96
.then(function(appointment) {
_96
appointment.name = name;
_96
appointment.phoneNumber = phoneNumber;
_96
appointment.notification = notification;
_96
appointment.timeZone = timeZone;
_96
appointment.time = time;
_96
_96
appointment.save()
_96
.then(function() {
_96
res.redirect('/');
_96
});
_96
});
_96
});
_96
_96
// POST: /appointments/:id/delete
_96
router.post('/:id/delete', function(req, res, next) {
_96
const id = req.params.id;
_96
_96
Appointment.remove({_id: id})
_96
.then(function() {
_96
res.redirect('/');
_96
});
_96
});
_96
_96
module.exports = router;

Now that we have our Appointment created, let's see how to schedule a reminder for it.


Schedule a job to send reminders

schedule-a-job-to-send-reminders page anchor

Every minute we'd like our application to check the appointments database to see if any appointments are coming up that require reminders to be sent out.

To do this we use node-cron(link takes you to an external page).

We configure on the start function both the job code we'd like to run, and the interval on which to run it. Then we call it from the application execution entry point like this: scheduler.start()

Schedule a job to send reminders

schedule-a-job-to-send-reminders-1 page anchor

scheduler.js


_19
'use strict';
_19
_19
const CronJob = require('cron').CronJob;
_19
const notificationsWorker = require('./workers/notificationsWorker');
_19
const moment = require('moment');
_19
_19
const schedulerFactory = function() {
_19
return {
_19
start: function() {
_19
new CronJob('00 * * * * *', function() {
_19
console.log('Running Send Notifications Worker for ' +
_19
moment().format());
_19
notificationsWorker.run();
_19
}, null, true, '');
_19
},
_19
};
_19
};
_19
_19
module.exports = schedulerFactory();

This start function uses a notificationsWorker, next we'll see how it works.


Create a worker function to run the job

create-a-worker-function-to-run-the-job page anchor

To actually execute our recurring job logic, we create a worker function which uses a Static Model Method(link takes you to an external page) to query the database for upcoming appointments and sends reminders as necessary.

Create a worker function to run the job

create-a-worker-function-to-run-the-job-1 page anchor

workers/notificationsWorker.js


_13
'use strict';
_13
_13
const Appointment = require('../models/appointment');
_13
_13
const notificationWorkerFactory = function() {
_13
return {
_13
run: function() {
_13
Appointment.sendNotifications();
_13
},
_13
};
_13
};
_13
_13
module.exports = notificationWorkerFactory();

Next, let's see how the Appointment job works in detail.


Find appointments that need reminders

find-appointments-that-need-reminders page anchor

Our recurring job uses a static model method of the Appointment model to query the database for appointments coming up in the current minute and send out reminder messages using a Twilio REST Client we previously initialized with our Twilio account credentials.

Because of the fact that appointments are defined in different time zones, we use Moment.js library(link takes you to an external page) in order to properly query every upcoming appointment considering its time zone.

Find appointments that need reminders

find-appointments-that-need-reminders-1 page anchor

models/appointment.js


_77
'use strict';
_77
_77
const mongoose = require('mongoose');
_77
const moment = require('moment');
_77
const cfg = require('../config');
_77
const Twilio = require('twilio');
_77
_77
const AppointmentSchema = new mongoose.Schema({
_77
name: String,
_77
phoneNumber: String,
_77
notification: Number,
_77
timeZone: String,
_77
time: {type: Date, index: true},
_77
});
_77
_77
AppointmentSchema.methods.requiresNotification = function(date) {
_77
return Math.round(moment.duration(moment(this.time).tz(this.timeZone).utc()
_77
.diff(moment(date).utc())
_77
).asMinutes()) === this.notification;
_77
};
_77
_77
AppointmentSchema.statics.sendNotifications = function(callback) {
_77
// now
_77
const searchDate = new Date();
_77
Appointment
_77
.find()
_77
.then(function(appointments) {
_77
appointments = appointments.filter(function(appointment) {
_77
return appointment.requiresNotification(searchDate);
_77
});
_77
if (appointments.length > 0) {
_77
sendNotifications(appointments);
_77
}
_77
});
_77
_77
/**
_77
* Send messages to all appoinment owners via Twilio
_77
* @param {array} appointments List of appointments.
_77
*/
_77
function sendNotifications(appointments) {
_77
const client = new Twilio(cfg.twilioAccountSid, cfg.twilioAuthToken);
_77
appointments.forEach(function(appointment) {
_77
// Create options to send the message
_77
const options = {
_77
to: `+ ${appointment.phoneNumber}`,
_77
from: cfg.twilioPhoneNumber,
_77
/* eslint-disable max-len */
_77
body: `Hi ${appointment.name}. Just a reminder that you have an appointment coming up.`,
_77
/* eslint-enable max-len */
_77
};
_77
_77
// Send the message!
_77
client.messages.create(options, function(err, response) {
_77
if (err) {
_77
// Just log it for now
_77
console.error(err);
_77
} else {
_77
// Log the last few digits of a phone number
_77
let masked = appointment.phoneNumber.substr(0,
_77
appointment.phoneNumber.length - 5);
_77
masked += '*****';
_77
console.log(`Message sent to ${masked}`);
_77
}
_77
});
_77
});
_77
_77
// Don't wait on success/failure, just indicate all messages have been
_77
// queued for delivery
_77
if (callback) {
_77
callback.call();
_77
}
_77
}
_77
};
_77
_77
_77
const Appointment = mongoose.model('appointment', AppointmentSchema);
_77
module.exports = Appointment;

All that is left is to send the actual SMS. We'll see that next.


Send reminder messages with the Twilio API

send-reminder-messages-with-the-twilio-api page anchor

This code is called for every appointment coming up that requires a reminder to be sent. We provide a configuration object with a to field, which is the customer's phone number, a from field, which is a number in our account, and a body field, which contains the text of the message. Then we pass it to the sendMessage method along with a callback to log errors and success.

Send reminder messages with the Twilio API

send-reminder-messages-with-the-twilio-api-1 page anchor

models/appointment.js


_77
'use strict';
_77
_77
const mongoose = require('mongoose');
_77
const moment = require('moment');
_77
const cfg = require('../config');
_77
const Twilio = require('twilio');
_77
_77
const AppointmentSchema = new mongoose.Schema({
_77
name: String,
_77
phoneNumber: String,
_77
notification: Number,
_77
timeZone: String,
_77
time: {type: Date, index: true},
_77
});
_77
_77
AppointmentSchema.methods.requiresNotification = function(date) {
_77
return Math.round(moment.duration(moment(this.time).tz(this.timeZone).utc()
_77
.diff(moment(date).utc())
_77
).asMinutes()) === this.notification;
_77
};
_77
_77
AppointmentSchema.statics.sendNotifications = function(callback) {
_77
// now
_77
const searchDate = new Date();
_77
Appointment
_77
.find()
_77
.then(function(appointments) {
_77
appointments = appointments.filter(function(appointment) {
_77
return appointment.requiresNotification(searchDate);
_77
});
_77
if (appointments.length > 0) {
_77
sendNotifications(appointments);
_77
}
_77
});
_77
_77
/**
_77
* Send messages to all appoinment owners via Twilio
_77
* @param {array} appointments List of appointments.
_77
*/
_77
function sendNotifications(appointments) {
_77
const client = new Twilio(cfg.twilioAccountSid, cfg.twilioAuthToken);
_77
appointments.forEach(function(appointment) {
_77
// Create options to send the message
_77
const options = {
_77
to: `+ ${appointment.phoneNumber}`,
_77
from: cfg.twilioPhoneNumber,
_77
/* eslint-disable max-len */
_77
body: `Hi ${appointment.name}. Just a reminder that you have an appointment coming up.`,
_77
/* eslint-enable max-len */
_77
};
_77
_77
// Send the message!
_77
client.messages.create(options, function(err, response) {
_77
if (err) {
_77
// Just log it for now
_77
console.error(err);
_77
} else {
_77
// Log the last few digits of a phone number
_77
let masked = appointment.phoneNumber.substr(0,
_77
appointment.phoneNumber.length - 5);
_77
masked += '*****';
_77
console.log(`Message sent to ${masked}`);
_77
}
_77
});
_77
});
_77
_77
// Don't wait on success/failure, just indicate all messages have been
_77
// queued for delivery
_77
if (callback) {
_77
callback.call();
_77
}
_77
}
_77
};
_77
_77
_77
const Appointment = mongoose.model('appointment', AppointmentSchema);
_77
module.exports = Appointment;

That's it! Our application is all set to send out reminders for upcoming appointments.


We hope you found this sample application useful. If you're a Node.js/Express developer working with Twilio you might enjoy these other tutorials:

Workflow Automation

Build a ready-for-scale automated SMS workflow for a vacation rental company.

Browser Calls

Make browser-to-phone and browser-to-browser calls with ease.

Did this help?

did-this-help page anchor

Thanks for checking out this tutorial! If you have any feedback to share with us, please reach out on Twitter(link takes you to an external page)... we'd love to hear your thoughts, and know what you're building!


Rate this page: