# Send Appointment Reminders with Node.js and Express

> \[!NOTE]
>
> 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](/docs/messaging/features/message-scheduling) to learn more about scheduling messages.

This Node.js [Express](https://expressjs.com/en/index.html) 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](https://github.com/TwilioDevEd/appointment-reminders-node) to see how to run the code yourself.

*[Check out how Yelp uses SMS to confirm restaurant reservations for diners.](https://customers.twilio.com/1154/yelp/?utm_source=docs\&utm_campaign=docs_to_stories)*

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

## Configure the application to use Twilio

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](/console). You'll also need an SMS-enabled phone number - you can find or purchase a new one [here](/user/account/phone-numbers/incoming).

```bash title="Configure the application to use Twilio"
TWILIO_ACCOUNT_SID=Your-Account-SID
TWILIO_AUTH_TOKEN=Your-Twilio-Auth-Token
TWILIO_PHONE_NUMBER=Your-Twilio-Phone-Number
MONGO_URL=Mongo-Url
MONGO_URL_TEST=mongodb://127.0.0.1:27017/appointment-reminders
NODE_ENV=production
```

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

## Create a new appointment

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](https://mongoosejs.com/docs/index.html) in this application to store our model in [MongoDB](https://docs.mongodb.org/).

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

```js title="Create a new appointment" description="routes/appointments.js"
// !mark(22:31)
'use strict';

const express = require('express');
const momentTimeZone = require('moment-timezone');
const moment = require('moment');
const Appointment = require('../models/appointment');
const router = new express.Router();


const getTimeZones = function() {
  return momentTimeZone.tz.names();
};

// GET: /appointments
router.get('/', function(req, res, next) {
  Appointment.find()
    .then(function(appointments) {
      res.render('appointments/index', {appointments: appointments});
    });
});

// GET: /appointments/create
router.get('/create', function(req, res, next) {
  res.render('appointments/create', {
    timeZones: getTimeZones(),
    appointment: new Appointment({name: '',
                                  phoneNumber: '',
                                  notification: '',
                                  timeZone: '',
                                  time: ''})});
});

// POST: /appointments
router.post('/', function(req, res, next) {
  const name = req.body.name;
  const phoneNumber = req.body.phoneNumber;
  const notification = req.body.notification;
  const timeZone = req.body.timeZone;
  const time = moment(req.body.time, 'MM-DD-YYYY hh:mma');

  const appointment = new Appointment({name: name,
                                       phoneNumber: phoneNumber,
                                       notification: notification,
                                       timeZone: timeZone,
                                       time: time});
  appointment.save()
    .then(function() {
      res.redirect('/');
    });
});

// GET: /appointments/:id/edit
router.get('/:id/edit', function(req, res, next) {
  const id = req.params.id;
  Appointment.findOne({_id: id})
    .then(function(appointment) {
      res.render('appointments/edit', {timeZones: getTimeZones(),
                                       appointment: appointment});
    });
});

// POST: /appointments/:id/edit
router.post('/:id/edit', function(req, res, next) {
  const id = req.params.id;
  const name = req.body.name;
  const phoneNumber = req.body.phoneNumber;
  const notification = req.body.notification;
  const timeZone = req.body.timeZone;
  const time = moment(req.body.time, 'MM-DD-YYYY hh:mma');

  Appointment.findOne({_id: id})
    .then(function(appointment) {
      appointment.name = name;
      appointment.phoneNumber = phoneNumber;
      appointment.notification = notification;
      appointment.timeZone = timeZone;
      appointment.time = time;

      appointment.save()
        .then(function() {
          res.redirect('/');
        });
    });
});

// POST: /appointments/:id/delete
router.post('/:id/delete', function(req, res, next) {
  const id = req.params.id;

  Appointment.remove({_id: id})
    .then(function() {
      res.redirect('/');
    });
});

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

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](https://github.com/kelektiv/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()`

```js title="Schedule a job to send reminders" description="scheduler.js"
'use strict';

const CronJob = require('cron').CronJob;
const notificationsWorker = require('./workers/notificationsWorker');
const moment = require('moment');

const schedulerFactory = function() {
  return {
    start: function() {
      new CronJob('00 * * * * *', function() {
        console.log('Running Send Notifications Worker for ' +
          moment().format());
        notificationsWorker.run();
      }, null, true, '');
    },
  };
};

module.exports = schedulerFactory();
```

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

## Create a worker function to run the job

To actually execute our recurring job logic, we create a worker function which uses a [Static Model Method](https://mongoosejs.com/docs/guide.html#statics) to query the database for upcoming appointments and sends reminders as necessary.

```js title="Create a worker function to run the job" description="workers/notificationsWorker.js"
'use strict';

const Appointment = require('../models/appointment');

const notificationWorkerFactory = function() {
  return {
    run: function() {
      Appointment.sendNotifications();
    },
  };
};

module.exports = notificationWorkerFactory();
```

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

## Find appointments that need reminders

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](https://momentjs.com/) in order to properly query every upcoming appointment considering its time zone.

```js title="Find appointments that need reminders" description="models/appointment.js"
// !mark(16:20,22:34)
'use strict';

const mongoose = require('mongoose');
const moment = require('moment');
const cfg = require('../config');
const Twilio = require('twilio');

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

AppointmentSchema.methods.requiresNotification = function(date) {
  return Math.round(moment.duration(moment(this.time).tz(this.timeZone).utc()
                          .diff(moment(date).utc())
                        ).asMinutes()) === this.notification;
};

AppointmentSchema.statics.sendNotifications = function(callback) {
  // now
  const searchDate = new Date();
  Appointment
    .find()
    .then(function(appointments) {
      appointments = appointments.filter(function(appointment) {
              return appointment.requiresNotification(searchDate);
      });
      if (appointments.length > 0) {
        sendNotifications(appointments);
      }
    });

    /**
    * Send messages to all appoinment owners via Twilio
    * @param {array} appointments List of appointments.
    */
    function sendNotifications(appointments) {
        const client = new Twilio(cfg.twilioAccountSid, cfg.twilioAuthToken);
        appointments.forEach(function(appointment) {
            // Create options to send the message
            const options = {
                to: `+ ${appointment.phoneNumber}`,
                from: cfg.twilioPhoneNumber,
                /* eslint-disable max-len */
                body: `Hi ${appointment.name}. Just a reminder that you have an appointment coming up.`,
                /* eslint-enable max-len */
            };

            // Send the message!
            client.messages.create(options, function(err, response) {
                if (err) {
                    // Just log it for now
                    console.error(err);
                } else {
                    // Log the last few digits of a phone number
                    let masked = appointment.phoneNumber.substr(0,
                        appointment.phoneNumber.length - 5);
                    masked += '*****';
                    console.log(`Message sent to ${masked}`);
                }
            });
        });

        // Don't wait on success/failure, just indicate all messages have been
        // queued for delivery
        if (callback) {
          callback.call();
        }
    }
};


const Appointment = mongoose.model('appointment', AppointmentSchema);
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

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](/user/account/phone-numbers/incoming), 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.

```js title="Send reminder messages with the Twilio API" description="models/appointment.js"
// !mark(36:73)
'use strict';

const mongoose = require('mongoose');
const moment = require('moment');
const cfg = require('../config');
const Twilio = require('twilio');

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

AppointmentSchema.methods.requiresNotification = function(date) {
  return Math.round(moment.duration(moment(this.time).tz(this.timeZone).utc()
                          .diff(moment(date).utc())
                        ).asMinutes()) === this.notification;
};

AppointmentSchema.statics.sendNotifications = function(callback) {
  // now
  const searchDate = new Date();
  Appointment
    .find()
    .then(function(appointments) {
      appointments = appointments.filter(function(appointment) {
              return appointment.requiresNotification(searchDate);
      });
      if (appointments.length > 0) {
        sendNotifications(appointments);
      }
    });

    /**
    * Send messages to all appoinment owners via Twilio
    * @param {array} appointments List of appointments.
    */
    function sendNotifications(appointments) {
        const client = new Twilio(cfg.twilioAccountSid, cfg.twilioAuthToken);
        appointments.forEach(function(appointment) {
            // Create options to send the message
            const options = {
                to: `+ ${appointment.phoneNumber}`,
                from: cfg.twilioPhoneNumber,
                /* eslint-disable max-len */
                body: `Hi ${appointment.name}. Just a reminder that you have an appointment coming up.`,
                /* eslint-enable max-len */
            };

            // Send the message!
            client.messages.create(options, function(err, response) {
                if (err) {
                    // Just log it for now
                    console.error(err);
                } else {
                    // Log the last few digits of a phone number
                    let masked = appointment.phoneNumber.substr(0,
                        appointment.phoneNumber.length - 5);
                    masked += '*****';
                    console.log(`Message sent to ${masked}`);
                }
            });
        });

        // Don't wait on success/failure, just indicate all messages have been
        // queued for delivery
        if (callback) {
          callback.call();
        }
    }
};


const Appointment = mongoose.model('appointment', AppointmentSchema);
module.exports = Appointment;
```

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

## Where to next?

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](/docs/messaging/tutorials/workflow-automation/node)**

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

**[Browser Calls](/docs/voice/sdks/javascript/get-started)**

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