Build an Interactive Voice Response (IVR) application with Africa's Talking, Flask, Docker and Heroku

Build an Interactive Voice Response (IVR) application with Africa's Talking, Flask, Docker and Heroku

ยท

0 min read

Introduction

This demo was written and shared with members of the Africa's Talking Freelance Developer sessions. If you are interested in joining us follow the links at the bottom of this article

Welcome back! This is a brand new series on creating amazing experiences with just phone calls. No fancy UI. No React. No Angular. Just a voice call. It may seem super simplistic but in reality, everyone makes calls. In light of this, is it possible to create apps that run on phone calls? Yes! That's what we'll be doing today.

We'll be using Flask, a python micro-framework, and deploying our app on Heroku. We don't have a database as of now but in a future series we'll be saving the data from user responses so stay tuned in for that.

What the app will do

We'll be building a simple guessing game that allows peeps to guess how old Africa's Talking is! This is the current user flow:

  1. A user will call our number and listen to the instructions
  2. They will dial in their response on the in-call dial pad
  3. The app will direct them on whether they have chosen the right answer or not

There are some challenges with the app, we aren't able to keep a user in a loop until they get the right answer but we'll cover this in the next series as we look at slightly mode advanced techniques.

Getting Started

To get your phone call set up, we are going to be using the Africa's Talking platform. Africa's Talking provides APIs that tap into telco infrastructure and makes it easier for developers across Africa to integrate services such as SMS, Voice, USSD, Payments and Airtime. There's also a brand new IoT platform that you could checkout too.

To get started, what you need is a programmable number. Africa's Talking provides these numbers at a cost however, for this tutorial, you can get a number for free. All you have to do is tell us what idea you would like to build on the Voice platform and we'll get you a number free of charge! Fill out this form to share your ideas.

How the Africa's Talking Voice API works

The platform provides a pretty simple approach to building our Interactive Voice Application through a REST API. Here's a simple diagram showing how it works:

Screen Shot 2019-11-06 at 1.14.59 PM.png

Project Setup

To see the API in action we need to build out our app. I'll explain where we'll be working with the API directly as we go along. Let's get this!

Make your project directory in your folder of choice with the following commands:

mkdir voice-python
cd voice-python

Next up, since we're writing python, we should get our python virtual environment set up. We'll do so with the following commands:

pip3 install virtualenv
virtualenv venv
source ./venv/bin/activate

Once done, we can start actually writing some code. Fire up your text editor of choice. I go for VS Code but Atom with Kite integration is really awesome for Python development.

Create a file called app.py in your project's root directory. This is where most of our code will live. In a real-world scenario we may have way more files and modules but one file makes it easier to write this article ๐Ÿ˜‚

Right now, all we need is the Flask micro-framework as well as the Flask API module to help us build our server. Flask is designed to make lightweight servers and serves perfectly for this and Flask API just gives us some cool functionality since we're basically building an API. Lets get it into our project:

pip3 install flask
pip3 install Flask-API

Having Flask and Flask API installed, we can actually write code. Add in the following lines of code:

import os #Allows us to work with local files
from flask import Flask, request #Flask itself
from flask_api import status

app = Flask(__name__)

@app.route("/", ["GET", "POST"])
def index():
    return "Hello, world"

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=os.environ.get("PORT"))

This is a basic Flask scaffold that sets up the first basic route and defines how the application will run including ports and the host. Since we'll be deploying to Docker, we need to set the host to 0.0.0.0 and get our port directly from the OS that will be running our application.

Now, we build out our actual app.

Building XML responses

Since, we're talking about Voice calls, remember that they are still OG tech. Calls have been around for over 100 years now therefore some aspects of it are still shrouded in older communication standards. We use XML to communicate with Voice servers and define certain aspects of our call.

The first methods that we will define will basically be returning XML that will be used to render various voices and add various functionalities.

I haven't found a great XML builder library for Python but don't hesitate to educate all of us in the comments

Let's get to writing our first proper methods! Just below the app=Flask(__name__) line, add the following lines of code:

def entry_phrase():
    response = '<?xml version="1.0"?>'
    response += '<Response>'
    response += '<GetDigits timeout="30" finishOnKey="#" callbackUrl="https://limitless-mountain-80043.herokuapp.com/voice/say">'
    response += '<Play url="http://197.248.0.197:8081/ad846fec309505710462e90a4b48bcb7.mp3">'
    response += '</Play>'
    response += '</GetDigits>'
    response += '<Say>Hi, welcome to the Africas Talking Freelance Developer Program demo app. We have a little question for you. How old is Africas Talking? Dial in your guess and press hash'
    response += '</Response>'

    return response

def success_flow():
    response = '<?xml version="1.0"?>'
    response += '<Response>'
    response += '<Say voice="man">'
    response += 'Awesome! You got it right! Africas Talking has been around for almost a decade!'
    response += '</Say>'
    response += '</Response>'

    return response

def error_flow_too_high():
    response = '<?xml version="1.0"?>'
    response += '<Response>'
    response += '<Say voice="man">'
    response += 'Hi, sorry, thats not quite it. Guess a little lower. Call back to try again. Goodbye.'
    response += '</Say>'
    response += '</Response>'

    return response

def error_flow_too_low():
    response = '<?xml version="1.0"?>'
    response += '<Response>'
    response += '<Say voice="man">'
    response += 'Hi, sorry, thats not quite it. Guess a little higher. Call back to try again. Goodbye.'
    response += '</Say>'
    response += '</Response>'

def error_flow():
    response = '<?xml version="1.0"?>'
    response += '<Response>'
    response += '<Say voice="man">'
    response += 'Hi, sorry, thats not quite it. Something is wrong with the input you provided. Call back to try again. Goodbye.'
    response += '</Say>'
    response += '</Response>'

The code above defines the responses that we will be using to give feedback and instructions to the user. Here's a breakdown of what each of them is doing:

entry_phrase()

This will be the first phrase that the user will hear as the app loads and collects the digits that the user presses (We use# to register user input). It contains instructions on what to do including the asking Africa's Talking's age. The final XML looks something like this:

<?xml version="1.0"?>
<Response>
    <GetDigits timeout="30" finishOnKey="#" callbackUrl="<YOUR_ROOT_URL>/voice/say">
        <Say voice="woman">Hi, welcome to the Africas Talking Freelance Developer Program demo app. We have a little question for you. How old is Africas Talking? Dial in your guess and press hash</Say>
    </GetDigits>
</Response>

We will leave the callback URL element empty for now till we deploy and get our actual route but for testing you can use localhost as

success_flow()

This piece returns XML that will be read out the success message if someone gets the message right.

error_flow_too_high()

This will be read out if the user guesses higher than the actual answer

error_flow_too_low()

This will be read out if the user guesses lower than the actual answer

error_flow()

This will be read out if the input isn't right.

Routes and Logic

The index route

The fun part is here! Actually setting up the routes that we will be using is quite simple. Majority of the work is actually done.

First off, we need to get our root route, "/" playing entry phrase. To do this, change your index route to look like this:

@app.route('/', methods=["GET","POST"])
def index():
    if request.method == "GET":
        return status.HTTP_200_OK
    elif request.method == "POST":
        return entry_phrase()
    else:
        return status.HTTP_400_BAD_REQUEST

This route will do a couple of things for us:

  1. If the HTTP request we get is a GET then we show a 200 status code that means everything is okay (at least, so far)
  2. If the request comes as a POST then we need to actually say something to our user and we return entry_phrase().
  3. If we get something that's not a POST or GET, we then return an error. Only POST and GET requests are allowed on the server.

Once we have this, we are set to handle the first part of the call. Next, we need to direct the user to the appropriate flow once they have provided their input.

Something to note is that at this phase, Africa's Taking's API will send in data to your API with some basic information. Let's breakdown some of the data we'll be receiving:

  1. sessionId - This uniquely identifies the call session
  2. callerNumber - This shows you which number is making a call
  3. destinationNumber - This is your Africa's Talking number
  4. dtmfDigits - This is the user input we collect once the user is on the call

We won't use all these data points in this tutorial but checkout the documentation around handling calls for more information on what you can do.

The next route

Next we need to define a new route, <YOUR_URL>/voice/say.

You could name the route whatever suits you best.

This route will check if the user input from the previous route is actually correct. Africa's Talking is 9 years old and that's the value we'll be working with. Add the code below just below the first route:

@app.route('/voice/say', methods=["GET","POST"])
def say_func():
    #Get values from the API
    digits = request.values.get("dtmfDigits")

    if digits == 9:
        return success_flow()
    elif digits > 9:
        return error_flow_too_high()
    elif digits < 9:
        return error_flow()
    else:
        return status.HTTP_400_BAD_REQUEST

Here we implement some simple if statement logic that checks whether user input, which we get from the API, is correct. Here we are only using dtmfDigits but you can save user responses and use that as a way of tracking progress through a custom flow.

Once done, we basically have our voice application ready. Next up, we need to prep it for deployment. You can test your endpoints with Postman or, as I found out today courtesy of Hashnode, Postwoman.

Deploying to Heroku with a Docker container

We'll be using Heroku in this case to deploy our app and best of all we'll be using Docker to containerize our application. To get this done, you'll need a Heroku account and Docker installed on your machine. Once you have your accounts created, install the Heroku CLI and run the following commands:

heroku login
heroku container:login

This logs us into the Heroku CLI and the second command gets our Heroku container registry setup. To confirm that you have Docker properly installed, run:

docker --version

You should see something like Docker version 19.03.4, build 9013bf5. That's what I'm running currently.

Let's get to deploying this awesome app of ours!

Docker and containers

Docker basically ends the "Works on my machine but not in production" problem that so many of us have faced. To make our app a containerized application, we need to create a Dockerfile. To do this, create a new file on your terminal with:

touch Dockerfile

Open the file and add the following lines:

FROM python:3
WORKDIR /code
ENV FLASK_APP app.py
ENV FLASK_RUN_HOST 0.0.0.0
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
COPY . .
CMD ["python","app.py"]

This is what is happening:

  1. The first line creates a container built on the Python 3 Alpine image which a lightweight linux distribution.
  2. Line 2 sets the working directory as code
  3. Line 3 sets the default flask application as app.py. This is an environmental variable
  4. Line 4 sets the default host to 0.0.0.0. This is an environmental variable as well
  5. We then copy requirements.txt into the container
  6. We install the packages defined in the requirements.txt file into the container
  7. We copy all the project files into the container's working directory which is /code.
  8. We then execute a command python app.py in the container's terminal

We have essentially finished setting up our container. We just need to run a few commands to get up and running to talk to the Africa's Talking service.

Firstly, we will build our container into an image. We ask docker to build this container, tag it voiceapp and use the Dockerfile in this folder.

docker build -t voiceapp .

Secondly, we will create a heroku app.:

heroku create

We now have an project on Heroku that can actually host our application. The Heroku CLI displays it on your terminal. We then set up heroku to take our container for deployment:

heroku container:push web --app <YOUR_HEROKU_APP_NAME>

The container will be published but it's still not live. Next, we make it live with this command:

heroku container:release web --app <YOUR_HEROKU_APP_NAME>

We are now live!!!!

You can view your application on your Heroku dashboard or view logs from the container using:

heroku logs --app <YOUR_HEROKU_APP_NAME> --tail

Remember to add in the link to your /voice/say route as soon as you're live

Connecting to the Africa's Talking Voice Dashboard

Now that our app is live, we head to the Africa's Talking dashboard . If you don't have an account, signing up is super easy. Once its all set up, create an team and an app that you would use. We currently are still working on getting the Voice sandbox out hence we'll be heading to a live account immediately. Head over to the Voice tab and you should be able to see the number assigned to you. You won't need an API key for this product.

Screen Shot 2019-11-06 at 4.30.12 PM.png

Using the link that we have from Heroku, we set it as the callback URL. And that's it. Give your number a call and enjoy a nice interactive voice response system right there!

Summary

Today, we built an IVR application with some basic functionality. This is a taste of the power of Africa's Talking APIs. I would love to know what awesome apps you can build using this tech and we'll be giving away numbers to developers and start-ups with great ideas on how to use this tech. You can fill out this form and we'll get back to you ASAP! Can't wait to see what awesome software comes out of this.

Next Steps

Next up, we'll be covering a little more advanced techniques around Voice and USSD. If you have cool apps you've built on Africa's Talking, share it in an article and we'll give you a shout-out! (and some sway too!!! ๐Ÿ˜)