What callers get
-
Triage first
Press 1 for an active haunting, 2 for a containment unit emergency, or 3 to report a sighting. Only the first two wake anyone up.
-
An on-call chain
<Dial sequential="true">rings Peter, then Ray, then Winston, 15 seconds each, and connects the first one who answers. -
A chain per emergency
Containment emergencies have their own call order: Egon first, then Ray.
-
A way out when nobody answers
After the chain, the caller can ring the team again or leave a report, and a caller whose call was answered just hangs up.
-
Routine reports, recorded
Sightings and inspection requests are taken as a recording of up to two minutes, so nobody is called for them.
How the pieces fit
| Piece | Where it lives | What it does |
|---|---|---|
| Main document | voiceml.org, /r/ URL | The number’s voice URL. Plays the menu, rings the on-call chains, and takes reports. |
| Goodbye document | voiceml.org, /s/ URL | The action of the <Record>. Confirms the report and hangs up. |
| On-call phones | Any phone numbers | The mobile numbers of the people on call. Each one the chain rings is an outbound call on your account. |
Before you start
- A VoiceML account with a phone number. See VoiceML or start a free trial.
- The phone numbers of the people on call, in E.164 format, and the order to call them in for each kind of emergency.
The numbers in these documents are fictional. They use the 555-0100 to 555-0199 range, which is set aside for fiction. Replace every <Number> with a real on-call number before you publish.
Step 1: Publish the goodbye document
Every <Record> needs an action; without one, the runtime requests the current document again and the caller hears the prompt and records a second time. This document is that action.
goodbye.xml
<Response>
<Say>Thank you. Your report has been logged with the Ghostbusters. If things get worse, call back and press 1.</Say>
<Pause length="1"/>
<Say>Until then, whatever you do, don't cross the streams. Goodbye.</Say>
<Hangup/>
</Response>
Paste it at voiceml.org, validate it, and publish. It contains no Liquid, so it is served from an /s/ URL. Keep the URL exactly as returned, key included. The key is a shared secret, so don’t post the URL publicly.
Step 2: Publish the main document
Replace GOODBYE_URL with the URL from step 1, and replace the on-call numbers with your own. Then publish it, and set the /r/ URL it returns as the phone number’s voice URL.
main.xml
<Response>
{% if Digits == blank %}
<!-- Triage menu -->
<Gather input="dtmf" numDigits="1" timeout="5" method="POST">
<Pause length="1"/>
<Say>Ghostbusters paranormal response. We're ready to believe you.</Say>
<Pause length="1"/>
<Say>For an active haunting, press 1.</Say>
<Say>For a containment unit emergency, press 2.</Say>
<Say>To report a sighting or request an inspection, press 3.</Say>
</Gather>
<Say>We did not receive a selection. Goodbye.</Say>
<Hangup/>
<!-- Press 1: active haunting. The on-call chain rings one Ghostbuster at a time. -->
{% elsif Digits == "1" %}
<Say>Dispatching the on-call Ghostbuster. Please stay on the line.</Say>
<Dial sequential="true" timeout="15" ringTone="us">
<Number>+12125550101</Number> <!-- Peter Venkman -->
<Number>+12125550102</Number> <!-- Ray Stantz -->
<Number>+12125550104</Number> <!-- Winston Zeddemore -->
</Dial>
<Gather input="dtmf" numDigits="1" timeout="5" method="POST">
<Say>If no one answered, press 1 to try the on-call team again, or press 3 to leave a report. Otherwise, you may hang up.</Say>
</Gather>
<Say>Goodbye.</Say>
<Hangup/>
<!-- Press 2: containment unit emergency. A different chain, with Egon first. -->
{% elsif Digits == "2" %}
<Say>This is a containment emergency. Do not switch off the containment unit. Connecting you to Egon.</Say>
<Dial sequential="true" timeout="15" ringTone="us">
<Number>+12125550103</Number> <!-- Egon Spengler -->
<Number>+12125550102</Number> <!-- Ray Stantz -->
</Dial>
<Gather input="dtmf" numDigits="1" timeout="5" method="POST">
<Say>If no one answered, press 2 to try the containment team again, or press 3 to leave a report. Otherwise, you may hang up.</Say>
</Gather>
<Say>Goodbye.</Say>
<Hangup/>
<!-- Press 3: non-urgent report, taken as a recording -->
{% elsif Digits == "3" %}
<Say>Describe what you saw, where you saw it, and a number where we can reach you. If it is a giant marshmallow man, please say so first. Press the pound key when you are finished.</Say>
<Record action="GOODBYE_URL" method="POST" maxLength="120" timeout="5" finishOnKey="#" playBeep="true" trim="trim-silence"/>
{% else %}
<Gather input="dtmf" numDigits="1" timeout="5" method="POST">
<Say>That is not a valid option. Please press 1, 2, or 3.</Say>
</Gather>
<Say>We did not receive a selection. Goodbye.</Say>
<Hangup/>
{% endif %}
</Response>
Step 3: Test it
- Call the number and press 1. The first on-call phone rings; don’t answer it.
- After 15 seconds the second phone rings. Answer it, and you are connected to the caller.
- Call again, press 1, and let every phone ring out. You hear the offer to try again or leave a report; press 3 and leave one.
How the escalation works
The menu’s <Gather> has no action, so the key the caller presses comes back to the same document as Digits and picks a branch. Branches 1 and 2 each hold a <Dial> with several <Number> nouns:
| Attribute | Value | Effect |
|---|---|---|
sequential | true | Rings the numbers one at a time, in document order, until one answers. Without it, they all ring at once and the first to answer wins. |
timeout | 15 | How long each number rings before the next one is tried. |
ringTone | us | The caller hears a US ringback tone while the chain rings. |
The on-call phone shows the caller’s own number, because a <Dial> without a callerId passes it through.
A <Dial> without an action continues to the next verb whether the call was answered or not, and a voiceml.org document can’t read the dial result to tell the two apart. So instead of guessing, the line asks: “If no one answered, press 1 to try the on-call team again, or press 3 to leave a report. Otherwise, you may hang up.” That <Gather> has no action either, so the key the caller presses runs the matching branch of the menu again.
Where the movie turns up
| Where | Reference |
|---|---|
| The greeting | “We’re ready to believe you,” the film’s tagline. |
| The on-call chain | Peter Venkman, Ray Stantz, and Winston Zeddemore. |
| Option 2 | The containment unit, and Egon Spengler at the top of its call order. Don’t switch it off. |
| Option 3 | A giant marshmallow man. |
| The goodbye | “Don’t cross the streams.” |
Limits of this design
- The caller decides what happens after the chain. To retry or take a report automatically when nobody answered, point the
<Dial>’sactionat your own server, which receivesDialCallStatusand can answer with VoiceML of its own. - A phone’s voicemail can end the chain. If an on-call phone sends calls to voicemail before
timeoutruns out, the voicemail answers and the chain stops there. Keeptimeoutshorter than your team’s phones take to go to voicemail. - The rotation is fixed in the document. Published documents can’t be edited, so changing who is on call means publishing a new document and pointing the number at its URL. To pick the chain by day of the week, combine this with the time checks in the business-hours example.
- Reports stay on your account. To have each report emailed, add the VoiceML Dashboard’s voicemail callback to the
<Record>, as the PBX example does.
Credits
- The Ghostbusters, the containment unit, and the marshmallow man come from Ghostbusters (1984), directed by Ivan Reitman, written by Dan Aykroyd and Harold Ramis, and distributed by Columbia Pictures. Bill Murray plays Peter Venkman, Dan Aykroyd plays Ray Stantz, Harold Ramis plays Egon Spengler, and Ernie Hudson plays Winston Zeddemore.
- The line, its menu, and its phone numbers are fictional in this example. VoiceTel is not affiliated with the film or its rights holders.
- References: the
<Dial>reference, the VoiceML language reference, and the voiceml.org publishing and integration guide.

