Please see our important announcement.
CallFlow Tutorial: Tony's Gym
In this tutorial, we will use CallFlow to enhance a simple web application and add interactive voice features to it.
We will cover both technical and usability / best practice aspects of application-to-person voice communications. Even if you skim through the pages below and feel comfortable with CallFlow scripts at first glance - which we hope many of you will - we would encourage you to follow the tutorial unless you have previous experience of developing voice applications.
Scenario
Let's imagine that we are doing some work for a local gym - Tony's Fitness Centre. Aside from membership fees, much of Tony's revenue comes from personal training sessions. Popular as these sessions are, he loses a lot of money every week due to customers not showing up, or cancelling late in the day. During busy periods, this can amount to thousands.
Tony's Fitness Centre has a fairly typical web presentation with a fairly typical web app behind it - probably written in something like Ruby on Rails. There is also a database containing customer details - names, addresses, phone numbers, and so on - in other words very useful data that should not fall into the wrong hands.
Tony would like to contact his customers shortly before their appointment is due, ask them to reconfirm their appointment, and in the process provide a gentle reminder of the time and location of the session. He is a man of little patience, and wants quick results. Time to dip into our toolbox and select CallFlow.
And get to work! Tony wouldn't have it any other way.
Setting up
To help you get started, we have pre-recorded a number of audio files. You are of course welcome to record your own.
You will need to make sure you have an application certificate, and have downloaded the Web21C SDK.
Once you've downloaded the Web21C SDK, start with a blank project in your language / environment of choice, and import the SDK libraries. You will need to configure security settings to allow you to use your Web21C certificate. If you're new to the Web21C SDK, you should probably start with one of the demo apps that ship with it and get that working first.
Step 0: CallFlow API whistlestop tour
You will probably already be aware that CallFlow provides a means of driving both static and dynamic voice interactions via its own simple XML-based language. The bulk of this tutorial will focus on this representation, what can be done with it, and how to write 'callflows'.
Before we get to that, let's have a quick look at how we get these XML-based CallFlow scripts to the CallFlow service, and what other ways of interacting with the service we need to be aware of.
CallFlow effectively exposes three services:
- OutboundCallFlow is used to trigger the execution of callflows by passing them to the service, terminate their execution, and retrieve information about how the execution unfolded.
- InboundCallFlow is used to retrieve information about callflows triggered by incoming calls, and manage the mappings between inbound phone numbers (or SIP URIs) and CallFlow scripts.
- CallFlowProvisioning is used to manage audio and script files used by CallFlow.
You may want to take a closer look at the list of operations for each of these. You will probably be accessing CallFlow through the SDK, hence naming may vary slightly depending on which flavour you're using. In the tutorial, we will mention specific operations where appropriate.
Step 1: Outbound call setup
Let's start with a nice, simple greeting. First, provision the audio file called welcome.wav via the putFile operation on the CallFlowProvisioning service, then execute the following callflow via the startCallFlow operation on the OutboundCallFlow service:
<?xml version="1.0" encoding="UTF-8"?> <callflow xmlns="http://sdk.bt.com/callflow/2007/04"> <call id="start" target="tel:xxxxxxxxxx" next="welcome" /> <announcement id="welcome" audio="welcome.wav"/> </callflow>
The target for the call tag should be a telephone number with the correct international number prefix (e.g. tel:44 for UK).
Run this script by passing it to the CallFlow service. Your phone should ring, and you should hear a friendly greeting from the man himself. The greeting tells the callee who is calling and why - very obvious, but also very important.
Step 2: Dynamic announcements
Let us now tell the callee when their next appointment is scheduled. To do this, we will chain together a few announcements. Firstly, add the 'next' attribute to the 'welcome' announcement to tell CallFlow to perform another action when the announcement completes, like this:
<announcement id="welcome" audio="welcome.wav" next="next_appt"/>
Next, let's tell the callee that their next appointment is scheduled for 6pm tomorrow:
<announcement id="next_appt" audio="next_appt.wav" next="time"/> <announcement id="time" audio="six.wav" next="am_pm"/> <announcement id="am_pm" audio="pm.wav" next="today_tomorrow"/> <announcement id="today_tomorrow" audio="tomorrow.wav"/>
How do we know it was 6pm tomorrow? Well, typically we would query a database and replace the audio attribute filenames with the relevant values, before passing the script to the CallFlow service.
Step 3: User input
Let us now ask the callee to confirm their appointment - we will do this by adding a 'prompt' step to request and collect touchtone digit input, not forgetting to point the "today_tomorrow" attribute at the new step:
<announcement id="today_tomorrow" audio="tomorrow.wav" next="confirm"/> <prompt id="confirm" audio="confirm.wav"> <input pattern="1" next="confirmed"/> <input pattern="2" next="declined"/> </prompt> <announcement id="confirmed" audio="confirmed.wav"/> <announcement id="declined" audio="declined.wav"/>
Step 4: Error handling
Now let's be considerate and kind to our callee. Let's give them love and kindness if they get an option wrong, or if they are so spooked by our call that they do not press a digit.
We will make use of the pattern matching abilities of CallFlow prompts to detect both scenarios and handle them separately. Let's extend the prompt tag as follows:
<prompt id="confirm" audio="confirm.wav"> <input pattern="1" next="confirmed"/> <input pattern="2" next="declined"/> <input pattern=".+" next="invalid_option"/> <default next="confirm"/> </prompt> <announcement id="invalid_option" audio="invalid_option.wav" next="confirm"/>
The default element under prompt is effectively a 'catch-all' clause - if you are familiar with the 'case' construct in most programming languages, this will be familiar to you too.
Step 5: Who are we talking to?
OK - so we can prompt our callee for confirmation and deal with invalid selections. Everything is going just fine.
Or is it? What if our callee's phone is switched off and our call is answered by voicemail? In the above scenario, we would loop the prompt again and again until voicemail recording times out, or CallFlow deems that there is a looping construct that doesn't end. Either way, our poor callee would get a voicemail with a repeated prompt, which does not make for happy callees. What to do?
And so we come to an important best practice for outbound calls: figure out whether you have a person or a machine at the other end of the phone. To do this, we will add a short prompt right at the beginning of our call, and invite the callee to press a key to accept the call. The idea is that a real person will be able to do this, and a machine will not. Unless it's a very smart machine - which voicemail certainly isn't. Or unless our callee has an antique phone.
So, change the 'call' tag at the beginning to point to a new prompt, as follows:
<call id="start" target="tel:xxxxxxxxxx" next="accept_call" /> <prompt id="accept_call" audio="accept_call.wav"> <input pattern="." next="welcome"/> </prompt>
You may have noticed that we are being nice and accepting any keypress. We're just looking for signs of life on the other side. Note also what the prompt is asking for. We gave the callee clear, specific instructions ('press 1') rather than something vague or potentially ambiguous ('press any key'). We asked them to tell us when they are ready to proceed, rather than whether or not they would like to 'accept the call' - the latter may carry connotations relating to accepting a chargeable call. You may also have noticed that we inserted about a second of silence at the beginning of 'accept_call.wav' - this gives the callee the opportunity to pick up the phone and say 'hello'.
When designing your own voice dialogs, think carefully about these kinds of issues. User experience is everything with voice. Unlike many other vendors, we expressly do not believe that IVR should exclusively be left to the pros - but a little thought can go a long, long way.
Step 6: Application notifications
Back to the plot. So we can get the user to tell us whether or not they will be coming - but how can we get this information back to our application, so that we know who is coming and who is not?
Well, one way would be to poll CallFlow and ask it for call information at regular intervals - we could do this using the getCallFlowInformation operation of the OutboundCallFlow service until the call is completed. Not exactly elegant but it would work. And if you find yourself at work, behind a firewall, or the thought of opening up your web server to the 'Wild Wild Web' gives you the creeps, this may be the only way. Simply keep invoking getCallFlowInformation, passing the call ID returned when initiating the call, until the callflow has completed.
But if you can receive HTTP POSTs, you can do better - you can tell CallFlow to notify you of the outcome. Check out the 'notification' tag:
<notification id="confirmed"> <resource uri="http://yoursite.com/notify" method="POST"> <parameter name="callee" value="${start.target}"/> <parameter name="status" value="confirmed"/> </resource> </notification>
Effectively we give CallFlow a URI to POST to, and some parameters. Here we use CallFlow variables to get access to the callee's number or SIP URI. Note that we can also access the actual DTMF digit selected via ${confirm.digits}. Now if you are a die-hard RESTafarian, you'll be pleased to know that those variables work just as well in URIs - we could have done something like:
<resource uri="http://yoursite.com/notify/confirm/${start.target}/" method="POST"/>
What about security? What is to stop 'Knuckle Duster' George, who fell out with Tony over an ambiguous, unpaid bet some years ago, from furiously POSTing to our HTTP server with bogus customer details?
A secure solution starts with sending notifications over HTTPS. We would then insert a unique identifier with every call we place, and have this reflected back to us in the POST from CallFlow. Because the channel between our application and the CallFlow service is secure, George won't be able to intercept the identifier. Nor will he be able to intercept the POSTs because of SSL encryption. For a head start on a secure setup of this kind with Apache Tomcat and your Web21C SDK cert, see our Howto article on configuring Tomcat to allow SSL requests.
Here is the notification parameter with the identifier parameter added:
<notification id="confirmed"> <resource uri="https://yoursite.com/notify" method="POST"> <parameter name="callee" value="${start.target}"/> <parameter name="status" value="confirmed"/> <parameter name="id" value="80a9af5c22ad1ee9932"/> </resource> </notification>
A simpler solution that may be adequate for your needs would be to use a combination of HTTP and the identifier. Because the identifier isn't sent over an unencrypted channel until the notification goes out, we could generate a unique identifier for every outgoing call we place, and disable that identifier once we get the notification. This would only work for outbound callflows, as inbound ones - where the callflow script needs to be preprovisioned - would be open to replay attacks.
So to finish off, let's flip back to HTTP for the purpose of this tutorial (HTTPS is great, but does hurt), and add a 'next' attribute to our notification, noting that we've renamed our 'confirmed' and 'declined' announcements, which now happen after successful notifications:
<notification id="confirmed" next="confirmed_thank_you" > <resource uri="http://yoursite.com/notify" method="POST"> <parameter name="callee" value="${start.target}"/> <parameter name="status" value="confirmed"/> <parameter name="id" value="80a9af5c22ad1ee9932"/> </resource> </notification>
And let's do the same for declined appointments:
<notification id="declined" next="declined_thank_you"> <resource uri="http://yoursite.com/notify" method="POST"> <parameter name="callee" value="${start.target}"/> <parameter name="status" value="declined"/> <parameter name="id" value="80a9af5c22ad1ee9932"/> </resource> </notification> <announcement id="declined_thank_you" audio="declined.wav"/>
Step 7: Talk to a person
Finally, let's add a personal touch and a bit of stickiness - let's offer the callee the opportunity to be connected to our gym. They may have changed their mind, selected the wrong option - or they may want to book further appointments.
<prompt id="connect_prompt" audio="connect_prompt.wav"> <input pattern="1" next="connect_to_gym"/> <default next="thank_you"/> </prompt> <dialog id="connect_to_gym" target="tel:xxxxxxxxxxx" audio="ring.wav" next="thank_you"/> <announcement id="thank_you" audio="thank_you.wav"/>
Note that we have added a ringing sound ('ring.wav') to let the callee know that the call is still active. We also transition to the 'thank you' step once the call with the gym has ended.
Don't forget to add the next="connect_prompt" attribute to connected_thank_you and declined_thank_you announcements, and set the target to your favourite Tony impersonator.
Intermission I: Completed outbound call scenario
Your callflow should now look very similar to this:
<?xml version="1.0" encoding="UTF-8"?> <callflow xmlns="http://sdk.bt.com/callflow/2007/04"> <call id="start" target="tel:xxxxxxx" next="accept_call"/> <prompt id="accept_call" audio="accept_call.wav"> <input pattern="." next="welcome"/> </prompt> <announcement id="welcome" audio="welcome.wav" next="next_appt"/> <announcement id="next_appt" audio="next_appt.wav" next="time"/> <announcement id="time" audio="twelve.wav" next="am_pm"/> <announcement id="am_pm" audio="pm.wav" next="today_tomorrow"/> <announcement id="today_tomorrow" audio="tomorrow.wav" next="confirm"/> <prompt id="confirm" audio="confirm.wav"> <input pattern="1" next="confirmed"/> <input pattern="2" next="declined"/> <input pattern=".+" next="invalid_option"/> <default next="confirm"/> </prompt> <announcement id="invalid_option" audio="invalid_option.wav" next="confirm"/> <notification id="confirmed" next="confirmed_thank_you" > <resource uri="http://mydomain.com/notify" method="POST"> <parameter name="callee" value="${start.target}"/> <parameter name="status" value="confirmed"/> <parameter name="id" value="80a9af5c22ad1ee9932"/> </resource> </notification> <announcement id="confirmed_thank_you" audio="confirmed.wav" next="connect_prompt"/> <notification id="declined" next="declined_thank_you"> <resource uri="http://mydomain.com/notify" method="POST"> <parameter name="callee" value="${start.target}"/> <parameter name="status" value="declined"/> <parameter name="id" value="80a9af5c22ad1ee9932"/> </resource> </notification> <announcement id="declined_thank_you" audio="declined.wav" next="connect_prompt"/> <prompt id="connect_prompt" audio="connect_prompt.wav"> <input pattern="1" next="connect_to_gym"/> <default next="thank_you"/> </prompt> <dialog id="connect_to_gym" audio="ring.wav" target="tel:xxxxxxxxxxxxx" next="thank_you"/> <announcement id="thank_you" audio="thank_you.wav"/> </callflow>
Step 8: Inbound call handling
CallFlow also supports inbound call handling, although we not currently accepting requests for inbound PSTN numbers.
From here, the story unfolds in a predictable manner: we show Tony our dial-out confirmation service and he is very pleased - but he gets more ideas. He would like his punters to be able to call in and verify their appointment.
This presents us with another challenge - but it's one we can certainly rise to.
Each customer already has a membership number and a PIN number. Perhaps we can simply get them to call in, enter these details, and look up their details in our database..
What's interesting here is that we don't know who the customer is until the call is in progress. Since this information drives the decision of whether or not we are to read out the appointment time, we will need to dynamically evaluate the user credentials.
We do this by using the 'subflow' tag and a bit of server-side code in our favourite language - we will stick to the Ruby theme here as that's what Tony's web app uses. A 'subflow' is nothing more than a nested callflow - all we have to do is write a bit of code that will generate the dynamic part of our inbound call handling callflow in response to a HTTP request similar to ones we used for notifications earlier on. As with notifications, HTTPS is also supported.
So - let us first write the call handling callflow - let us greet the callee, prompt them for a membership number and a pin number. All of the following should look familiar by now (don't forget to provision the missing audio files):
<?xml version="1.0" encoding="UTF-8"?> <callflow xmlns="http://sdk.bt.com/callflow/2007/04"> <announcement id="welcome_inbound" audio="welcome_inbound.wav" next="membership_number_prompt"/> <prompt id="membership_number_prompt" audio="membership_number_prompt.wav" maxDigits="6"> <input pattern="\d{6}" next="pin_number_prompt"/> <input pattern=".+" next="incorrect_membership_number"/> <default next="membership_number_prompt"/> </prompt> <announcement id="incorrect_membership_number" audio="incorrect_number.wav" next="membership_number_prompt"/> <prompt id="pin_number_prompt" audio="pin_number_prompt.wav" maxDigits="6"> <input pattern="\d{6}" next="validate_pin"/> <input pattern=".+" next="incorrect_pin_number"/> <default next="pin_number_prompt"/> </prompt> <announcement id="incorrect_pin_number" audio="incorrect_number.wav" next="pin_number_prompt"/> <announcement id="validate_pin" audio="thank_you.wav"/> </callflow>
Note that we've lost the 'call' tag, as we no longer have to dial out. Also note that we've inserted a placeholder step - 'validate_pin'. This is a placeholder in the sense that it doesn't yet actually validate anything - we'll implement that shortly. For now, we just need a next step for a valid pin input pattern.
Let us now provision this script - we do this in the same way as provisioning audio files, using the putFile operation. CallFlow will detect and validate the script, then store it on the server.
One more step - we need to map our inbound number or SIP URI to the script. We can use the inbound API to manage mappings - simply map the tel or SIP URI to the script name.
If you've done this correctly, you will hear the familiar, inimitable voice of the man himself when you dial the number.
Step 9: Dynamic authentication
OK - so let's add a 'subflow'. Let's write a bit of code that will do the following:
- Extract user id and pin parameters from the HTTP request
- Look up user details to figure out whether or not we have a valid number / pin combination (we'll stub this out)
- Return a call flow that either reads out the next appointment time, or tells the caller that they have entered invalid credentials
Let's start by replacing our placeholder 'validate_pin' state:
<subflow id="validate_pin"> <resource uri="http://mysite.com/auth" method="GET"> <parameter name="membership_number" value="${membership_number_prompt.digits}"/> <parameter name="pin_number" value="${pin_number_prompt.digits}"/> </resource> </subflow>
If you followed through the earlier section about notifications, this will look familiar. Again we're making use of CallFlow variables, which allow us to use data available at runtime, such as DTMF input and caller / callee number or SIP URI. The main difference between a subflow and a notification is that with the former we expect the server to return a valid CallFlow script.
Let's cross over to the server side - as mentioned earlier, we'll use Ruby on Rails here, but your favourite language and tool will work just fine.
In Rails, we first add the following route to routes.rb to match the above URI pattern:
map.connect '/auth', :controller => 'auth'
We will now add an 'auth' controller and a implement a default request handler:
class AuthController < ApplicationController
def index
@membership_num = params['membership_number'].to_i
@pin_num = params['pin_number'].to_i
@auth = @membership_num.even? && @pin_num.even?
@time_filename = 'twelve'
@am_pm_filename = 'pm'
@today_tomorrow_filename = 'tomorrow'
render :layout => false
end
end
For the purpose of this tutorial, we've chosen to allow a combination of an even membership number and pin, and to hard-code the appointment time.
Finally, we generate a callflow as a Rails view (index.xml.builder), with the dynamic values from the controller 'plugged in':
xml.instruct!
xml.callflow('xmlns' => 'http://sdk.bt.com/callflow/2007/04') do
if @auth == false
xml.announcement("id" => 'invalid_pin', "audio" => 'incorrect_number.wav', "next" => 'membership_number_prompt')
else
xml.announcement("id" => 'next_appt', "audio" => "next_appt.wav", "next" => "time")
xml.announcement("id" => "time", "audio" => "#{@time_filename}.wav", "next" => 'am_pm')
xml.announcement("id" => "am_pm", "audio" => "#{@am_pm_filename}.wav", "next" => "today_tomorrow")
xml.announcement("id" => "today_tomorrow", "audio" => "#{@today_tomorrow_filename}.wav", "next" => "thank_you")
end
end
And that's it. If you are familiar with the model-view-controller (MVC) pattern, you'll be right at home.
It's worth paying attention to what is going on here in terms of execution flow. Firstly, note that we haven't specified where to start execution of our subflow. By default, execution starts from the first tag in the retrieved callflow. Had we wanted to, we could have specified a 'start' attribute, telling CallFlow to start execution from a specific step in the subflow - in that case, we would have had to ensure that the specified step is always present.
Next, note how we hand execution back to the 'parent' script - once we've read out the appointment time, we specify the next step to be 'thank_you'. We could have equally chosen to keep our subflow self-contained and define the 'thank_you' step within it. Which of these two approaches you should use within your application will depend on how much control you have of each of the scripts, and how much change can be absorbed by both.
You will, no doubt, have a view on how best to generate callflows - in our experience, we have found that templating works very well for simpler callflows, and that more complex ones benefit from some level of automation. The Web21C team maintains an open-source callflow design tool for CallFlow. It's written in C#, and will happily work with the freely available Visual C# Express.
Here are a couple of examples showing dynamically generated scripts - first the 'happy' path, with valid user credentials:
<?xml version="1.0" encoding="UTF-8"?> <callflow xmlns="http://sdk.bt.com/callflow/2007/04"> <announcement audio="next_appt.wav" id="next_appt" next="time"/> <announcement audio="twelve.wav" id="time" next="am_pm"/> <announcement audio="pm.wav" id="am_pm" next="today_tomorrow"/> <announcement audio="today.wav" id="today_tomorrow" next="thank_you"/> </callflow>
... then the 'unhappy' path, sending execution back to the original prompt:
<?xml version="1.0" encoding="UTF-8"?> <callflow xmlns="http://sdk.bt.com/callflow/2007/04"> <announcement audio="incorrect_number.wav" id="invalid_pin" next="membership_number_prompt"/> </callflow>
And here is how the main script inbound should look now:
<?xml version="1.0" encoding="UTF-8"?> <callflow xmlns="http://sdk.bt.com/callflow/2007/04"> <announcement id="welcome_inbound" audio="welcome_inbound.wav" next="membership_number_prompt"/> <prompt id="membership_number_prompt" audio="membership_number_prompt.wav" maxDigits="6"> <input pattern="\d{6}" next="pin_number_prompt"/> <input pattern=".+" next="incorrect_membership_number"/> <default next="membership_number_prompt"/> </prompt> <announcement id="incorrect_membership_number" audio="incorrect_number.wav" next="membership_number_prompt"/> <prompt id="pin_number_prompt" audio="pin_number_prompt.wav" maxDigits="6"> <input pattern="\d{6}" next="validate_pin"/> <input pattern=".+" next="incorrect_pin_number"/> <default next="pin_number_prompt"/> </prompt> <announcement id="incorrect_pin_number" audio="incorrect_number.wav" next="pin_number_prompt"/> <subflow id="validate_pin"> <resource uri="http://mysite.com/auth" method="GET"> <parameter name="membership_number" value="${membership_number_prompt.digits}"/> <parameter name="pin_number" value="${pin_number_prompt.digits}"/> </resource> </subflow> <announcement id="thank_you" audio="thank_you.wav"/> </callflow>
The 'subflow' tag has a number of further attributes that enhance user experience. The 'audio' attribute can be used to specify an announcement to play while subflow retrieval is in progress - typically this will simply be a 'please wait' message - while the onFailure attribute allows us to handle problems gracefully and provide recovery options, such as connecting the callee to an operator.
One last point - this is a tutorial, so we're relaxed about sending out the pin as clear text. Real-life applications that allow us to sleep well at night require something better, and for use cases like this HTTPS is the way to go.
Step 10: Using the caller ID
Since we have each customer's phone number in our database, couldn't we just capture the phone number that initiated the incoming call (in telco-speak this is called the CLI - 'caller line identity'), in preference to requesting membership number entry? Our customers would simply call in, hear the unmistakable 'Tony's gym' greeting, and be prompted to enter their pin.
We would advise caution here, for two reasons. The first reason is the distinction between a person's phone number and a person's identity. Whilst it is easy enough to deal with an unknown CLI, interesting things might happen if one of our customers was to borrow another's phone - although the use of a PIN provides a level of protection, much frustration could result from a customer seemingly having their usual PIN rejected.
The second reason is the increasing ease with which CLIs can be spoofed. Once locked behind the towering walls of Big Telco, the hooks needed to set CLI keep moving up and out. As a rule of thumb, CLIs alone are probably inadequate for anything that would normally require a password.
If you've thought about these issues and decided that it's for you, stick around. Let's see how we could make this work for Tony.
Firstly, let's make sure that we don't ask callers for their membership number if we have a caller id. We do this using the matchCall tag:
<matchCall id="matchFrom" source="from"> <input pattern=".+" next="welcome_inbound_cli"/> <default next="welcome_inbound_nocli"/> </matchCall> <announcement id="welcome_inbound_cli" audio="welcome_inbound.wav" next="membership_number_prompt"/> <announcement id="welcome_inbound_nocli" audio="welcome_inbound.wav" next="pin_number_prompt"/>
Note how we have two announcement tags playing the same announcement, but having different 'next' steps. It would have been more efficient but more laborious to just send the incoming call to the server via a subflow tag - again, both approaches have their place.
All we have left to do is add another parameter to our subflow, and pick it up server-side. Our 'resource' element inside 'subflow' becomes:
<resource uri="http://mydomain.com/auth" method="GET"> <parameter name="caller" value="${caller}"/> <parameter name="membership_number" value="${membership_number_prompt.digits}"/> <parameter name="pin_number" value="${pin_number_prompt.digits}"/> </resource>
And in the above Ruby controller the decision to authenticate now allows for either a caller ID or a membership number:
@auth = (!params['membership_number'].nil? || !params['caller'].nil?) && @pin_num.even? && @membership_num.even?
Step 11: Making it personal
Once we know who is calling us, or who we are calling, we can start to make this automated call a little more personal. We know the name of the person we are in the call with, and using the text to speech feature, we can dynamically read out announcements and prompts. A simple change can make a big difference to Tony's customers, let's review our welcome message.
<announcement id="welcome" audio="welcome.wav" next="next_appt"/>
Using the 'text' element, we can make this personal.
<announcement id="welcome" next="next_appt">
<text>Hello there Mr Smith, this is Tony's Gym calling.</text>
<announcement>
Of course, mixing the text to speech engine with Tony's unique style may sound a little odd, a little usability testing will help there. As you can see, using the text to speech engine is pretty easy, and it can be used with different voices and even different languages, have a look through the API documentation for more details.
Intermission II: Completed inbound call scenario
Let's take stock again - our main inbound script should now look a little something like this:
<?xml version="1.0" encoding="UTF-8"?> <callflow xmlns="http://sdk.bt.com/callflow/2007/04"> <matchCall id="matchFrom" source="from"> <input pattern=".+" next="welcome_inbound_cli"/> <default next="welcome_inbound_nocli"/> </matchCall> <announcement id="welcome_inbound_cli" audio="welcome_inbound.wav" next="pin_number_prompt"/> <announcement id="welcome_inbound_nocli" audio="welcome_inbound.wav" next="membership_number_prompt"/> <prompt id="membership_number_prompt" audio="membership_number_prompt.wav" maxDigits="6"> <input pattern="\d{6}" next="pin_number_prompt"/> <input pattern=".+" next="incorrect_membership_number"/> <default next="membership_number_prompt"/> </prompt> <announcement id="incorrect_membership_number" audio="incorrect_number.wav" next="membership_number_prompt"/> <prompt id="pin_number_prompt" audio="pin_number_prompt.wav" maxDigits="6"> <input pattern="\d{6}" next="validate_pin"/> <input pattern=".+" next="incorrect_pin_number"/> <default next="pin_number_prompt"/> </prompt> <announcement id="incorrect_pin_number" audio="incorrect_number.wav" next="pin_number_prompt"/> <subflow id="validate_pin"> <resource uri="http://mysite.com/auth" method="GET"> <parameter name="caller" value="${caller}"/> <parameter name="membership_number" value="${membership_number_prompt.digits}"/> <parameter name="pin_number" value="${pin_number_prompt.digits}"/> </resource> </subflow> <announcement id="thank_you" audio="thank_you.wav"/> </callflow>
Exercise 1: Alerting the callee
If you're itching to try something out for yourself, here's your chance as we cover another important feature of CallFlow affecting callee experience.
In the outbound call scenario, where we make calls to customers to have them confirm their training sessions, we saw how we could use the 'dialog' tag to connect an ongoing call to the gym. We were able to add the 'audio' attribute to the 'dialog' element and play the customer a ringing tone. But we could certainly improve the in-call experience of gym staff - or Tony himself, who currently just receive a call without any context around appointment confirmation.
As an exercise, see if you can enhance the original script to play a prompt to the party called via the 'dialog' tag, telling them that they have a call from one of the customers, and asking them to accept or reject the call. To get you started, check out the 'init' attribute for the 'dialog' element.
Exercise 2: Recording a message
Another easy enhancement we could make would involve giving the customer the option of leaving a message instead of being connected to Tony's Gym. Check out the 'record' tag, and pay attention to ensuring that you generate unique filenames, and that your app knows about them. Various combinations of subflow and notification elements, and callflowid and timestamp variables will give us what we're after.
Epilogue
Tony is extremely happy, and already has plans to take things further - much further. He would like to provide membership expiry alerts and fee collection, he would like a personalised call handling service for his own use, and he's been telling his many and varied friends all about you. Eventful times are on the horizon.
* * *
Hope you've enjoyed this journey through CallFlow, and are getting a grasp of just how much you can do with it. We've had to omit certain features to keep the length manageable, so we would encourage you to explore further - if you haven't already, start by looking at the tag reference and 'cheat sheet' documents. And keep an eye on and participate in what Web21C are up to - there's plenty more to come!
