
A small voice application is a set of decisions: what to play, what input to accept, where to go next and when to end. Build and test those decisions locally before attaching a telephone number. This exercise creates a two-choice menu using Twilio's documented TwiML format and Python's standard library.
The downloadable lab requires Python 3.10 or later and a terminal. It runs on your own computer, generates XML and accepts local HTTP requests. It makes no telephone calls, needs no account or credentials, and does not synthesize or play speech. Its tests cover application logic and simulated events; carrier connectivity and real audio require a separate deployment test.
Know which component does what
Swipe horizontally, or focus the table and use the arrow keys.
| Component | Responsibility | This exercise |
|---|---|---|
| Voice service | Handles the telephone connection and requests call instructions | Represented by local test requests |
| Application server | Chooses the next instructions from input and application state | Python code returns TwiML XML |
| Endpoint and media | Carry the caller's speech and keypad interaction | No SIP endpoint or audio stream is created |
| Event store | Keeps status evidence and handles repeated or delayed notifications | A small in-memory reducer consumes fictional fixtures |
This is an HTTP application interface. For configuring SIP clients and a PBX, use the separate Asterisk learning lab. Keeping these boundaries clear makes a failed test easier to locate.
Define every branch first
The fictional workshop menu accepts one keypad digit: 1 for hours or 2 for directions. Both choices return a short message and end the interaction. An invalid digit gets one retry; a second invalid digit ends it. Silence ends with an explicit message. The example avoids collecting personal information or dialing another number.
Twilio's Gather documentation defines keypad collection, the action URL, timeout and empty-input behavior. The lab sets input="dtmf", numDigits="1", timeout="5" and actionOnEmptyResult="true". That last setting sends an empty result to the menu handler, so the handler owns the silence branch. The five-second value belongs to this exercise and should be chosen deliberately for a real caller population.
Run the local exercise
- Download and extract the voice application lab into a private folder. Read
README.txt, then open a terminal in that folder. - Run
python -m unittest -v. The suite checks valid choices, silence, both invalid-input attempts, XML structure, local HTTP responses and repeated/out-of-order status fixtures. On Windows, usepy -3in place ofpythonif that is your installed launcher. - Run
python app.py --digits 1. Read the returned XML: it contains the fictional hours message followed byHangup. Try--digits 2,--digits 9and--digits 9 --attempt 1. With--digits=, the silence branch ends the interaction. - Run
python app.py --serveto start the optional loopback HTTP server. Openhttp://127.0.0.1:8765/voiceto inspect the initial menu XML. The tests show how to POST keypad input to/menu. Stop the server with Ctrl+C.
If Python is unavailable, install a supported runtime through your normal software process. If port 8765 is occupied, stop the earlier lab instance or run the offline commands and tests; the test suite uses a temporary local port. XML displayed in a browser is an instruction document, so silence from the computer is expected.
Make repeated and delayed events harmless
A call's current status and its event history serve different purposes. Twilio's Call resource documentation explains that status-callback sequence numbers describe the order events were fired, while separate HTTP requests may arrive out of order. The lab therefore keys each fictional event by call identifier and sequence number, keeps a history, ignores identical duplicates and chooses the highest sequence for the current view. Conflicting reuse of the same key raises an error.
Fictional event replay: completed at sequence 3 arrives before ringing at sequence 1. Replaying the completion once more leaves two unique historical events and a current state of completed. An arrival-order assignment would incorrectly move the visible state backward to ringing. Run python app.py --events to inspect the sample result.
This reducer is a teaching model for the fixture's event scheme. A deployed application needs durable storage, provider-specific event identities, a policy for retries and conflicts, and coordination between simultaneous requests. A process restart clears this lab's memory.
Prepare a separate deployment review
Python documents http.server as unsuitable for production. Keep this lab on loopback. Before connecting a real service, use a supported application host with HTTPS, verify incoming requests using the provider's supported signature-validation method, and keep credentials in protected configuration. Twilio's webhook security guidance explains validation against the actual request URL and parameters, including details that matter behind a proxy.
Then test with an owned number and consenting participants: each menu choice, silence, invalid input, delayed delivery, application failure and recovery. Verify audible prompts and caller termination on real calls, define a bounded failure route, and record costs and retention requirements. The local suite gives a reproducible starting point; those deployment results establish the operating service.