Building Blocks for a Modern and Conversational IVR

August 04, 2019
Written by

modern_ivr.png

Many IVRs expect too much from customers - they lack customizability and require your customers to patiently learn how to use the IVR. What if you could replace this with a natural conversational IVR? What if you could add security and personalized customer data and deploy this using multiple channels? 

Well, you can! In this two-part blog post, we’re going to use Twilio APIs as “building blocks” to build an IVR for our pseudo-business, Signal Hardware. Here is the stack:

IVR_stack.png

Layer 1 - Studio and Autopilot

In the first Layer of our IVR we’re using Twilio Studio and Autopilot to give us structure, flexibility and control of our workflow.

Studio is a virtual application builder. It allows you to rapidly create communication flows using pre-built widgets. Autopilot is our Natural Language Processing and Machine Learning platform. Where Studio creates our structure inside of a UI, Autopilot allows us to have a conversation where the IVR needs it. 

In this IVR, we’re going to switch between Studio and Autopilot. 

Layer 2 - Functions and Channels

Twilio Functions are low latency, serverless, standalone functions that you can execute within a pre-configured environment that includes things like helper libraries and API keys. In our IVR, Functions act as the gateway to add personalization and send OTP and email.

SMS and Voice are at the second layer of the stack because we’ll be using both these channels concurrently in Studio and Autopilot to communicate with our customers.

Layer 3 - Email, Sync, Pay and Authy

The building blocks at layer 3 allow us to add our own “flavor” to the IVR. We’re customizing Signal Hardware’s IVR to send an email (Sendgrid), provide 2FA (Authy), make a payment (Pay) and we’re using Sync as our on-the-fly database for customer information - this will make sense in a second.

Let’s Build!

Now that we know our ingredients, it’s time to build our IVR!

We’re going to be covering a lot of ground in a short amount of time. This is not for the faint hearted. But if you stick with me, you’ll be a Twilio expert and have a super cool IVR to play around with by the end of this!

Step 1 - Configuration

We’re going to be using a number of Functions in this IVR, so we’ll need to configure our environment variables and dependencies. Let’s do that first.

IVR_Post_envvariables.png
  • Set up your Sendgrid API Key here. This will be the value for SENDGRID_API_KEY.
  • Set up your Authy API Key here - you will need to create a new Application and then copy the “Production API Key”, which will be the value for AUTHY_API_KEY. Feel free to change the Settings within your Authy Application, but make sure that “Authentication via SMS” is Enabled.
  • Create a Sync Service here. The SID will be the value for SYNC_SERVICE_SID.
  • To get the value for SYNC_MAP, you will need to create a new Map, you can do this here. I like to create a name for my Map, so that would look like this -
client.sync.services(‘ISXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX')
           .syncMaps
           .create({uniqueName: "YOUR_SYNC_MAP_NAME" })
           .then(document => console.log(document.sid));
  • Under Dependencies, make sure that you have authy-client, sendgrid/mail and moment imported. If you don’t, you can copy my list of dependencies (versions up-to-date as of 08/02/19) 
IVR_post_Dependencies.png

Step 2 - Create an “on-the-fly” Customer Database 

We are going to use SMS and our Sync Map to create a dynamic database that we can use in our IVR straight away. To begin, we’ll need to make sure we have at least one phone number in our Twilio console. 

Now it’s time to create our first Function

  1. Name your function Create_Sync_Item
  2. Add a path to your function https://your-runtime.twil.io/create_sync_item
  3. Add this code and then press Save
exports.handler = function(context, event, callback) {
    let sync = Runtime.getSync({serviceName: context.SYNC_SERVICE_SID}); // hello Sync
    let splitWords = event.Body.split(' '); // split into array
    // organize data
    let phone = event.From;
    let firstName = splitWords[0];
    let lastName = splitWords[1];
    let DOB = splitWords[2];
    let email = splitWords[3];
    let data = {
        firstName: firstName,
        lastName: lastName,
        DOB: DOB,
        email: email
    };
    console.log(data);
    // push into SyncMap Item
    sync.maps(context.SYNC_MAP).syncMapItems.create({
        key: phone,
        data: data
    }).then(response => {
        console.log(response);
        callback(null, response);
    }).catch(error => {
        console.error('Uh oh, something went wrong!:');
        console.error(error.message);
    });
    // send reply message
    // var client = context.getTwilioClient();
    // client.messages.create({to: event.From, from: context.PHONE, body: "Thanks for messaging, " + firstName + " ! Add some custom text here!"});
};

I have commented out an SMS auto-reply, but you can uncomment this and add your phone number to PHONE in your env variables if you would like to send an auto-reply message. If this doesn’t make sense to you now, come back to this at the end of this section.

Let’s create a second function, which will read the object we have just created from our key, in this case that's a phone number.

  1. Name your function Get_Sync_Item
  2. Add a path to your function https://your-runtime.twil.io/get_sync_item
  3. Add this code and then press Save
exports.handler = function(context, event, callback) {
    const moment = require('moment');
    let todayDate = moment().format("MM/DD"); 
    let sync = Runtime.getSync({serviceName: context.SYNC_SERVICE_SID});
    let phone = event.phone;
    
    sync.maps(context.SYNC_MAP)
        .syncMapItems(phone)
        .fetch()
        .then(response => {
            let DOB = moment(response.data.DOB);
            let firstName = response.data.firstName;
            let lastName = response.data.lastName;
            let email = response.data.email;
            let tillBday = DOB.diff(todayDate, 'days');
            console.log("How many days until Bday?", tillBday);
            let date = moment(DOB).format("Do MMMM");
            console.log("new DOB Format:", date);
            
        let data = {
            firstName: firstName,
            lastName: lastName,
            email: email,
            days: tillBday,
            date: date
        }
            console.log(data);
            callback(null, data)
        })
        .catch(error => {
            console.error('Uh oh, something went wrong!:');
            console.error(error.message);
        });
};

Lastly, navigate to your Phone Number and Configure the Messaging Webhook to your Create_Sync_Item function, so it will look like this - 

IVR_post_configurenumberwithwebhook.png

Sweet! We have just created a database that we can dynamically update, along with a Function to read that data which we’ll use later in our IVR. Now let’s test it out. Message that phone number with 4 words, in the specific order and format (including date format of MM/DD) shown below 

enter_details.png

If that was successful, you can navigate to
https://sync.twilio.com/v1/Services/ISXXXXXXXXXXXX/Maps/MPXXXXXXXXXXX/Items

Where:
ISXXXXXXXXXXXX = Your Service SID
MPXXXXXXXXXXX = Your Sync Map

And you should see something like

{
"map_sid": "MPXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"url": "https://sync.twilio.com/v1/Services/ISXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Maps/MPXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Items/+14151234567",
"created_by": "system",
"account_sid": "ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"date_expires": null,
"date_updated": "2019-07-29T18:36:02Z",
"key": "+14151234567",
"date_created": "2019-07-29T18:36:02Z",
"service_sid": "ISXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"data": {
"DOB": "09/26",
"lastName": "Lowes",
"email": "djlowes@gmail.com",
"firstName": "David"
},
"revision": "2d"
}

Step 3 - Personalise the Entry into our IVR

Navigate to Twilio Studio and create a new Studio Flow. If you’re not familiar with Studio yet, here are some tutorials.

  • To start your flow, as soon as a phone call comes in, Trigger your Get_Sync_Item Function, and make sure you add Function Parameters inside the widget, exactly as shown below (phone: trigger.call.Caller)

IVR_Post_Get_Sync_in_studio.png
  • Drag out a Gather input on call widget, select "Say a Message" and type out some text to Say. To personalize the greeting, we’re going to use some information that we have gathered from our GET_SYNC_DATA widget! If your function and widget name are the same as mine, you will do this by using {{widgets.GET_SYNC_DATA.parsed.firstName}} in the body of your message to <Say> the callers first name.

IVR_post_saywidget.png
  • We’re about to add some security to this IVR, so make sure in your <Say> message, you are asking the user to initiate the security check. In my example, we are just going to use DTMF in a simple flow, so let’s ask the customer to press 1. My message reads "Hey {{widgets.GET_SYNC_DATA.parsed.firstName}}, thanks for calling! Signal Hardware Store protects our customer accounts and we make sure your information and orders are secure. Press 1 so we can send you a security code."
  • You might notice that our Gather input on call widget will execute on both success and fail of our Function. This is by design to maximize space for illustration. It is recommended that you include a separate widget to handle our function failing.

Step 4 - Add Security via Authy 2FA

Protecting customer data is important, we’re going to add a layer of security right into our IVR.

  • Let’s create a new Function named Send_Authy. Choose a path name, perhaps "send_authy' is easiest to use. 
  • Add this code and press Save -
const Client = require('authy-client').Client;

exports.handler = function(context, event, callback) {
    const verify = new Client({ key: context.AUTHY_API_KEY });
    let phone = event.phone
	verify.startPhoneVerification({ 
      countryCode: 'US', 
      locale: 'en', 
      phone: phone, 
      via: 'sms'
    },  function(err, res) {
        console.log(res);
    if (err) {throw err}
    callback(null, {success: true});
  });
};
  1. Let’s create another Function! Name this one Verify_Code and choose a relevant path name.
  2. Add this code and press Save -
const Client = require('authy-client').Client;

exports.handler = function(context, event, callback) {
    const verify = new Client({ key: context.AUTHY_API_KEY });
    let code = event.code
    let phone = event.phone
    console.log(code);
	verify.verifyPhone({ 
      countryCode: 'US', 
      phone: phone, 
      token: code
    }, function(err, res) {
        console.log(res);
    if (err) {throw err}
    callback(null, res);
  });
};
  • Time to navigate back to our Studio flow and add some more widgets! Drag out a Split Based On… widget and connect to our Gather on input widget, mine is called WELCOME_SAY

IVR_post_split widget.png
  • Drag out another Function widget, connect it to our Send_Authy Function and add the Function Parameters: phone : {{trigger.call.Caller}} (My function name below says “Send_Authy_SIGNAL2019”, yours should just say “Send_Authy”)

IVR_post_sendauthy.png
  • Navigate back to our SPLIT_WIDGET and add a Transition, so that If Value = 1, then we transition to SEND_AUTHY

IVR_post_splitwidgettransition.png
  • Drag out another Gather input on call widget, I’m calling mine GATHER_CODE. Say something like “We just sent you a message! Can you please type in those 4 digits to your phone's keypad!”. If you have configured your Authy service for 4 digit codes only, then navigate to the bottom this widget and where it says STOP GATHERING AFTER, and enter 4 digits.

IVR_post_Gathercode.png
  • Again, we are not handling failure on this function, but please add this workflow yourself. 
  • Drag out another Function, I’m calling mine VERIFY_TOKEN, and connect it to your Verify_Code function (again, mine says Verify_Code_SIGNAL2019, but if you followed my earlier instructions, yours will just say Verify_Code)
  • Add two Function Parameters
    code : {{widgets.GATHER_CODE.Digits}}
    phone : {{trigger.call.Caller}}

IVR_post_verifytoken.png

Okay, that was a lot. But now we have 2FA verification right inside our IVR! Woohoo!

Step 4 - Get Creative

There are many ways to get creative inside of an IVR. In our example, we’re going to send our customers a $100 gift card to thank them for being a loyal customer at our hardware store, which is valid on their birthday! But not by post... We’re going to send them this gift card via email and in real-time.

  • Drag out a Say/Play widget, mine is called SAY_VERIFIED_OFFER. Type this into the <Say> box - "Thanks {{widgets.GET_SYNC_DATA.parsed.firstName}} {{widgets.GET_SYNC_DATA.parsed.lastName}}  you've been verified! I'm looking at our customer records and I can see that your birthday is in {{widgets.GET_SYNC_DATA.parsed.days}} days! We'd like to say thanks for being a loyal customer, and will be sending you out a $100 gift card on the  {{widgets.GET_SYNC_DATA.parsed.date}} as an early Birthday present from us."
  • Feel free to edit the language to your liking

IVR_post_say_verified.png
  • Head back to Functions and create a new Function called Send_Email (path can be the same) and add this code (I’m giving you both API v2 and v3 code for new and old Sendgrid folk - you choose which you would like to use)

Here's the code for Web API V3 via the helper library (which you should already have as a dependency)

exports.handler = function(context, event, callback) {
	const sgMail = require('@sendgrid/mail');
    sgMail.setApiKey(context.SENDGRID_API_KEY);

        const msg = {
            to: event.email,
            from: 'your_email_address@gmail.com',
            subject: '$100 Gift Voucher!',
            text: 'Hey ' + event.firstName + " Here’s a $100 voucher to Signal Hardware for you”,
            html: '<strong>woohoo!</strong>',
        };
        
    sgMail.send(msg);
    console.log(msg)
	callback(null);
};

Here's the code if using Web API v2, where you will need to add your Sendgrid API User name also. 

Only use one of these two code samples!

exports.handler = function(context, event, callback) {
var request = require("request");
console.log(event.email, event.firstName, event.team);
var options = { method: 'POST',
  url: 'https://api.sendgrid.com/api/mail.send.json',
  qs: 
  { api_user: 'your_v2_username',
     api_key: context.SENDGRID_API_KEY,
     to: event.email,
     subject: '$100 gift voucher!',
     text: ‘Hey ' + event.firstName + " Here’s a $100 voucher to Signal Hardware for you”,
     from: 'your_email_address@gmail.com' }
 };
request(options, function (error, response, body) {
  if (error) throw new Error(error);
  callback(null)
  console.log(body);
});
};

  • You will need to add your email address to the from parameter (replace your_email_address@gmail.com) and if you are using V2 of the API, also add your `api_user` name. Feel free to customize the subject and text to your liking. 
  • Navigate back to your Studio flow and add the Function you just created with these Function Parameters -
    • firstName: {{widgets.GET_SYNC_DATA.parsed.firstName}}
    • email: {{widgets.GET_SYNC_DATA.parsed.email}}

IVR_Post_sendemail.png

 

Let’s test what we’ve done so far. Navigate to the phone number you are using and in Configure, add your Studio Flow to "A Call Comes in", which will look something like this - 

IVR_post_voicewebhook.png

Hit save and call the number! 

Step 5 - Have a Conversation

By now we have already built a fairly sophisticated and robust IVR that is authenticating the customer using 2FA, personalizing their experience using a data dip into our system of records and sending them a gift to their preferred communication channel because we have (presumably) computed the value they are as a customer from their spending habits. 

But we’re just started. Let’s take a break. Next up we're getting into the real fun stuff - training our IVR to have a conversation using Autopilot!



If you find an error or have questions, please reach out @djlowes!