Voicemail 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 Voicemail application from Plivo as an example. This sample application answers the call and invites the calling party to record a voicemail message which they can listen to later. You can download the application source code at GitHub.

The application contains a single voicemail.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. record Route

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

We modify the record route like this:

  1. The record 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 action attribute, as FlexML allows using the relative path. This will be more useful if we decide to test our application locally. We leave the absolute URL for the transcription_url attribute, but rename it to transcribeCallback which is used with FlexML. We also remove the response.add() function which builds the response.

  3. Finally, we return the FlexML formatted string instead of the Plivo XML Response(). In this string, we add the playBeep="true" and transcribe="true" attributes as they are set to false by default in FlexML. We also replace Plivo XML Speak element with the corresponding CarrierX FlexML Say verb.

Plivo XML Python Code
@app.route("/record/", methods=["POST", "GET"])
def record():
    response = plivoxml.ResponseElement()
    response.add(
        plivoxml.SpeakElement(
            "Please leave a message after the beep."
        )
    )
    response.add(
        plivoxml.RecordElement(
            action="http://foo.com/save_record_url/",
            method="GET",
            max_length=30,
            transcription_type="auto",
            transcription_url="http://foo.com/transcription/",
            transcription_method="GET",
        )
    )
    return Response(response.to_string(), mimetype="application/xml")

Corresponding FlexML Python Syntax
@app.route("/record/", methods=["POST", "GET"])
def record():
    return '''
        <Response>
            <Say>Please leave a message after the beep.</Say>
            <Record action="/save_record_url/" maxLength="30" playBeep="true" method="GET" transcribe="true" transcribeCallback="http://foo.com/transcription/"></Record>
        </Response>'''

2. save_record_url Route

The save_record_url route checks what method is used to access the route and prints the URL of the recorded message to the console:

Thus, we change this route the following way:

  1. In CarrierX FlexML, the callback returns the recording URL as the RecordingUrl attribute. We change the attribute name returned by the GET request.

  2. The next code portion to change is the way the application gets the data from the callback which uses the POST method. In CarrierX, it is pure JSON, which you can receive and parse using the common Flask request module. We replace the way the application extracts the recording URL for the POST request and change the attribute name with RecordingUrl.

  3. Finally, we pronounce the OK message to the calling party.

Plivo XML Python Code
@app.route("/save_record_url/", methods=["GET"])
def save_record_url():
    if request.method == "GET":
        print(f"Record URL : {request.args.get('RecordUrl')}")
    elif request.method == "POST":
        print(f"Record URL :{request.form.get('RecordUrl')}")
    return Response("OK", mimetype="text/xml")

Corresponding FlexML Python Syntax
@app.route("/save_record_url/", methods=["GET"])
def save_record_url():
    if request.method == "GET":
        print(f"Record URL : {request.args.get('RecordingUrl')}")
    elif request.method == "POST":
        print(f"Record URL :{request.get_json().get('RecordingUrl','')}")
    return '''
        <Response>
            <Say>OK</Say>
        </Response>'''

3. transcription Route

The transcription route checks what method is used to access the route and prints the transcription of the recorded message to the console:

Thus, we change this route the following way:

  1. In CarrierX FlexML, the callback returns the transcription text as the TranscriptionText attribute. We change the attribute name returned by the GET request.

  2. The next code portion to change is the way the application gets the data from the callback which uses the POST method. In CarrierX, it is pure JSON, which you can receive and parse using the common Flask request module. We replace the way the application extracts the transcription text for the POST request and change the attribute name with TranscriptionText.

  3. Finally, we pronounce the OK message to the calling party.

Plivo XML Python Code
@app.route("/transcription/", methods=["GET", "POST"])
def transcription():
    if request.method == "GET":
        print("Transcription is : %s " % (request.args.get("transcription")))
    elif request.method == "POST":
        print("Transcription is : %s " % (request.form.get("transcription")))
    return Response("OK", mimetype="text/xml")

Corresponding FlexML Python Syntax
@app.route("/transcription/", methods=["GET", "POST"])
def transcription():
    if request.method == "GET":
        print("Transcription is : %s " % (request.args.get('TranscriptionText')))
    elif request.method == "POST":
        print("Transcription is : %s " % (request.get_json().get('TranscriptionText','')))
    return '''
        <Response>
            <Say>OK</Say>
        </Response>'''

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

from plivo import plivoxml
from flask import Flask, request

app = Flask(__name__)

@app.route("/record/", methods=["POST", "GET"])
def record():
    return '''
        <Response>
            <Say>Please leave a message after the beep.</Say>
            <Record action="/save_record_url/" maxLength="30" playBeep="true" method="GET" transcribe="true" transcribeCallback="/transcription/"></Record>
        </Response>'''

@app.route("/save_record_url/", methods=["GET"])
def save_record_url():
    if request.method == "GET":
        print(f"Record URL : {request.args.get('RecordingUrl')}")
    elif request.method == "POST":
        print(f"Record URL :{request.get_json().get('RecordingUrl','')}")
    return '''
        <Response>
            <Say>OK</Say>
        </Response>'''

@app.route("/transcription/", methods=["GET", "POST"])
def transcription():
    if request.method == "GET":
        print("Transcription is : %s " % (request.args.get('TranscriptionText')))
    elif request.method == "POST":
        print("Transcription is : %s " % (request.get_json().get('TranscriptionText','')))
    return '''
        <Response>
            <Say>OK</Say>
        </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=voicemail.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/record, as we chose record 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 Voicemail 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 start guides for instructions on how to work with CarrierX: