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

Workflow Automation with Node.js and Express


One of the more abstract concepts you'll handle when building your business is what the workflow will look like.

At its core, setting up a standardized workflow is about enabling your service providers (agents, hosts, customer service reps, administrators, and the rest of the gang) to better serve your customers.

To illustrate a very real-world example, today we'll build a Node.js and Express webapp for finding and booking vacation properties — tentatively called Airtng.

Here's how it'll work:

  1. A host creates a vacation property listing
  2. A guest requests a reservation for a property
  3. The host receives an SMS notifying them of the reservation request. The host can either Accept or Reject the reservation
  4. The guest is notified whether a request was rejected or accepted

Learn how Airbnb used Twilio SMS to streamline the rental experience for 60M+ travelers around the world.(link takes you to an external page)


Workflow Building Blocks

workflow-building-blocks page anchor

We'll be using the Twilio REST API to send our users messages at important junctures. Here's a bit more on our API:

  • Sending Messages with Twilio API

Ready to go? Boldly click the button right after this sentence.


For this workflow to work, we need to handle user authentication. We're going to rely on Passport(link takes you to an external page) for Node.js.

Each User will need to have a countryCode and a phoneNumber which will be required to send SMS notifications later.

User Model to handle authentication

user-model-to-handle-authentication page anchor

models/user.js


_17
var mongoose = require('mongoose');
_17
var passportLocalMongoose = require('passport-local-mongoose');
_17
_17
var userSchema = new mongoose.Schema({
_17
email: { type: String, required: true },
_17
username: { type: String, required: true },
_17
password: { type: String },
_17
countryCode: { type: String, required: true },
_17
phoneNumber: { type: String, required: true },
_17
date: { type: Date, default: Date.now },
_17
});
_17
_17
userSchema.plugin(passportLocalMongoose);
_17
_17
var user = mongoose.model('user', userSchema);
_17
_17
module.exports = user;

Next let's model the vacation properties.


In order to build our rentals company we'll need a way to create the property listings.

The Property belongs to the User who created it (we'll call this user the host moving forward) and contains only two properties: a description and an imageUrl.

The Vacation Property Model

the-vacation-property-model page anchor

models/property.js


_15
var mongoose = require('mongoose');
_15
_15
var propertySchema = new mongoose.Schema({
_15
description: { type: String, required: true },
_15
imageUrl: { type: String, required: true },
_15
date: { type: Date, default: Date.now },
_15
owner: {
_15
type: mongoose.Schema.Types.ObjectId,
_15
ref: 'user'
_15
}
_15
});
_15
_15
var property = mongoose.model('property', propertySchema);
_15
_15
module.exports = property;

Next up, the reservation model.


The Reservation model is at the center of the workflow for this application.

It is responsible for keeping track of:

  • The guest who performed the reservation
  • The vacation_property the guest is requesting (and associated host )
  • The status of the reservation: pending , confirmed , or rejected

models/reservation.js


_22
var mongoose = require('mongoose');
_22
var deepPopulate = require('mongoose-deep-populate')(mongoose);
_22
_22
var reservationSchema = new mongoose.Schema({
_22
message: { type: String, required: true },
_22
status: { type: String, default: 'pending' },
_22
date: { type: Date, default: Date.now },
_22
property: {
_22
type: mongoose.Schema.Types.ObjectId,
_22
ref: 'property'
_22
},
_22
guest: {
_22
type: mongoose.Schema.Types.ObjectId,
_22
ref: 'user'
_22
}
_22
});
_22
_22
reservationSchema.plugin(deepPopulate, {});
_22
_22
var reservation = mongoose.model('reservation', reservationSchema);
_22
_22
module.exports = reservation;

Next, let's look at triggering the creation of a new reservation.


The reservation creation form holds only a single field field: the message that will be sent to the host when reserving one of her properties. The rest of the information necessary to create a reservation is taken from the vacation property.

A reservation is created with a default status pending, so when the host replies with an accept or reject response our application knows which reservation to update.

routes/reservations.js


_70
var MessagingResponse = require('twilio').twiml.MessagingResponse;
_70
var twilio = require('twilio');
_70
var express = require('express');
_70
var router = express.Router();
_70
var Property = require('../models/property');
_70
var Reservation = require('../models/reservation');
_70
var User = require('../models/user');
_70
var notifier = require('../lib/notifier');
_70
_70
// POST: /reservations
_70
router.post('/', function (req, res) {
_70
var propertyId = req.body.propertyId;
_70
var user = req.user;
_70
_70
Property.findOne({ _id: propertyId })
_70
.then(function (property) {
_70
var reservation = new Reservation({
_70
message: req.body.message,
_70
property: propertyId,
_70
guest: user.id
_70
});
_70
_70
return reservation.save();
_70
})
_70
.then(function () {
_70
notifier.sendNotification();
_70
res.redirect('/properties');
_70
})
_70
.catch(function(err) {
_70
console.log(err);
_70
});
_70
});
_70
_70
// POST: /reservations/handle
_70
router.post('/handle', twilio.webhook({validate: false}), function (req, res) {
_70
var from = req.body.From;
_70
var smsRequest = req.body.Body;
_70
_70
var smsResponse;
_70
_70
User.findOne({phoneNumber: from})
_70
.then(function (host) {
_70
return Reservation.findOne({status: 'pending'});
_70
})
_70
.then(function (reservation) {
_70
if (reservation === null) {
_70
throw 'No pending reservations';
_70
}
_70
reservation.status = smsRequest.toLowerCase() === "accept" ? "confirmed" : "rejected";
_70
return reservation.save();
_70
})
_70
.then(function (reservation) {
_70
var message = "You have successfully " + reservation.status + " the reservation";
_70
respond(res, message);
_70
})
_70
.catch(function (err) {
_70
var message = "Sorry, it looks like you do not have any reservations to respond to";
_70
respond(res, message);
_70
});
_70
});
_70
_70
var respond = function(res, message) {
_70
var messagingResponse = new MessagingResponse();
_70
messagingResponse.message({}, message);
_70
_70
res.type('text/xml');
_70
res.send(messagingResponse.toString());
_70
}
_70
_70
module.exports = router;

In the next step, we'll take a look at how the SMS notification is sent to the host when the reservation is created.


When a reservation is created, we want to notify the owner of said property that someone has made a reservation.

This is where we use Twilio Node Helper Library to send an SMS message to the host, using our Twilio phone number. As you can see, sending SMS messages using Twilio is just a few lines of code.

Now we just have to wait for the host to send an SMS response accepting or rejecting the reservation so we can notify the guest and host that the reservation information is updated.

Send out reservation notifications

send-out-reservation-notifications page anchor

Notify through SMS the host of the new reservation


_44
var config = require('../config');
_44
var client = require('twilio')(config.accountSid, config.authToken);
_44
var Reservation = require('../models/reservation');
_44
_44
var sendNotification = function() {
_44
Reservation.find({status: 'pending'})
_44
.deepPopulate('property property.owner guest')
_44
.then(function (reservations) {
_44
if (reservations.length > 1) {
_44
return;
_44
}
_44
_44
var reservation = reservations[0];
_44
var owner = reservation.property.owner;
_44
_44
// Send the notification
_44
client.messages.create({
_44
to: phoneNumber(owner),
_44
from: config.phoneNumber,
_44
body: buildMessage(reservation)
_44
})
_44
.then(function(res) {
_44
console.log(res.body);
_44
})
_44
.catch(function(err) {
_44
console.log(err);
_44
});
_44
});
_44
};
_44
_44
var phoneNumber = function(owner) {
_44
return "+" + owner.countryCode + owner.phoneNumber;
_44
};
_44
_44
var buildMessage = function(reservation) {
_44
var message = "You have a new reservation request from " + reservation.guest.username +
_44
" for " + reservation.property.description + ":\n" +
_44
reservation.message + "\n" +
_44
"Reply accept or reject";
_44
_44
return message;
_44
};
_44
_44
exports.sendNotification = sendNotification;

The next step shows how to handle and configure the host's SMS response.


Handle Incoming Messages

handle-incoming-messages page anchor

The reservations/handle endpoint handles our incoming Twilio request and does three things:

  1. Checks for a pending reservation from the incoming user.
  2. Updates the status of the reservation.
  3. Responds to the host and sends notification to the guest.

In the Twilio console(link takes you to an external page), you should change the 'A Message Comes In' webhook to call your application's endpoint in the route /handle:

SMS Webhook.

One way to expose your machine to the world during development is to use ngrok(link takes you to an external page). Your URL for the SMS web hook on your phone number should look something like this:


_10
http://<subdomain>.ngrok.io/handle

An incoming request from Twilio comes with some helpful including the From phone number and the message Body.

We'll use the From parameter to lookup the host and check if she has any pending reservations. If she does, we'll use the message body to check if she accepted or rejected the reservation.

Deal with host responses

deal-with-host-responses page anchor

routes/reservations.js


_70
var MessagingResponse = require('twilio').twiml.MessagingResponse;
_70
var twilio = require('twilio');
_70
var express = require('express');
_70
var router = express.Router();
_70
var Property = require('../models/property');
_70
var Reservation = require('../models/reservation');
_70
var User = require('../models/user');
_70
var notifier = require('../lib/notifier');
_70
_70
// POST: /reservations
_70
router.post('/', function (req, res) {
_70
var propertyId = req.body.propertyId;
_70
var user = req.user;
_70
_70
Property.findOne({ _id: propertyId })
_70
.then(function (property) {
_70
var reservation = new Reservation({
_70
message: req.body.message,
_70
property: propertyId,
_70
guest: user.id
_70
});
_70
_70
return reservation.save();
_70
})
_70
.then(function () {
_70
notifier.sendNotification();
_70
res.redirect('/properties');
_70
})
_70
.catch(function(err) {
_70
console.log(err);
_70
});
_70
});
_70
_70
// POST: /reservations/handle
_70
router.post('/handle', twilio.webhook({validate: false}), function (req, res) {
_70
var from = req.body.From;
_70
var smsRequest = req.body.Body;
_70
_70
var smsResponse;
_70
_70
User.findOne({phoneNumber: from})
_70
.then(function (host) {
_70
return Reservation.findOne({status: 'pending'});
_70
})
_70
.then(function (reservation) {
_70
if (reservation === null) {
_70
throw 'No pending reservations';
_70
}
_70
reservation.status = smsRequest.toLowerCase() === "accept" ? "confirmed" : "rejected";
_70
return reservation.save();
_70
})
_70
.then(function (reservation) {
_70
var message = "You have successfully " + reservation.status + " the reservation";
_70
respond(res, message);
_70
})
_70
.catch(function (err) {
_70
var message = "Sorry, it looks like you do not have any reservations to respond to";
_70
respond(res, message);
_70
});
_70
});
_70
_70
var respond = function(res, message) {
_70
var messagingResponse = new MessagingResponse();
_70
messagingResponse.message({}, message);
_70
_70
res.type('text/xml');
_70
res.send(messagingResponse.toString());
_70
}
_70
_70
module.exports = router;

In the last step, we'll use Twilio's TwiML and instruct Twilio to send SMS messages to the guest.


After updating the reservation status, we must notify the host that he/she has successfully confirmed or rejected the reservation. If no reservation is found, we send an error message instead.

If a reservation is confirmed or rejected we send an additional SMS to the guest to pass along the news.

We use the verb Message from TwiML to instruct Twilio's server that it should send SMS messages.

Build the TwiML Response

build-the-twiml-response page anchor

routes/reservations.js


_70
var MessagingResponse = require('twilio').twiml.MessagingResponse;
_70
var twilio = require('twilio');
_70
var express = require('express');
_70
var router = express.Router();
_70
var Property = require('../models/property');
_70
var Reservation = require('../models/reservation');
_70
var User = require('../models/user');
_70
var notifier = require('../lib/notifier');
_70
_70
// POST: /reservations
_70
router.post('/', function (req, res) {
_70
var propertyId = req.body.propertyId;
_70
var user = req.user;
_70
_70
Property.findOne({ _id: propertyId })
_70
.then(function (property) {
_70
var reservation = new Reservation({
_70
message: req.body.message,
_70
property: propertyId,
_70
guest: user.id
_70
});
_70
_70
return reservation.save();
_70
})
_70
.then(function () {
_70
notifier.sendNotification();
_70
res.redirect('/properties');
_70
})
_70
.catch(function(err) {
_70
console.log(err);
_70
});
_70
});
_70
_70
// POST: /reservations/handle
_70
router.post('/handle', twilio.webhook({validate: false}), function (req, res) {
_70
var from = req.body.From;
_70
var smsRequest = req.body.Body;
_70
_70
var smsResponse;
_70
_70
User.findOne({phoneNumber: from})
_70
.then(function (host) {
_70
return Reservation.findOne({status: 'pending'});
_70
})
_70
.then(function (reservation) {
_70
if (reservation === null) {
_70
throw 'No pending reservations';
_70
}
_70
reservation.status = smsRequest.toLowerCase() === "accept" ? "confirmed" : "rejected";
_70
return reservation.save();
_70
})
_70
.then(function (reservation) {
_70
var message = "You have successfully " + reservation.status + " the reservation";
_70
respond(res, message);
_70
})
_70
.catch(function (err) {
_70
var message = "Sorry, it looks like you do not have any reservations to respond to";
_70
respond(res, message);
_70
});
_70
});
_70
_70
var respond = function(res, message) {
_70
var messagingResponse = new MessagingResponse();
_70
messagingResponse.message({}, message);
_70
_70
res.type('text/xml');
_70
res.send(messagingResponse.toString());
_70
}
_70
_70
module.exports = router;

And that's a wrap! Next let's take a look at other features you might enjoy in your application.


Node.js goes great with a helping of Twilio... let us prove it:

IVR: Phone Tree

Easily route callers to the right people and information with an IVR (interactive voice response) system.

Automated Survey

Instantly collect structured data from your users with a survey conducted over a voice call or SMS text messages.

Did this help?

did-this-help page anchor

Thanks for checking this tutorial out! Tweet to us @twilio(link takes you to an external page) with what you're building!


Rate this page: