What callers get
-
A booking in two keys
The first key is the party size, from 1 to 8. The second is the time: 1 for 6 p.m., 2 for 7 p.m., or 3 for 8 p.m.
-
A text for the caller
A
<Message>during the call sends the caller an SMS with the party size and time. -
A text for the restaurant
A second
<Message>tells the staff about the booking, with the caller’s number. -
Checks on what was pressed
An unknown time or a party of zero is turned away, and so is a party of nine.
How the pieces fit
| Piece | Where it lives | What it does |
|---|---|---|
| Main document | voiceml.org, /s/ URL | The number’s voice URL. Asks for the two keys. |
| Reservation document | voiceml.org, /r/ URL | The action of the <Gather>. Reads the two keys, confirms the booking aloud, and sends both text messages. |
| Restaurant phone | Any mobile number | Receives a text for each booking. |
Before you start
- A VoiceML account with a phone number. See VoiceML or start a free trial.
- Messaging on the account and the number. The texts are sent from the number that was called. Without messaging, each text is recorded as failed with error 21609, “Account not enabled for messaging,” and the call carries on. In the US, texts from a local number also need a registered 10DLC campaign.
- A mobile number for the restaurant, to receive the booking texts.
The restaurant number in this document is fictional. It uses the 555-0100 to 555-0199 range, which is set aside for fiction. Replace it with a real mobile number before you publish.
Step 1: Publish the reservation document
Replace the restaurant number with your own, then publish it at voiceml.org. It contains Liquid tags, so it is published to an /r/ URL and rendered on every call with that call’s parameters. Keep the URL exactly as returned, key included. The key is a shared secret, so don’t post the URL publicly.
reserve.xml
<Response>
{% comment %} The first key is the party size, the second the time slot. {% endcomment %}
{% assign party = Digits | slice: 0 | plus: 0 %}
{% assign slot = Digits | slice: 1 %}
{% case slot %}
{% when "1" %}{% assign time = "6 p.m." %}
{% when "2" %}{% assign time = "7 p.m." %}
{% when "3" %}{% assign time = "8 p.m." %}
{% endcase %}
{% if party == 9 %}
<Say>Monk's Café seats parties of up to eight. No table for you!</Say>
<Hangup/>
{% elsif party < 1 or time == blank %}
<Say>Sorry, that is not a reservation we can take. Please call again.</Say>
<Hangup/>
{% else %}
<Say>A table for {{ party }} at {{ time }} tonight. We are sending you a text message to confirm.</Say>
<!-- To the caller: with no to attribute, the message goes to whoever called -->
<Message>Monk's Café: your table for {{ party }} is booked for {{ time }} tonight. Call us if your plans change.</Message>
<!-- To the restaurant, so the staff know the table is taken -->
<Message to="+12125550123">New reservation: {{ party }} at {{ time }} tonight, for {{ From }}.</Message>
<Say>Thank you for calling Monk's Café. Goodbye.</Say>
<Hangup/>
{% endif %}
</Response>
Step 2: Publish the main document and point the number at it
Replace RESERVE_URL with the /r/ URL from step 1 and publish. It contains no Liquid, so it is served from an /s/ URL. Set that URL as the phone number’s voice URL.
main.xml
<Response>
<Gather input="dtmf" numDigits="2" action="RESERVE_URL" method="POST" timeout="8">
<Pause length="1"/>
<Say>Thank you for calling Monk's Café.</Say>
<Pause length="1"/>
<Say>To reserve a table for tonight, press the number of people in your party, from 1 to 8. Then press 1 for 6 p.m., 2 for 7 p.m., or 3 for 8 p.m.</Say>
<Say>For example, for four people at 7 p.m., press 4, then 2.</Say>
</Gather>
<Say>We did not receive a reservation. Goodbye.</Say>
<Hangup/>
</Response>
Step 3: Test it
- Call the number from a mobile phone and press 4, then 2.
- You hear “A table for 4 at 7 p.m. tonight,” and a text with the same details arrives on your phone.
- The restaurant phone gets a text with the booking and your number.
How the confirmation works
The main document’s <Gather numDigits="2"> collects both keys in one go and sends them to the reservation document as Digits, such as 42. Values can’t carry from one voiceml.org document to the next, so taking both keys in one <Gather> is what lets a single document see the whole booking. Liquid splits them apart:
| Liquid | For 42 |
Means |
|---|---|---|
Digits | slice: 0 | plus: 0 | 4 | The party size, as a number so it can be compared |
Digits | slice: 1 | 2 | The time slot, turned into “7 p.m.” by a {% case %} |
Then two <Message> verbs send the texts while the call continues:
- The first has no
to. On an inbound call, a<Message>withouttogoes to the caller, and withoutfromit is sent from the number they called. - The second has
toset to the restaurant. Its text includes{{ From }}, the caller’s number, so the staff can call back.
Neither has an action, so the call carries on to the goodbye whether or not the texts go through. Each send is saved as a message record you can look up over the REST API, and a statusCallback on a <Message> reports whether it was sent or failed.
Where the show turns up
| Where | Reference |
|---|---|
| The greeting | Monk’s Café, the diner from the show. |
| A party of nine | “No table for you!”, after the show’s “No soup for you!” |
Limits of this design
- Nothing checks availability. A voiceml.org document can’t look anything up or remember earlier calls, so every booking is accepted, even for a full room. The restaurant’s text is the only record. Checking tables needs a server.
- Landlines don’t get the text. The confirmation goes to the number the caller rang from; if it can’t receive texts, that message fails and the call carries on.
- The times are fixed in the document. Changing the menu of times means publishing again and pointing the number at the new URL.
- Callers can’t change a booking. The text asks them to call the restaurant instead.
Credits
- Monk’s Café and “No soup for you!” come from Seinfeld (NBC, 1989–1998), created by Larry David and Jerry Seinfeld, and starring Jerry Seinfeld, Julia Louis-Dreyfus, Michael Richards, and Jason Alexander. Tom’s Restaurant, at Broadway and 112th Street in Manhattan, stood in for the diner’s exterior.
- The restaurant’s reservations, times, and phone number are fictional in this example. VoiceTel is not affiliated with the show or its rights holders.
- References: the
<Message>reference, the voiceml.org publishing and integration guide, and messaging support.

