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 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 to see how to run the code yourself.
Check out how Yelp uses SMS to confirm restaurant reservations for diners.
Let's get started! Click the button below to get started.
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.
In order to send an appointment reminder we need to have an appointment first!
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 in this application to store our model in MongoDB.
_10var AppointmentSchema = new mongoose.Schema({_10 name:String,_10 phoneNumber: String,_10 notification : Number,_10 timeZone : String,_10 time : {type : Date, index : true}_10});
routes/appointments.js
_96'use strict';_96_96const express = require('express');_96const momentTimeZone = require('moment-timezone');_96const moment = require('moment');_96const Appointment = require('../models/appointment');_96const router = new express.Router();_96_96_96const getTimeZones = function() {_96 return momentTimeZone.tz.names();_96};_96_96// GET: /appointments_96router.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_96router.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_96router.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_96router.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_96router.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_96router.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_96module.exports = router;
Now that we have our Appointment
created, let's see how to schedule a reminder for it.
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.
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()
scheduler.js
_19'use strict';_19_19const CronJob = require('cron').CronJob;_19const notificationsWorker = require('./workers/notificationsWorker');_19const moment = require('moment');_19_19const 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_19module.exports = schedulerFactory();
This start
function uses a notificationsWorker
, next we'll see how it works.
To actually execute our recurring job logic, we create a worker function which uses a Static Model Method to query the database for upcoming appointments and sends reminders as necessary.
workers/notificationsWorker.js
_13'use strict';_13_13const Appointment = require('../models/appointment');_13_13const notificationWorkerFactory = function() {_13 return {_13 run: function() {_13 Appointment.sendNotifications();_13 },_13 };_13};_13_13module.exports = notificationWorkerFactory();
Next, let's see how the Appointment
job works in detail.
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 in order to properly query every upcoming appointment considering its time zone.
models/appointment.js
_77'use strict';_77_77const mongoose = require('mongoose');_77const moment = require('moment');_77const cfg = require('../config');_77const Twilio = require('twilio');_77_77const AppointmentSchema = new mongoose.Schema({_77 name: String,_77 phoneNumber: String,_77 notification: Number,_77 timeZone: String,_77 time: {type: Date, index: true},_77});_77_77AppointmentSchema.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_77AppointmentSchema.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_77const Appointment = mongoose.model('appointment', AppointmentSchema);_77module.exports = Appointment;
All that is left is to send the actual SMS. We'll see that next.
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.
models/appointment.js
_77'use strict';_77_77const mongoose = require('mongoose');_77const moment = require('moment');_77const cfg = require('../config');_77const Twilio = require('twilio');_77_77const AppointmentSchema = new mongoose.Schema({_77 name: String,_77 phoneNumber: String,_77 notification: Number,_77 timeZone: String,_77 time: {type: Date, index: true},_77});_77_77AppointmentSchema.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_77AppointmentSchema.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_77const Appointment = mongoose.model('appointment', AppointmentSchema);_77module.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:
Build a ready-for-scale automated SMS workflow for a vacation rental company.
Make browser-to-phone and browser-to-browser calls with ease.
Thanks for checking out this tutorial! If you have any feedback to share with us, please reach out on Twitter... we'd love to hear your thoughts, and know what you're building!