Building the IT Crowd Answering Machine with Twilio Functions and JavaScript

July 04, 2017
Written by

Picture of Roy from IT Crowd answering the phone

One of my favorite things from IT Crowd is their “Hello IT” machine. It’s the perfect solution for lazy people who are tired of answering the same IT support questions again and again. With Twilio Functions and the new Speech Recognition we can build our own version of this machine with just a few lines of code.

If you are not familiar with the answering machine from IT Crowd make sure to check out this video of it in action.

 

You can also check out the final result by calling one of these numbers:

  • 🇬🇧 +44 20 3389 5853
  • 🇺🇸 (415) 702-4376

Before we get going make sure you have a Twilio account. Sign up for free.

Now let’s get coding!

giphy.gif

“Hello IT”

The first thing we need to do is create a new Twilio Function that will handle all of our requests. Twilio Functions allows you to host Node.js code directly within your Twilio Console. No need for you to spin up your own server.

To get started:

  • Go into the Runtime section of your Twilio Console
  • click on Functions
  • click on the red “+” and choose the blank template

Your screen should look like this:

create-function-screenshot.png

The code that you see is currently generating and returning an empty TwiML response. Additionally we have the option to name our Function and give it a path. Each account is given a unique base URL that looks like fancy-badger.twil.io, the path is used to differentiate between the Functions you create for your account. I’ll name mine “IT Support Hotline” and give it the path “/it-support”. You can give yours other values but make sure to replace it accordingly throughout the post.

Choose for Event in the Configuration section “Incoming Voice Calls” as this is the event we will use this Function for. Let’s start by greeting the caller with “Hello IT”. We’ll choose a British accent since we don’t have an Irish one. So your hotline will sound a bit more like Moss answering than Roy. Change the commented line to the following code:


exports.handler = function(context, event, callback) {
  let twiml = new Twilio.twiml.VoiceResponse();
  twiml.say({ voice: 'man', language: 'en-gb' }, 'Hello I.T.');
  callback(null, twiml);
};

Make sure to save your Function. You can test if it works by going in your browser to the URL that you configured for this Function. You should see the following code returned:

<Response>
  <Say voice="man" language="en-gb">Hello I.T.</Say>
</Response>

Let’s configure this to a phone number to be able to fully test it. Go to the Phone Numbers section and either click on one of your existing numbers that you would like to configure or purchase a new phone number. Once you are on the configuration screen, choose in the “Voice & Fax” section for “A call comes in” the values “Function” and the name of your Twilio Function.

configure-function-screenshot.png

Save the changes and give your phone number a call. You should be greeted by a male voice with a British accent saying “Hello IT”.

answer-phone-screenshot.gif

“Have you tried turning it off and on again?”

Now let’s add some Speech Recognition to the mix by adding the following user flow:

  1. User is greeted with “Hello IT”
  2. System waits for user to finish what they are saying
  3. System ignores what has been said and asks “Have you tried turning it off and on again?”
  4. If the user mentions “thank” somewhere we say “You’re welcome, mate!”.
  5. If they don’t say it, we ask the same question again (because most likely they haven’t).

In order to use speech recognition we  use the Gather verb in our TwiML and set input to “speech”. We will also set the action property to our URL, so that when there is a result Twilio will call this function again, and append a query parameter that helps us later to add more call flows. The first time the Function is called there will be no results from the speech recognition with Gather, so we return the greeting. On subsequent calls to the Function we will get a SpeechResult parameter, accessible as event.SpeechResult, which contains the text the API retrieved from the speech.

Change your Function code appropriately:


exports.handler = function(context, event, callback) {
  let twiml = new Twilio.twiml.VoiceResponse();
  if (!event.SpeechResult) {
    twiml
      .gather({ input: 'speech', action: '/it-support?next=onoff' })
      .say({ voice: 'man', language: 'en-gb' }, 'Hello I.T.');
  } else if (event.SpeechResult.toLowerCase().indexOf('thank') !== -1) {
    twiml.say({ voice: 'man', language: 'en-gb' }, "You're welcome, mate.");
    twiml.hangup();
  } else {
    askOnOff(twiml);
  }
  callback(null, twiml);
};

function askOnOff(twiml) {
  twiml
    .gather({ input: 'speech', action: '/it-support' })
    .say(
      { voice: 'man', language: 'en-gb' },
      'Did you try turning it off and on again?'
    );
}

Save the changes in your Function and give your phone number another call. It should greet you with “Hello IT” and once you said something ask you again and again if you tried to turn it off and on again until you say “Thank you” or “Thanks”.

answering-phone.gif

“Is it definitely plugged in?”

One important question is still open though. “Is it definitely plugged in?”. Let’s add that in case the person answers “yes” to the previous question. Add the following lines to your code:


exports.handler = function(context, event, callback) {
  let twiml = new Twilio.twiml.VoiceResponse();
  if (!event.SpeechResult) {
    twiml
      .gather({ input: 'speech', action: '/it-support?next=onoff' })
      .say({ voice: 'man', language: 'en-gb' }, 'Hello I.T.');
  } else if (event.SpeechResult.toLowerCase().indexOf('thank') !== -1) {
    twiml.say({ voice: 'man', language: 'en-gb' }, "You're welcome, mate.");
    twiml.hangup();
  } else if (event.next === 'plugged') {
    if (event.SpeechResult.toLowerCase().indexOf('yes') !== -1) {
      askPluggedIn(twiml);
    } else {
      askOnOff(twiml);
    }
  } else {
    askOnOff(twiml);
  }
  callback(null, twiml);
};

function askOnOff(twiml) {
  twiml
    .gather({ input: 'speech', action: '/it-support?next=plugged' })
    .say(
      { voice: 'man', language: 'en-gb' },
      'Did you try turning it off and on again?'
    );
}

function askPluggedIn(twiml) {
  twiml
    .gather({ input: 'speech', action: '/it-support' })
    .say({ voice: 'man', language: 'en-gb' }, 'Is it definitely plugged in?');
}

Don’t forget to save and you should be good to go to test your support hotline again.

Doing the work for them

We got the basic answering machine done but why don’t we leverage the fact that we can do so much more with Twilio Programmable Voice to try to help the caller. We could do something very sophisticated here but for now let’s actually ask them again for their problem and simply send them the link to the Stack Overflow search page with that question.
For this we need one more gather and one conditional where we send the SMS. Change your code to this:


exports.handler = function(context, event, callback) {
  let twiml = new Twilio.twiml.VoiceResponse();
  if (!event.SpeechResult) {
    twiml
      .gather({ input: 'speech', action: '/it-support?next=onoff' })
      .say({ voice: 'man', language: 'en-gb' }, 'Hello I.T.');
  } else if (event.SpeechResult.toLowerCase().indexOf('thank') !== -1) {
    twiml.say({ voice: 'man', language: 'en-gb' }, "You're welcome, mate.");
    twiml.hangup();
  } else if (event.next === 'plugged') {
    if (event.SpeechResult.toLowerCase().indexOf('yes') !== -1) {
      askPluggedIn(twiml);
    } else {
      askOnOff(twiml);
    }
  } else if (event.next === 'problem') {
    askForProblem(twiml);
  } else if (event.next === 'solution') {
    const linkToStackOverflow =
      'https://stackoverflow.com/search?q=' +
      require('querystring').escape(event.SpeechResult);
    twiml.say(
      { voice: 'man', language: 'en-gb' },
      "Alright I found a few solutions. I'll send you an SMS with them. Bye."
    );
    twiml.sms(linkToStackOverflow);
  } else {
    askOnOff(twiml);
  }
  callback(null, twiml);
};

function askOnOff(twiml) {
  twiml
    .gather({ input: 'speech', action: '/it-support?next=plugged' })
    .say(
      { voice: 'man', language: 'en-gb' },
      'Did you try turning it off and on again?'
    );
}

function askPluggedIn(twiml) {
  twiml
    .gather({ input: 'speech', action: '/it-support?next=problem' })
    .say({ voice: 'man', language: 'en-gb' }, 'Is it definitely plugged in?');
}

function askForProblem(twiml) {
  twiml
    .gather({ input: 'speech', action: '/it-support?next=solution' })
    .say(
      { voice: 'man', language: 'en-gb' },
      'Alright what was your exact problem again?'
    );
}

Give your phone number another ring and perform the following scenario to test it:

  • Call number
  • Reply with “Hello”
  • Say “Yes” to the question of turning it off and on again
  • Say “Yes I did” to the question of whether it’s plugged in
  • State a problem you want to search for
  • You should receive an SMS with the link to Stack Overflow 👍

That’s it! Don’t forget to save your Function and once it restarts your new IT support hotline is ready to be distributed to everyone who constantly bugs you for help.

What’s Next?

We’ve only used a few features from Programmable Voice so far and this is merely the beginning. There is a bunch of additional features you might want to look into:

  • Use the <Play /> verb to replace <Say /> with the original sound from IT Crowd or your own recordings that you can store in the assets section of the Runtime.
  • Use a smarter lookup for solutions by using the StackOverflow or Google API and sending the link to an actually useful result
  • Send the actual question to you via Email or Slack. Maybe file a ticket in your system?
  • Port the hotline to one of the other languages supported.

I would love to hear what kind of ideas you have or if you build something similar that makes your life easier. If you have any questions just send me a message: