Build a real-time SMS call center with Data McFly and Twilio

code, Twilio

This is the first of a planned series of posts demonstrating how to build a call center-type system with Twilio and Data McFly

Do you want to know one of the beautiful things about Data McFly? It integrates really easily with other services.

In this article, we are going to walk through using Data McFly and Twilio together to build a real-time SMS call center.

This could be used as a customer help desk where customers send a text message for help and an agent sends a reply back from their web browser.

The actual phone work will be handled by Twilio, and Data McFly will store the data and display the chats in real-time. We'll use node.js to send and receive the text messages and an HTML frontend to handle the actual chatting.

The setup

We’ll be using a few tools to build this app. You’ll want to have these set up before you continue on:

  • Twilio: To send and receive SMS messages. Don’t have a Twilio account? Sign up for Free.
  • Data McFly: A real-time database API. We’ll be using it to store our incoming and outgoing messages.
  • Node.js: A platform built on Chrome’s JavaScript runtime for easily building fast, scalable network applications.

If you haven't already, Sign up for a free Data McFly account now, then create a new app. You'll use your app for your call center.

Getting Started

We first need to set up our Node.js app.

Besides the Twilio and Data McFly modules, we’ll be using the Express framework to set up our node web server to receive the POST request from Twilio so we’ll need to install the express package. We’ll also be using the body-parser module so we are going to install that as well.

Let's create our package.json file:

{
    "name": "sms-contact-center",
    "version": "0.0.1",
    "description": "SMS Contact Center powered by Data McFly, Twilio and Node.js",
    "main": "app.js",
    "repository": "https://github.com/DataMcFly/sms-contact",
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
    },
    "keywords": [
        "twilio",
        "data mcfly",
        "sms"
    ],
    "author": "Roger Stringer",
    "license": "MIT",
    "dependencies": {
        "twilio": "~1.6.0",
        "ejs": "~0.8.5",
        "express": "~3.4.8",
        "datamcfly": "~1.4.4",
        "node-buzz": "~1.1.0",
        "moment": "~2.5.1",
        "less-middleware": "~0.2.1-beta",
        "body-parser" : "~1.4.2",
        "method-override" : "~2.0.2"   
    },
    "engines": {
        "node": "0.10.26"
    }
}

Save this file, and from the terminal run the following command:

npm install

This will create a node_modules folder containing all of the modules we want to use.

Let's set up our folder structure, create a folder called views, this is where we will keep our frontend.

Now, create a folder called public, this will host our static files, inside that folder, create a css folder and a js folder, we'll come back to these later.

At the beginning of our app.js file we’ll need to require express and initialize it into a variable called app. We’re also going to use the bodyParser middleware to make it easy to use the data we’ll be getting in our POST request.

Create a new file called app.js and require the twilio, express and datamcfly packages:

var express = require('express');
var bodyParser = require('body-parser');
var methodOverride = require('method-override');
var twilio = require('twilio');
var  path = require('path');

var app = express();
app.set('views', path.join(process.cwd(), 'views'));
app.set('view engine', 'ejs');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({    extended: true  }));
app.use(express.static(__dirname + '/public'));

var port = process.env.PORT || 8080; // set our port

var client = twilio('ACCOUNTSID', 'AUTHTOKEN');
var twilio_number = 'YOUR-NUMBER';

var api_key = "YOUR-API-KEY";
var appname = "YOUR-DATAMCFLY-APP";
var collection = "smscontact";
var messagesRef = require('datamcfly').init(appname, collection, api_key);

// backend routes =========================

Replace ACCOUNTSID, AUTHTOKEN and YOUR-NUMBER with your Twilio credentials, and a phone number in your Twilio account that you'll be using.

Then, replace YOUR-API-KEY, YOUR-DATAMCFLY-APP and smscontact with your Data McFly API Key, and create a collection to use. If you haven't already created a collection, one will be automatically created for you when you first save data, so you can leave the collection name set to smscontact if you want.

Data McFly uses collections to organize data inside apps, so one app could have several collections. If you're familiar with relational databases, this is the equivalent of a table.

This is the start of our app, next we'll tell it what to do when new texts come in and when an agent replies to a text.

Sending and Receiving Texts

Twilio uses webhooks to let your server know when an incoming message or phone call comes into our app. We need to set up an endpoint that we can tell Twilio to use for the messaging webhook.

We’re going to add a route for /message that responds with some TwiML. TwiML is a basic set of instructions you can use to tell Twilio what to do when you receive an incoming call or SMS message. Our code will look like this:

app.post('/message', function (request, response) {
    var d = new Date();
    var date = d.toLocaleString();

    messagesRef.push({
        sid: request.param('MessageSid'),
        type:'text',
        direction: "inbound",
        tstamp: date,
        fromNumber:request.param('From'),
        textMessage:request.param('Body'),
        fromCity:request.param('FromCity'),
        fromState:request.param('FromState'),
        fromCountry:request.param('FromCountry')
    });

    var resp = new twilio.TwimlResponse();
    resp.message('Thanks for the message, an agent will get back to you shortly.');
    response.writeHead(200, {
        'Content-Type':'text/xml'
    });
    response.end(resp.toString());
});

This will listen for any incoming sms messages and store them inside your Data McFly app.

Once a message has been received, we use the Twilio node library to initialize a new TwimlResponse. We then use the Message verb to set what we want to respond to the message with. In this case we’ll just say “Thanks for the message, an agent will get back to you shortly.”. Then we’ll set the content-type of our response to text/xml and send the string representation of the TwimlResponse we built.

Whenever a customer sends a message to the phone number we've set up, it will send them the response and store the message inside Data McFly. If an agent is watching the client, then they will see the message appear instantly, and can send a reply back.

Now, let's add a route called /reply this is what we will call via AJAX when our agents want to reply to a message:

app.post('/reply', function (request, response) {
    var d = new Date();
    var date = d.toLocaleString();

    messagesRef.push({
        type:'text',
        direction: "outbound",
        tstamp: date,
        fromNumber:request.param('From'),
        textMessage:request.param('Body'),
        fromCity:'',
        fromState:'',
        fromCountry:''
    });

    client.sendMessage( {
        to:request.param('To'), 
        from:twilio_number,
        body:request.param('Body')
    }, function( err, data ) {
        console.log( data.body );
    });
});

This will store the reply in our Data McFly app as an outbound reply, then send the message to the customer.

Finally, let’s set our server to listen on port 8080, and tell it what to do when we view it from a brower:

// frontend routes =========================

// route to handle all angular requests
app.get('*', function(req, res) {
    res.render('home', {
        apikey:api_key,
        appname:appname,
        collection:collection
    });
});


var server = app.listen(port, function() {
    console.log('Listening on port %d', server.address().port);
});

Now that we've built our server, we need to tell twilio to use this messaging url as a our Message Request URL:

Send an SMS message to your Twilio number and you should get a response back. If you don’t, take a look at the Twilio App Monitor to help determine what went wrong.

This is the backend portion of our call center, it listens for incoming text messages, stores them in our Data McFly app, and then sends replies when an agent replies.

Now, we need to build our agent system, where an agent can watch incoming messages and reply to them.

We'll build that now.

The Client

We've got our Node.js app listening for messages to send and receive, now let's set up our client which is what agents will see from their web browser.

When a message comes in, we'll display a chat box showing the message, then send a reply.

First, let's create our view, in the /views folder, create a file called home.ejs:

<!doctype html>
<html>
<head>
    <link href='http://fonts.googleapis.com/css?family=Lato:400,300italic,400italic&subset=latin,latin-ext' rel='stylesheet' type='text/css'>
    <link rel="stylesheet" type="text/css" href="http://angular-ui.github.com/ng-grid/css/ng-grid.css" />
    <link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
    <link rel="stylesheet" type="text/css" href="/css/style.css">

    ](http://twilio.com
    ](https://www.twilio.com/try-twilio
    ](https://www.datamcfly.com/

    <title>SMS Contact Center, powered by Data McFly and Twilio</title>
</head>
<body>
    <div class='container'>
        <br />
        <div class="well">
            <p class='homefont'>Welcome to your SMS Contact Center</p>

            <p class='homefont'>This call center is the start of a modern day call center.</p>

            <p class='homefont'>Take a look around and give us a try.</p>
        </div>
        <hr />
        <h3>Incoming messages</h3>
        <div id="templateContainer"></div>
    </div>
    ](http://nodejs.org/
</body>
</html>

This file will act as our HTML file, and we are using EJS so we can pass our Data McFly settings and not have to configure it in multiple places. EJS is handy for adding template functionality to your Node.js apps.

Now, let's create our style sheet. In the /public/css folder we created earlier, create new file called style.css:

body{font-size:12pt;font-family:helvetica}
.chatWindow{float:left;margin:20px;border:1px solid #000;width:300px;background:#e5e5e5;border-radius:5px}
.chatName{margin-bottom:10px;background:#666;color:#fff;padding:4px}
.messages{padding:4px}
.message_outbound{color:blue;text-align:right}
.tstamp{font-size:9px;padding:2px;margin-bottom:10px;border-bottom:1px dotted #666;color:#666}
.error{color:red;text-align:center}
.messageForm textarea{float:left;width:220px;margin:5px}

Lastly, we want to set up the brains of our app. We've saved the biggest file for last.

In the /public/js/ folder, create a new file called chat.js:

var chatManager = function(datamcflyRef) {
    this.datamcflyRef = datamcflyRef;
};

chatManager.prototype = {
    chats: [], 
    getChat: function(fromNumber) {
        var foundChat = null;
        for (c = 0; c < this.chats.length; c++) {
            if (this.chats[c].from == fromNumber) {
                foundChat = this.chats[c];
            }
        }

        if (foundChat == null) {
            foundChat = new chat( this.datamcflyRef );
            foundChat.init(fromNumber);
            foundChat.displayTemplate();
            this.chats.push(foundChat);
        }
        return foundChat;
    },
    updateChats: function() {
        var _this = this;
        this.datamcflyRef.once('value', function (data) {
            data.forEach( function(message){                    
                var row = message.value();
                _this.getChat( row.fromNumber ).addMessage(
                    row.textMessage,
                    row.tstamp,
                    row.direction
                );
            });
        });
        this.datamcflyRef.on('added', function (data) {
            var row = data.value();
            _this.getChat( row.fromNumber ).addMessage(
                row.textMessage,
                row.tstamp,
                row.direction
            );
        });
    }
};

var chat = function(datamcflyRef) {
    this.datamcflyRef = datamcflyRef;
};
chat.prototype = {
    init: function(name) {
        this.from = name;
        this.chatName = 'chat-' + this.from;
        this.buttonName = 'submit-' + this.from;
        this.textName = 'reply-' + this.from;
    },
    replyMessage: function(message) {
        var _this = this;
        $.ajax({
            type: "POST",
            url: "/reply",
            data: {
                'To': this.from,
                'Body': message,
                'From': this.from
            },
            dataType: "json",
            success: function(data) {
                // your message was sent
            }
        });
    },
    displayTemplate: function() {
        var content = '<div class="chatName">Chat with ' + this.from + '</div> \
        <div class="messages" id="' + this.chatName + '"></div> \
        <div class="messageForm"><textarea id="' + this.textName + '"></textarea><button id="' + this.buttonName + '">Reply</button></div> \
      </div>';

        content = '<div class="chatWindow" id="' + this.tmplName + '">' + content + '</div>';

        $('#templateContainer').append(content);
        var _this = this;

        $('#' + this.buttonName).click(function() {
            _this.replyMessage($('#' + _this.textName).val());
            $('#' + _this.textName).val('');
        });
    },
    addMessage: function(message, tstamp, direction) {
        $('#' + this.chatName).append("<div class='message_" + direction + "'>" + message + "<div class='tstamp'>" + tstamp + "</div></div>");
    }
};

Our chatManager class is set up so that when it loads, it first grabs a list of saved text messages using the value event trigger, and displays them by the phone number they were sent from.

We treat all messages to and from the same number as one session, so for each chat session, we would see a box displaying the messages between the agent and the customer, and a textbox to use to send new messages.

We then listen for any new new messages to come in using the added even trigger, and we then display them inside the proper chat box.

The chat class tells our app how display the chat boxes, and how to handle sending new replies.

In this case, when a message is sent by an agent, we post it to our backend /reply route, where it is saved to our Data McFly app and then sent to the customer as a text message.

We're also storing the direction a message came from, either inbound or outbound, this way we can style each message to appear similar to when you view a chat log on your phone. The customer's texts will appear on the left side, and the agent's replies will appear on the right side.

Now let’s fire up our app:

node app.js

We've told our app to run on port 8080, so if you go to your web browser and type in http://localhost:8080/ you should see your call center.

Just an aside, if you’re running this locally, you’ll want to make sure you’ve got ngrok running before you go to the next step. If you haven’t used ngrok before, Kevin Whinnery over at Twilio has put together a great tutorial to help you get started.

And Cut!

We did it! Now that you’ve built a simple SMS call center app it’s your chance to create something with it.

Your Mission Should You Choose To Accept

Take this project and roll with it.

Some ideas are you could actually completely remove the /reply ajax call, and instead create an outbound text queue that would store the message, then add an added listener to the outbound collection that would both send the reply to the customer, and add it into the message collection so it would appear in the chat window.

This would eliminate the need for that /reply AJAX call, and also add some queue support in cases of sending out multiple replies to several customers at once.