Phone IVR Application Migration from Plivo XML to FlexML

If you have read the Migrating from Plivo to CarrierX Quick Start, you could see that Plivo XML and FlexML are not too much different. Both offer a special syntax to provide instructions, and all you need is to change the representation of these instructions in your code.

But when it comes to a real-case migration from Plivo to CarrierX, users might meet some difficulties.

Let’s see such a migration in details and learn how to solve the issues that arise so that your migrated application worked with CarrierX flawlessly.

Getting Application Source Code

We take the Phone IVR application from Plivo as an example. This sample application answers the call and offers the calling party to either listen to the prerecorded message in different languages or to listen to a song. You can download the application source code at GitHub.

The application contains a single phone_ivr.py file which we are going to modify.

All the routes used to send requests and responses for our application are in this file.

Modifying Application Routes

Let’s take a closer look at each of the routes in the file.

The application code file contains three routes:

We will go step by step through each of the routes, check what each of them contains, and learn how to modify these routes to migrate their code to FlexML.

1. ivr Route

The first route we are going to change is ivr. The application uses it to greet its users, play the initial instructions, and wait for the calling party input.

We modify the ivr route like this:

  1. The ivr route uses the ResponseElement() class to build the Plivo XML response to the call. In FlexML we do not need it, so we can remove this line.

  2. We remove the absolute URL path from the getinput_action_url, as FlexML allows using the relative path. This will be more useful if we decide to test our application locally.

  3. We also remove the response.add() function which builds the response.

  4. Finally, we return the FlexML formatted string instead of the Plivo XML Response(). In it, we replace Plivo XML GetInput element with the corresponding CarrierX FlexML Gather verb (dropping the unsupported dtmf attribute), the Speak element with the Say verb.

Plivo XML Python Code

@app.route('/ivr/', methods=['GET','POST'])
def ivr():
    response = plivoxml.ResponseElement()
    getinput_action_url = "http://www.foo.com/firstbranch/"
    response.add(plivoxml.GetInputElement().
        set_action(getinput_action_url).
        set_method('POST').
        set_input_type('dtmf').
        set_digit_end_timeout(5).
        set_redirect(True).add(
            plivoxml.SpeakElement(ivr_message1)))
    response.add(plivoxml.SpeakElement(noinput_message))
    return Response(response.to_string(), mimetype='application/xml')

Corresponding FlexML Python Syntax

@app.route('/ivr/', methods=['GET','POST'])
def ivr():
    getinput_action_url = "/firstbranch/"
    return f'''
        <Response>
            <Gather action="{getinput_action_url}" method="POST" timeout="5">
                <Say>{ivr_message1}</Say>
            </Gather>
            <Say>{noinput_message}</Say>
        </Response>'''

2. firstbranch Route

The firstbranch route checks what digits the calling party enters:

Thus, we change this route the following way:

  1. The firstbranch route uses the ResponseElement() class to build the Plivo XML response to the call. In FlexML we do not need it, so we can remove this line.

  2. The next code portion to change is the way the application gets the data from the call. In CarrierX, it is pure JSON, which you can receive and parse using the common Flask request module. Refer to the code below to see how we replace the definition of the digit variable.

  3. We remove the absolute URL path from the getinput_action_url, as FlexML allows using the relative path. This will be more useful if we decide to test our application locally.

  4. We replace the response.add() functions in the if condition with a new response variable. It will be a string with the FlexML syntax.

  5. We perform the same operation for the response.add() function in the elif condition.

  6. And the response.add() function in the else condition.

  7. Finally, we simply return the resulting response to the route.

Plivo XML Python Code

@app.route('/firstbranch/', methods=['GET','POST'])
def firstbranch():
    response = plivoxml.ResponseElement()
    digit = request.values.get('Digits')
    if digit == "1":
        getinput_action_url = "http://www.foo.com/secondbranch/"
        response.add(plivoxml.GetInputElement().
            set_action(getinput_action_url).
            set_method('POST').
            set_input_type('dtmf').
            set_digit_end_timeout(5).
            set_redirect(True).add(
                plivoxml.SpeakElement(ivr_message2)))
        response.add(plivoxml.SpeakElement(noinput_message))
    elif digit == "2":
        response.add_play(plivo_song)
    else:
        response.add_speak(wronginput_message)
    return Response(response.to_string(), mimetype='application/xml')

Corresponding FlexML Python Syntax

@app.route('/firstbranch/', methods=['GET','POST'])
def firstbranch():
    data = request.get_json()
    digit = data.get('Digits','')
    if digit == "1":
        getinput_action_url = "/secondbranch/"
        response = f'''
            <Response>
                <Gather action="{getinput_action_url}" method="POST" timeout="5">
                    <Say>{ivr_message2}</Say>
                </Gather>
                <Say>{noinput_message}</Say>
            </Response>'''
    elif digit == "2":
        response = f'''
            <Response>
                <Play>{plivo_song}</Play>
            </Response>'''
    else:
        response = f'''
            <Response>
                <Say>{wronginput_message}</Say>
            </Response>'''
    return response

3. secondbranch Route

The secondbranch route checks what digits the calling party enters:

Thus, we change this route the following way:

  1. The secondbranch route uses the ResponseElement() class to build the Plivo XML response to the call. In FlexML we do not need it, so we can remove this line.

  2. The next code portion to change is the way the application gets the data from the call. In CarrierX, it is pure JSON, which you can receive and parse using the common Flask request module. Refer to the code below to see how we replace the definition of the digit variable.

  3. We replace the params variable with the language variable and the response.add() function with a new response variable in the if condition. It will be a string with the FlexML syntax.

  4. We do the same for the French language in the first elif condition.

  5. And the same for the Russian language in the second elif condition.

  6. We also replace the response.add() function with the response variable in the else condition.

  7. Finally, we simply return the resulting response to the route.

Plivo XML Python Code

@app.route('/secondbranch/', methods=['GET','POST'])
def secondbranch():
    response = plivoxml.ResponseElement()
    digit = request.values.get('Digits')
    if digit == "1":
        text = u"This message is being read out in English"
        params = {
            'language': "en-GB",
        }
        response.add_speak(text,**params)
    elif digit == "2":
        text = u"Ce message est lu en français"
        params = {
            'language': "fr-FR",
        }
        response.add_speak(text,**params)
    elif digit == "3":
        text = u"Это сообщение было прочитано на русском"
        params = {
            'language': "ru-RU",
        }
        response.add_speak(text,**params)
    else:
        response.add_speak(wronginput_message)
    return Response(response.to_string(), mimetype='application/xml')

Corresponding FlexML Python Syntax

@app.route('/secondbranch/', methods=['GET','POST'])
def secondbranch():
    data = request.get_json()
    digit = data.get('Digits','')
    if digit == "1":
        text = u"This message is being read out in English"
        language = "en-GB"
        response = f'''
            <Response>
                <Say language="{language}">{text}</Say>
            </Response>'''
    elif digit == "2":
        text = u"Ce message est lu en français"
        language = "fr-FR"
        response = f'''
            <Response>
                <Say language="{language}">{text}</Say>
            </Response>'''
    elif digit == "3":
        text = u"Это сообщение было прочитано на русском"
        language = "ru-RU"
        response = f'''
            <Response>
                <Say language="{language}">{text}</Say>
            </Response>'''
    else:
        response = f'''
            <Response>
                <Say>{wronginput_message}</Say>
            </Response>'''
    return response

Now that we modified all the routes, we can safely remove the Plivo library import declaration from the beginning of the phone_ivr.py file:

from plivo import plivoxml
from flask import Flask, request

plivo_song = "https://s3.amazonaws.com/plivocloud/music.mp3"
ivr_message1 = "Welcome to the CarrierX IVR Demo App. Press 1 to listen to a pre recorded text in different languages.  \
                Press 2 to listen to a song."
ivr_message2 = "Press 1 for English. Press 2 for French. Press 3 for Russian"
noinput_message = "Sorry, I didn't catch that. Please hangup and try again \
                    later."
wronginput_message = "Sorry, it's wrong input."

app = Flask(__name__)

@app.route('/', methods=['GET','POST'])
def ivr():
    getinput_action_url = "/firstbranch/"
    return f'''
        <Response>
            <Gather action="{getinput_action_url}" method="POST" timeout="5">
                <Say>{ivr_message1}</Say>
            </Gather>
            <Say>{noinput_message}</Say>
        </Response>'''

@app.route('/firstbranch/', methods=['GET','POST'])
def firstbranch():
    data = request.get_json()
    digit = data.get('Digits','')
    if digit == "1":
        getinput_action_url = "/secondbranch/"
        response = f'''
            <Response>
                <Gather action="{getinput_action_url}" method="POST" timeout="5">
                    <Say>{ivr_message2}</Say>
                </Gather>
                <Say>{noinput_message}</Say>
            </Response>'''
    elif digit == "2":
        response = f'''
            <Response>
                <Play>{plivo_song}</Play>
            </Response>'''
    else:
        response = f'''
            <Response>
                <Say>{wronginput_message}</Say>
            </Response>'''
    return response

@app.route('/secondbranch/', methods=['GET','POST'])
def secondbranch():
    data = request.get_json()
    digit = data.get('Digits','')
    if digit == "1":
        text = u"This message is being read out in English"
        language = "en-GB"
        response = f'''
            <Response>
                <Say language="{language}">{text}</Say>
            </Response>'''
    elif digit == "2":
        text = u"Ce message est lu en français"
        language = "fr-FR"
        response = f'''
            <Response>
                <Say language="{language}">{text}</Say>
            </Response>'''
    elif digit == "3":
        text = u"Это сообщение было прочитано на русском"
        language = "ru-RU"
        response = f'''
            <Response>
                <Say language="{language}">{text}</Say>
            </Response>'''
    else:
        response = f'''
            <Response>
                <Say>{wronginput_message}</Say>
            </Response>'''
    return response

if __name__ == '__main__':
    app.run(host='0.0.0.0', debug=True)

Finishing Migration

Now let’s test our application! You can run your local Flask server through terminal using the following command that will serve the created application to the incoming requests.

FLASK_APP=phone_ivr.py flask run

Now your application is running, but it is not visible to the outside world. We need to expose the localhost route publicly over the Internet so that your endpoint could access and load the FlexML instructions. To do this, we can use the free ngrok tool. If you choose to use ngrok, follow the instructions on their website to download it. Alternatively, you may expose the URL any other way you choose.

Once your Flask server is running, your terminal will list the port it is running on. Expose this port by running the following command through terminal. Make sure to replace 5000 (which is the Flask default port) with the port that your Flask server is running on.

./ngrok http 5000

When ngrok is run successfully, it will open a new terminal tab that will show you the http and https URLs that make the application publicly available over the Internet.

ngrok Terminal Window

Add the https ngrok link (in our sample it is https://d4d66ed41d49.ngrok.io/ivr, as we chose ivr to be our default route) to either a FlexML endpoint or a DID associated with a FlexML endpoint. Refer to the FlexML Endpoint Quick Start Guide to learn how.

After that, call the associated DID to check how the application works.

Further Reading

You have successfully migrated the Phone IVR application from Plivo XML to FlexML!

Refer to the following pages to learn more about FlexML verbs and how to use them, and about ways to set up a FlexML endpoint:

Use our Migrating from Plivo XML to CarrierX Quick Start to learn more about other difficulties you can meet while migrating from Plivo XML to CarrierX and the ways to solve these issues.

Read other instructions on real-case migrations from Plivo XML to CarrierX here:

Refer to our other quick starts for instructions on how to work with CarrierX: