IVR: Screening & Recording with Python and Django

January 10, 2017
Written by
Martin Mena
Contributor
Opinions expressed by Twilio contributors are their own
Reviewed by
Jose Oliveros
Contributor
Opinions expressed by Twilio contributors are their own
Paul Kamp
Twilion
Kat King
Twilion

ivr-screening-recording-python-django

 

This Django sample application is modeled after a typical call center experience, but with more Reese's Pieces.

Stranded aliens can call an agent and receive instructions on how to get off of Earth safely. In this tutorial, we'll show you the key bits of code that allow an agent to send a caller to voicemail, and later read transcripts and listen to voicemails.

To run this sample app yourself, download the code and follow the instructions on GitHub.

IVR Screening and Recording in Python & Django

See more IVR application builds on our IVR application page.

Route the Call to an Agent

When our alien caller chooses a planet, we need to figure out where to route the call. Depending on their input, we will route this call to an extension. Extensions are used to look up an agent. Any string can be used to define an extension.

When our alien caller has made their choice we use the key-press to look up the requested Agent.

Once we look up the agent, we can use the <Dial> verb to dial the agent's phone number and try to connect the call.

Editor: this is a migrated tutorial. Find the original code at https://github.com/TwilioDevEd/ivr-recording-django/

from django.http import HttpResponse, HttpResponseServerError
from django.shortcuts import render
from django.urls import reverse
from django.views.decorators.csrf import csrf_exempt
from twilio.twiml.voice_response import VoiceResponse

from .models import Agent, Recording


def index(request):
    context = {'title': 'Sample IVR Recordings'}
    return render(request, 'ivr/index.html', context)


def agents(request):
    agents = Agent.objects.order_by('name')
    context = {'agents': agents}
    return render(request, 'ivr/agents.html', context)


class TwiMLResponse(HttpResponse):
    def __init__(self, *args, **kwargs):
        kwargs['content_type'] = 'application/xml'
        super().__init__(*args, **kwargs)


@csrf_exempt
def welcome(request):
    twiml_response = VoiceResponse()
    gather = twiml_response.gather(action=reverse('ivr:menu'), num_digits=1)
    gather.play('https://can-tasty-8188.twil.io/assets/et-phone.mp3', loop=3)
    return HttpResponse(twiml_response, content_type='application/xml')


# MAIN MENU


@csrf_exempt
def menu(request):
    selected_option = request.POST['Digits']
    options = {
        '1': return_instructions,
        '2': planets,
    }
    action = options.get(selected_option, redirect_welcome)
    return TwiMLResponse(action())


def return_instructions():
    twiml_response = VoiceResponse()
    twiml_response.say(
        'To get to your extraction point, get on your bike and go down '
        'the street. Then Left down an alley. Avoid the police cars.'
        ' Turn left into an unfinished housing development. Fly over '
        'the roadblock. Go passed the moon. Soon after you will see '
        'your mother ship.',
        voice='Polly.Amy',
        language='en-GB',
    )
    twiml_response.say(
        'Thank you for calling the ET Phone Home Service - the '
        'adventurous alien\'s first choice in intergalactic travel'
    )
    twiml_response.hangup()
    return twiml_response


def planets():
    twiml_response = VoiceResponse()
    gather = twiml_response.gather(action=reverse('ivr:agent_connect'), num_digits=1)
    gather.say(
        'To call the planet Broh doe As O G, press 2. To call the '
        'planet DuhGo bah, press 3. To call an oober asteroid to your '
        'location, press 4. To go back to the main menu, press '
        'the star key ',
        voice='Polly.Amy',
        language='en-GB',
        loop=3,
    )
    return twiml_response


def redirect_welcome():
    twiml = VoiceResponse()
    twiml.redirect(reverse('ivr:welcome'))
    return twiml


# AGENTS


@csrf_exempt
def agent_connect(request):
    selected_option = request.POST.get('Digits')
    agents = {
        '2': 'Brodo',
        '3': 'Dagobah',
        '4': 'Oober',
    }
    selected_agent = agents.get(selected_option)

    if not selected_agent:
        # Bad user input
        return TwiMLResponse(redirect_welcome())

    try:
        agent = Agent.objects.get(name=selected_agent)
    except Agent.DoesNotExist:
        return TwiMLResponse(redirect_welcome())

    twiml = VoiceResponse()
    twiml.say(
        'You\'ll be connected shortly to your planet.',
        voice='Polly.Amy',
        language='en-GB',
    )
    dial = twiml.dial(
        action=f"{reverse('ivr:agents_call')}?agentId={agent.id}",
        callerId=agent.phone_number,
    )
    dial.number(agent.phone_number, url=reverse('ivr:agents_screencall'))
    return TwiMLResponse(twiml)


@csrf_exempt
def agent_call(request):
    if request.POST['CallStatus'] == 'completed':
        return HttpResponse('')
    twiml = VoiceResponse()
    twiml.say(
        'It appears that no agent is available. Please leave a message after the beep',
        voice='Polly.Amy',
        language='en-GB',
    )
    twiml.record(
        max_length=20,
        action=reverse('ivr:hangup'),
        transcribe_callback=f"{reverse('ivr:recordings')}?agentId={request.GET.get('agentId')}",  # noqa
    )
    return TwiMLResponse(twiml)


@csrf_exempt
def hangup(request):
    twiml = VoiceResponse()
    twiml.say(
        'Thanks for your message. Goodbye', voice='Polly.Amy', language='en-GB',
    )
    twiml.hangup()
    return TwiMLResponse(twiml)


@csrf_exempt
def screencall(request):
    twiml = VoiceResponse()
    gather = twiml.gather(action=reverse('ivr:agents_connect_message'))
    phone_number = request.POST['From']
    spelled_phone_number = ' '.join(char for char in phone_number)
    gather.say(spelled_phone_number)
    gather.say('Press any key to accept')
    twiml.say('Sorry. Did not get your response')
    twiml.hangup()
    return HttpResponse(str(twiml))


@csrf_exempt
def connect_message(request):
    twiml = VoiceResponse()
    twiml.say('Connecting you to the extraterrestrial in distress')
    return HttpResponse(str(twiml))


# RECORDINGS


@csrf_exempt
def recordings(request):
    agentId = request.GET.get('agentId')
    agent = Agent.objects.get(pk=agentId)
    try:
        recording = Recording.objects.create(
            caller_number=request.POST['From'],
            transcription=request.POST['TranscriptionText'],
            url=request.POST['RecordingUrl'],
        )
        agent.recordings.add(recording)
    except:  # noqa
        return HttpResponseServerError('Could not create a recording')
    else:
        return HttpResponse('Recording created', status=201)

Now that our user has chosen their agent, our next step is to connect the call to that agent.

Call the agent

This code begins the process of transferring the call to our agent.

By passing a url to the <Number> noun, we are telling Twilio to make a POST request to the agent/screencall route after the agent has picked up but before connecting the two parties.

Essentially, we are telling Twilio to execute some TwiML that only the agent will hear.

from django.http import HttpResponse, HttpResponseServerError
from django.shortcuts import render
from django.urls import reverse
from django.views.decorators.csrf import csrf_exempt
from twilio.twiml.voice_response import VoiceResponse

from .models import Agent, Recording


def index(request):
    context = {'title': 'Sample IVR Recordings'}
    return render(request, 'ivr/index.html', context)


def agents(request):
    agents = Agent.objects.order_by('name')
    context = {'agents': agents}
    return render(request, 'ivr/agents.html', context)


class TwiMLResponse(HttpResponse):
    def __init__(self, *args, **kwargs):
        kwargs['content_type'] = 'application/xml'
        super().__init__(*args, **kwargs)


@csrf_exempt
def welcome(request):
    twiml_response = VoiceResponse()
    gather = twiml_response.gather(action=reverse('ivr:menu'), num_digits=1)
    gather.play('https://can-tasty-8188.twil.io/assets/et-phone.mp3', loop=3)
    return HttpResponse(twiml_response, content_type='application/xml')


# MAIN MENU


@csrf_exempt
def menu(request):
    selected_option = request.POST['Digits']
    options = {
        '1': return_instructions,
        '2': planets,
    }
    action = options.get(selected_option, redirect_welcome)
    return TwiMLResponse(action())


def return_instructions():
    twiml_response = VoiceResponse()
    twiml_response.say(
        'To get to your extraction point, get on your bike and go down '
        'the street. Then Left down an alley. Avoid the police cars.'
        ' Turn left into an unfinished housing development. Fly over '
        'the roadblock. Go passed the moon. Soon after you will see '
        'your mother ship.',
        voice='Polly.Amy',
        language='en-GB',
    )
    twiml_response.say(
        'Thank you for calling the ET Phone Home Service - the '
        'adventurous alien\'s first choice in intergalactic travel'
    )
    twiml_response.hangup()
    return twiml_response


def planets():
    twiml_response = VoiceResponse()
    gather = twiml_response.gather(action=reverse('ivr:agent_connect'), num_digits=1)
    gather.say(
        'To call the planet Broh doe As O G, press 2. To call the '
        'planet DuhGo bah, press 3. To call an oober asteroid to your '
        'location, press 4. To go back to the main menu, press '
        'the star key ',
        voice='Polly.Amy',
        language='en-GB',
        loop=3,
    )
    return twiml_response


def redirect_welcome():
    twiml = VoiceResponse()
    twiml.redirect(reverse('ivr:welcome'))
    return twiml


# AGENTS


@csrf_exempt
def agent_connect(request):
    selected_option = request.POST.get('Digits')
    agents = {
        '2': 'Brodo',
        '3': 'Dagobah',
        '4': 'Oober',
    }
    selected_agent = agents.get(selected_option)

    if not selected_agent:
        # Bad user input
        return TwiMLResponse(redirect_welcome())

    try:
        agent = Agent.objects.get(name=selected_agent)
    except Agent.DoesNotExist:
        return TwiMLResponse(redirect_welcome())

    twiml = VoiceResponse()
    twiml.say(
        'You\'ll be connected shortly to your planet.',
        voice='Polly.Amy',
        language='en-GB',
    )
    dial = twiml.dial(
        action=f"{reverse('ivr:agents_call')}?agentId={agent.id}",
        callerId=agent.phone_number,
    )
    dial.number(agent.phone_number, url=reverse('ivr:agents_screencall'))
    return TwiMLResponse(twiml)


@csrf_exempt
def agent_call(request):
    if request.POST['CallStatus'] == 'completed':
        return HttpResponse('')
    twiml = VoiceResponse()
    twiml.say(
        'It appears that no agent is available. Please leave a message after the beep',
        voice='Polly.Amy',
        language='en-GB',
    )
    twiml.record(
        max_length=20,
        action=reverse('ivr:hangup'),
        transcribe_callback=f"{reverse('ivr:recordings')}?agentId={request.GET.get('agentId')}",  # noqa
    )
    return TwiMLResponse(twiml)


@csrf_exempt
def hangup(request):
    twiml = VoiceResponse()
    twiml.say(
        'Thanks for your message. Goodbye', voice='Polly.Amy', language='en-GB',
    )
    twiml.hangup()
    return TwiMLResponse(twiml)


@csrf_exempt
def screencall(request):
    twiml = VoiceResponse()
    gather = twiml.gather(action=reverse('ivr:agents_connect_message'))
    phone_number = request.POST['From']
    spelled_phone_number = ' '.join(char for char in phone_number)
    gather.say(spelled_phone_number)
    gather.say('Press any key to accept')
    twiml.say('Sorry. Did not get your response')
    twiml.hangup()
    return HttpResponse(str(twiml))


@csrf_exempt
def connect_message(request):
    twiml = VoiceResponse()
    twiml.say('Connecting you to the extraterrestrial in distress')
    return HttpResponse(str(twiml))


# RECORDINGS


@csrf_exempt
def recordings(request):
    agentId = request.GET.get('agentId')
    agent = Agent.objects.get(pk=agentId)
    try:
        recording = Recording.objects.create(
            caller_number=request.POST['From'],
            transcription=request.POST['TranscriptionText'],
            url=request.POST['RecordingUrl'],
        )
        agent.recordings.add(recording)
    except:  # noqa
        return HttpResponseServerError('Could not create a recording')
    else:
        return HttpResponse('Recording created', status=201)

Our agent can now be called, but how does our agent interact with this feature? Let's dig into what is happening in the agent's screening call.

The agent screens the call

When our agent picks up the phone, we use a <Gather> verb to ask them if they want to accept the call.

If the agent responds by entering any digit, the response will be processed by our agent/connect_message

 route. This will <Say> a quick message and continue with the original <Dial> command to connect the two parties.

from django.http import HttpResponse, HttpResponseServerError
from django.shortcuts import render
from django.urls import reverse
from django.views.decorators.csrf import csrf_exempt
from twilio.twiml.voice_response import VoiceResponse

from .models import Agent, Recording


def index(request):
    context = {'title': 'Sample IVR Recordings'}
    return render(request, 'ivr/index.html', context)


def agents(request):
    agents = Agent.objects.order_by('name')
    context = {'agents': agents}
    return render(request, 'ivr/agents.html', context)


class TwiMLResponse(HttpResponse):
    def __init__(self, *args, **kwargs):
        kwargs['content_type'] = 'application/xml'
        super().__init__(*args, **kwargs)


@csrf_exempt
def welcome(request):
    twiml_response = VoiceResponse()
    gather = twiml_response.gather(action=reverse('ivr:menu'), num_digits=1)
    gather.play('https://can-tasty-8188.twil.io/assets/et-phone.mp3', loop=3)
    return HttpResponse(twiml_response, content_type='application/xml')


# MAIN MENU


@csrf_exempt
def menu(request):
    selected_option = request.POST['Digits']
    options = {
        '1': return_instructions,
        '2': planets,
    }
    action = options.get(selected_option, redirect_welcome)
    return TwiMLResponse(action())


def return_instructions():
    twiml_response = VoiceResponse()
    twiml_response.say(
        'To get to your extraction point, get on your bike and go down '
        'the street. Then Left down an alley. Avoid the police cars.'
        ' Turn left into an unfinished housing development. Fly over '
        'the roadblock. Go passed the moon. Soon after you will see '
        'your mother ship.',
        voice='Polly.Amy',
        language='en-GB',
    )
    twiml_response.say(
        'Thank you for calling the ET Phone Home Service - the '
        'adventurous alien\'s first choice in intergalactic travel'
    )
    twiml_response.hangup()
    return twiml_response


def planets():
    twiml_response = VoiceResponse()
    gather = twiml_response.gather(action=reverse('ivr:agent_connect'), num_digits=1)
    gather.say(
        'To call the planet Broh doe As O G, press 2. To call the '
        'planet DuhGo bah, press 3. To call an oober asteroid to your '
        'location, press 4. To go back to the main menu, press '
        'the star key ',
        voice='Polly.Amy',
        language='en-GB',
        loop=3,
    )
    return twiml_response


def redirect_welcome():
    twiml = VoiceResponse()
    twiml.redirect(reverse('ivr:welcome'))
    return twiml


# AGENTS


@csrf_exempt
def agent_connect(request):
    selected_option = request.POST.get('Digits')
    agents = {
        '2': 'Brodo',
        '3': 'Dagobah',
        '4': 'Oober',
    }
    selected_agent = agents.get(selected_option)

    if not selected_agent:
        # Bad user input
        return TwiMLResponse(redirect_welcome())

    try:
        agent = Agent.objects.get(name=selected_agent)
    except Agent.DoesNotExist:
        return TwiMLResponse(redirect_welcome())

    twiml = VoiceResponse()
    twiml.say(
        'You\'ll be connected shortly to your planet.',
        voice='Polly.Amy',
        language='en-GB',
    )
    dial = twiml.dial(
        action=f"{reverse('ivr:agents_call')}?agentId={agent.id}",
        callerId=agent.phone_number,
    )
    dial.number(agent.phone_number, url=reverse('ivr:agents_screencall'))
    return TwiMLResponse(twiml)


@csrf_exempt
def agent_call(request):
    if request.POST['CallStatus'] == 'completed':
        return HttpResponse('')
    twiml = VoiceResponse()
    twiml.say(
        'It appears that no agent is available. Please leave a message after the beep',
        voice='Polly.Amy',
        language='en-GB',
    )
    twiml.record(
        max_length=20,
        action=reverse('ivr:hangup'),
        transcribe_callback=f"{reverse('ivr:recordings')}?agentId={request.GET.get('agentId')}",  # noqa
    )
    return TwiMLResponse(twiml)


@csrf_exempt
def hangup(request):
    twiml = VoiceResponse()
    twiml.say(
        'Thanks for your message. Goodbye', voice='Polly.Amy', language='en-GB',
    )
    twiml.hangup()
    return TwiMLResponse(twiml)


@csrf_exempt
def screencall(request):
    twiml = VoiceResponse()
    gather = twiml.gather(action=reverse('ivr:agents_connect_message'))
    phone_number = request.POST['From']
    spelled_phone_number = ' '.join(char for char in phone_number)
    gather.say(spelled_phone_number)
    gather.say('Press any key to accept')
    twiml.say('Sorry. Did not get your response')
    twiml.hangup()
    return HttpResponse(str(twiml))


@csrf_exempt
def connect_message(request):
    twiml = VoiceResponse()
    twiml.say('Connecting you to the extraterrestrial in distress')
    return HttpResponse(str(twiml))


# RECORDINGS


@csrf_exempt
def recordings(request):
    agentId = request.GET.get('agentId')
    agent = Agent.objects.get(pk=agentId)
    try:
        recording = Recording.objects.create(
            caller_number=request.POST['From'],
            transcription=request.POST['TranscriptionText'],
            url=request.POST['RecordingUrl'],
        )
        agent.recordings.add(recording)
    except:  # noqa
        return HttpResponseServerError('Could not create a recording')
    else:
        return HttpResponse('Recording created', status=201)

Now our agent can interact with the call, but what if our agent is currently out? In these cases it's helpful to have voicemail set up.

Send the caller to voicemail

When Twilio makes a request to our Call action method, it will pass a DialCallStatus argument to tell us the call status. If the status was "completed", we hang up. Otherwise, we need to <Say> a quick prompt and then <Record> a voicemail from the alien caller.

We also specify an action for <Record>. This route will be called after the call and recording have finished. The route will say "Goodbye" and then <Hangup>.

from django.http import HttpResponse, HttpResponseServerError
from django.shortcuts import render
from django.urls import reverse
from django.views.decorators.csrf import csrf_exempt
from twilio.twiml.voice_response import VoiceResponse

from .models import Agent, Recording


def index(request):
    context = {'title': 'Sample IVR Recordings'}
    return render(request, 'ivr/index.html', context)


def agents(request):
    agents = Agent.objects.order_by('name')
    context = {'agents': agents}
    return render(request, 'ivr/agents.html', context)


class TwiMLResponse(HttpResponse):
    def __init__(self, *args, **kwargs):
        kwargs['content_type'] = 'application/xml'
        super().__init__(*args, **kwargs)


@csrf_exempt
def welcome(request):
    twiml_response = VoiceResponse()
    gather = twiml_response.gather(action=reverse('ivr:menu'), num_digits=1)
    gather.play('https://can-tasty-8188.twil.io/assets/et-phone.mp3', loop=3)
    return HttpResponse(twiml_response, content_type='application/xml')


# MAIN MENU


@csrf_exempt
def menu(request):
    selected_option = request.POST['Digits']
    options = {
        '1': return_instructions,
        '2': planets,
    }
    action = options.get(selected_option, redirect_welcome)
    return TwiMLResponse(action())


def return_instructions():
    twiml_response = VoiceResponse()
    twiml_response.say(
        'To get to your extraction point, get on your bike and go down '
        'the street. Then Left down an alley. Avoid the police cars.'
        ' Turn left into an unfinished housing development. Fly over '
        'the roadblock. Go passed the moon. Soon after you will see '
        'your mother ship.',
        voice='Polly.Amy',
        language='en-GB',
    )
    twiml_response.say(
        'Thank you for calling the ET Phone Home Service - the '
        'adventurous alien\'s first choice in intergalactic travel'
    )
    twiml_response.hangup()
    return twiml_response


def planets():
    twiml_response = VoiceResponse()
    gather = twiml_response.gather(action=reverse('ivr:agent_connect'), num_digits=1)
    gather.say(
        'To call the planet Broh doe As O G, press 2. To call the '
        'planet DuhGo bah, press 3. To call an oober asteroid to your '
        'location, press 4. To go back to the main menu, press '
        'the star key ',
        voice='Polly.Amy',
        language='en-GB',
        loop=3,
    )
    return twiml_response


def redirect_welcome():
    twiml = VoiceResponse()
    twiml.redirect(reverse('ivr:welcome'))
    return twiml


# AGENTS


@csrf_exempt
def agent_connect(request):
    selected_option = request.POST.get('Digits')
    agents = {
        '2': 'Brodo',
        '3': 'Dagobah',
        '4': 'Oober',
    }
    selected_agent = agents.get(selected_option)

    if not selected_agent:
        # Bad user input
        return TwiMLResponse(redirect_welcome())

    try:
        agent = Agent.objects.get(name=selected_agent)
    except Agent.DoesNotExist:
        return TwiMLResponse(redirect_welcome())

    twiml = VoiceResponse()
    twiml.say(
        'You\'ll be connected shortly to your planet.',
        voice='Polly.Amy',
        language='en-GB',
    )
    dial = twiml.dial(
        action=f"{reverse('ivr:agents_call')}?agentId={agent.id}",
        callerId=agent.phone_number,
    )
    dial.number(agent.phone_number, url=reverse('ivr:agents_screencall'))
    return TwiMLResponse(twiml)


@csrf_exempt
def agent_call(request):
    if request.POST['CallStatus'] == 'completed':
        return HttpResponse('')
    twiml = VoiceResponse()
    twiml.say(
        'It appears that no agent is available. Please leave a message after the beep',
        voice='Polly.Amy',
        language='en-GB',
    )
    twiml.record(
        max_length=20,
        action=reverse('ivr:hangup'),
        transcribe_callback=f"{reverse('ivr:recordings')}?agentId={request.GET.get('agentId')}",  # noqa
    )
    return TwiMLResponse(twiml)


@csrf_exempt
def hangup(request):
    twiml = VoiceResponse()
    twiml.say(
        'Thanks for your message. Goodbye', voice='Polly.Amy', language='en-GB',
    )
    twiml.hangup()
    return TwiMLResponse(twiml)


@csrf_exempt
def screencall(request):
    twiml = VoiceResponse()
    gather = twiml.gather(action=reverse('ivr:agents_connect_message'))
    phone_number = request.POST['From']
    spelled_phone_number = ' '.join(char for char in phone_number)
    gather.say(spelled_phone_number)
    gather.say('Press any key to accept')
    twiml.say('Sorry. Did not get your response')
    twiml.hangup()
    return HttpResponse(str(twiml))


@csrf_exempt
def connect_message(request):
    twiml = VoiceResponse()
    twiml.say('Connecting you to the extraterrestrial in distress')
    return HttpResponse(str(twiml))


# RECORDINGS


@csrf_exempt
def recordings(request):
    agentId = request.GET.get('agentId')
    agent = Agent.objects.get(pk=agentId)
    try:
        recording = Recording.objects.create(
            caller_number=request.POST['From'],
            transcription=request.POST['TranscriptionText'],
            url=request.POST['RecordingUrl'],
        )
        agent.recordings.add(recording)
    except:  # noqa
        return HttpResponseServerError('Could not create a recording')
    else:
        return HttpResponse('Recording created', status=201)

Now let's take a step back to see how to actually record the call.

Record the caller

When we tell Twilio to record, we have a few options we can pass to the <Record> verb.

Here we instruct <Record> to stop the recording at 20 seconds, to transcribe the call, and to send the transcription to the agent when it's complete.

Notice that we redirect to a URL that is specific to this agent. This is a convenient way to specify which agent was called to produce the voice message. This way we can also save the associated agent together with the voicemail.

from django.http import HttpResponse, HttpResponseServerError
from django.shortcuts import render
from django.urls import reverse
from django.views.decorators.csrf import csrf_exempt
from twilio.twiml.voice_response import VoiceResponse

from .models import Agent, Recording


def index(request):
    context = {'title': 'Sample IVR Recordings'}
    return render(request, 'ivr/index.html', context)


def agents(request):
    agents = Agent.objects.order_by('name')
    context = {'agents': agents}
    return render(request, 'ivr/agents.html', context)


class TwiMLResponse(HttpResponse):
    def __init__(self, *args, **kwargs):
        kwargs['content_type'] = 'application/xml'
        super().__init__(*args, **kwargs)


@csrf_exempt
def welcome(request):
    twiml_response = VoiceResponse()
    gather = twiml_response.gather(action=reverse('ivr:menu'), num_digits=1)
    gather.play('https://can-tasty-8188.twil.io/assets/et-phone.mp3', loop=3)
    return HttpResponse(twiml_response, content_type='application/xml')


# MAIN MENU


@csrf_exempt
def menu(request):
    selected_option = request.POST['Digits']
    options = {
        '1': return_instructions,
        '2': planets,
    }
    action = options.get(selected_option, redirect_welcome)
    return TwiMLResponse(action())


def return_instructions():
    twiml_response = VoiceResponse()
    twiml_response.say(
        'To get to your extraction point, get on your bike and go down '
        'the street. Then Left down an alley. Avoid the police cars.'
        ' Turn left into an unfinished housing development. Fly over '
        'the roadblock. Go passed the moon. Soon after you will see '
        'your mother ship.',
        voice='Polly.Amy',
        language='en-GB',
    )
    twiml_response.say(
        'Thank you for calling the ET Phone Home Service - the '
        'adventurous alien\'s first choice in intergalactic travel'
    )
    twiml_response.hangup()
    return twiml_response


def planets():
    twiml_response = VoiceResponse()
    gather = twiml_response.gather(action=reverse('ivr:agent_connect'), num_digits=1)
    gather.say(
        'To call the planet Broh doe As O G, press 2. To call the '
        'planet DuhGo bah, press 3. To call an oober asteroid to your '
        'location, press 4. To go back to the main menu, press '
        'the star key ',
        voice='Polly.Amy',
        language='en-GB',
        loop=3,
    )
    return twiml_response


def redirect_welcome():
    twiml = VoiceResponse()
    twiml.redirect(reverse('ivr:welcome'))
    return twiml


# AGENTS


@csrf_exempt
def agent_connect(request):
    selected_option = request.POST.get('Digits')
    agents = {
        '2': 'Brodo',
        '3': 'Dagobah',
        '4': 'Oober',
    }
    selected_agent = agents.get(selected_option)

    if not selected_agent:
        # Bad user input
        return TwiMLResponse(redirect_welcome())

    try:
        agent = Agent.objects.get(name=selected_agent)
    except Agent.DoesNotExist:
        return TwiMLResponse(redirect_welcome())

    twiml = VoiceResponse()
    twiml.say(
        'You\'ll be connected shortly to your planet.',
        voice='Polly.Amy',
        language='en-GB',
    )
    dial = twiml.dial(
        action=f"{reverse('ivr:agents_call')}?agentId={agent.id}",
        callerId=agent.phone_number,
    )
    dial.number(agent.phone_number, url=reverse('ivr:agents_screencall'))
    return TwiMLResponse(twiml)


@csrf_exempt
def agent_call(request):
    if request.POST['CallStatus'] == 'completed':
        return HttpResponse('')
    twiml = VoiceResponse()
    twiml.say(
        'It appears that no agent is available. Please leave a message after the beep',
        voice='Polly.Amy',
        language='en-GB',
    )
    twiml.record(
        max_length=20,
        action=reverse('ivr:hangup'),
        transcribe_callback=f"{reverse('ivr:recordings')}?agentId={request.GET.get('agentId')}",  # noqa
    )
    return TwiMLResponse(twiml)


@csrf_exempt
def hangup(request):
    twiml = VoiceResponse()
    twiml.say(
        'Thanks for your message. Goodbye', voice='Polly.Amy', language='en-GB',
    )
    twiml.hangup()
    return TwiMLResponse(twiml)


@csrf_exempt
def screencall(request):
    twiml = VoiceResponse()
    gather = twiml.gather(action=reverse('ivr:agents_connect_message'))
    phone_number = request.POST['From']
    spelled_phone_number = ' '.join(char for char in phone_number)
    gather.say(spelled_phone_number)
    gather.say('Press any key to accept')
    twiml.say('Sorry. Did not get your response')
    twiml.hangup()
    return HttpResponse(str(twiml))


@csrf_exempt
def connect_message(request):
    twiml = VoiceResponse()
    twiml.say('Connecting you to the extraterrestrial in distress')
    return HttpResponse(str(twiml))


# RECORDINGS


@csrf_exempt
def recordings(request):
    agentId = request.GET.get('agentId')
    agent = Agent.objects.get(pk=agentId)
    try:
        recording = Recording.objects.create(
            caller_number=request.POST['From'],
            transcription=request.POST['TranscriptionText'],
            url=request.POST['RecordingUrl'],
        )
        agent.recordings.add(recording)
    except:  # noqa
        return HttpResponseServerError('Could not create a recording')
    else:
        return HttpResponse('Recording created', status=201)

Legal implications of call recording

If you choose to record voice or video calls, you need to comply with certain laws and regulations, including those regarding obtaining consent to record (such as California’s Invasion of Privacy Act and similar laws in other jurisdictions). Additional information on the legal implications of call recording can be found in the "Legal Considerations with Recording Voice and Video Communications" Help Center article.

Notice: Twilio recommends that you consult with your legal counsel to make sure that you are complying with all applicable laws in connection with communications you record or store using Twilio.

Finally, we will see how to view an agent's voicemail.

View an agent's voicemail

Once we look up the agent, all we need to do is display their recordings. We bind the agent, along with their recordings, to a View.

It is possible to look up recordings via the Twilio REST API, but since we have all of the data we need in the transcribe_callback request, we can easily store it ourselves and save a roundtrip.

from django.http import HttpResponse, HttpResponseServerError
from django.shortcuts import render
from django.urls import reverse
from django.views.decorators.csrf import csrf_exempt
from twilio.twiml.voice_response import VoiceResponse

from .models import Agent, Recording


def index(request):
    context = {'title': 'Sample IVR Recordings'}
    return render(request, 'ivr/index.html', context)


def agents(request):
    agents = Agent.objects.order_by('name')
    context = {'agents': agents}
    return render(request, 'ivr/agents.html', context)


class TwiMLResponse(HttpResponse):
    def __init__(self, *args, **kwargs):
        kwargs['content_type'] = 'application/xml'
        super().__init__(*args, **kwargs)


@csrf_exempt
def welcome(request):
    twiml_response = VoiceResponse()
    gather = twiml_response.gather(action=reverse('ivr:menu'), num_digits=1)
    gather.play('https://can-tasty-8188.twil.io/assets/et-phone.mp3', loop=3)
    return HttpResponse(twiml_response, content_type='application/xml')


# MAIN MENU


@csrf_exempt
def menu(request):
    selected_option = request.POST['Digits']
    options = {
        '1': return_instructions,
        '2': planets,
    }
    action = options.get(selected_option, redirect_welcome)
    return TwiMLResponse(action())


def return_instructions():
    twiml_response = VoiceResponse()
    twiml_response.say(
        'To get to your extraction point, get on your bike and go down '
        'the street. Then Left down an alley. Avoid the police cars.'
        ' Turn left into an unfinished housing development. Fly over '
        'the roadblock. Go passed the moon. Soon after you will see '
        'your mother ship.',
        voice='Polly.Amy',
        language='en-GB',
    )
    twiml_response.say(
        'Thank you for calling the ET Phone Home Service - the '
        'adventurous alien\'s first choice in intergalactic travel'
    )
    twiml_response.hangup()
    return twiml_response


def planets():
    twiml_response = VoiceResponse()
    gather = twiml_response.gather(action=reverse('ivr:agent_connect'), num_digits=1)
    gather.say(
        'To call the planet Broh doe As O G, press 2. To call the '
        'planet DuhGo bah, press 3. To call an oober asteroid to your '
        'location, press 4. To go back to the main menu, press '
        'the star key ',
        voice='Polly.Amy',
        language='en-GB',
        loop=3,
    )
    return twiml_response


def redirect_welcome():
    twiml = VoiceResponse()
    twiml.redirect(reverse('ivr:welcome'))
    return twiml


# AGENTS


@csrf_exempt
def agent_connect(request):
    selected_option = request.POST.get('Digits')
    agents = {
        '2': 'Brodo',
        '3': 'Dagobah',
        '4': 'Oober',
    }
    selected_agent = agents.get(selected_option)

    if not selected_agent:
        # Bad user input
        return TwiMLResponse(redirect_welcome())

    try:
        agent = Agent.objects.get(name=selected_agent)
    except Agent.DoesNotExist:
        return TwiMLResponse(redirect_welcome())

    twiml = VoiceResponse()
    twiml.say(
        'You\'ll be connected shortly to your planet.',
        voice='Polly.Amy',
        language='en-GB',
    )
    dial = twiml.dial(
        action=f"{reverse('ivr:agents_call')}?agentId={agent.id}",
        callerId=agent.phone_number,
    )
    dial.number(agent.phone_number, url=reverse('ivr:agents_screencall'))
    return TwiMLResponse(twiml)


@csrf_exempt
def agent_call(request):
    if request.POST['CallStatus'] == 'completed':
        return HttpResponse('')
    twiml = VoiceResponse()
    twiml.say(
        'It appears that no agent is available. Please leave a message after the beep',
        voice='Polly.Amy',
        language='en-GB',
    )
    twiml.record(
        max_length=20,
        action=reverse('ivr:hangup'),
        transcribe_callback=f"{reverse('ivr:recordings')}?agentId={request.GET.get('agentId')}",  # noqa
    )
    return TwiMLResponse(twiml)


@csrf_exempt
def hangup(request):
    twiml = VoiceResponse()
    twiml.say(
        'Thanks for your message. Goodbye', voice='Polly.Amy', language='en-GB',
    )
    twiml.hangup()
    return TwiMLResponse(twiml)


@csrf_exempt
def screencall(request):
    twiml = VoiceResponse()
    gather = twiml.gather(action=reverse('ivr:agents_connect_message'))
    phone_number = request.POST['From']
    spelled_phone_number = ' '.join(char for char in phone_number)
    gather.say(spelled_phone_number)
    gather.say('Press any key to accept')
    twiml.say('Sorry. Did not get your response')
    twiml.hangup()
    return HttpResponse(str(twiml))


@csrf_exempt
def connect_message(request):
    twiml = VoiceResponse()
    twiml.say('Connecting you to the extraterrestrial in distress')
    return HttpResponse(str(twiml))


# RECORDINGS


@csrf_exempt
def recordings(request):
    agentId = request.GET.get('agentId')
    agent = Agent.objects.get(pk=agentId)
    try:
        recording = Recording.objects.create(
            caller_number=request.POST['From'],
            transcription=request.POST['TranscriptionText'],
            url=request.POST['RecordingUrl'],
        )
        agent.recordings.add(recording)
    except:  # noqa
        return HttpResponseServerError('Could not create a recording')
    else:
        return HttpResponse('Recording created', status=201)

That's it! We've just implemented an IVR with real Agents, call screening and voicemail.

Where to next?

If you're a Python developer working with Twilio, you might want to check out these other tutorials.

Part 1 of this Tutorial: ET Phone Home Service - IVR Phone Trees

Increase your rate of response by automating the workflows that are key to your business.

Automated Survey

Instantly collect structured data from your users with a survey conducted over a voice call or SMS text messages.

Did this help?

Thanks for checking out this tutorial! If you have any feedback to share with us, we'd love to hear it. Connect with us on Twitter and let us know what you build!