Voice AI

I Replaced My Voicemail With an AI That Texts Me Summaries (in 118 Lines of Python)

Every voicemail system does the same thing: record audio, hope the recipient checks it. Most never do. Here's how I built a voicemail that calls an LLM, classifies the call (urgent? spam? meeting confirmation?), and texts me a one-line digest — in 118 lines of Python using the Telnyx API.

The TL;DR: Use Telnyx Call Control to answer the call, TTS a greeting, start recording and real-time transcription in parallel. On hangup, send the transcript to Telnyx AI Inference with a triage prompt, parse the JSON response, and SMS the result to yourself. Total time to working demo: ~30 minutes.

The canonical code example is in the Telnyx code examples repo:

https://github.com/team-telnyx/telnyx-code-examples/tree/main/ai-voicemail-transcription-forwarding-python

What the finished product looks like

When someone calls my Telnyx number and hangs up, I get a text like this within 3 seconds:

[URGENT] Voicemail from +1••• ••• 1234
Priority: urgent | Category: support
Production server is down, need callback ASAP
Callback requested.

For routine messages, the prefix changes to [VM] and I don't get pinged about it.

The full architecture

Inbound call
    │
    ▼
Telnyx Call Control App
    │
    ├──► answer() + speak("Hi, what is the purpose of your call?")
    │
    ├──► start_recording(transcription=True)
    │       │
    │       └──► webhook: call.recording.transcription.saved (full transcript)
    │
    └──► on call.hangup webhook:
            │
            ▼
        POST /v2/ai/chat/completions  ← Telnyx AI Inference
            │
            ▼
        Parse JSON (priority, summary, callback_needed, ...)
            │
            ▼
        POST /v2/messages             ← Telnyx Messaging
            │
            └──► SMS to my phone with priority emoji + summary

The carrier does the speech-to-text for us. The LLM does the triage. We just glue the pieces together.

How the state machine works

Telnyx Call Control is bidirectional. The platform sends you webhook events describing what happened, and your response tells it what to do next. Five events drive this app:

EventWhenWhat the app does
call.initiatedInbound call ringinganswer(), stash caller number
call.answeredCall connectedTTS the greeting
call.speak.endedGreeting finished playingstart_recording() with transcription=True
call.recording.transcription.savedRecording + transcription completeStore the transcription_text
call.hangupCaller hung upClassify transcript, send SMS

The transcript arrives as a call.recording.transcription.saved webhook AFTER the hangup, with the full text in the transcription_text field. The app waits for this event before processing.

The triage prompt

This is the entire prompt — it's deliberately compact:

system = (
    "You are a voicemail triage assistant. Analyze the voicemail and return ONLY a JSON object "
    "with these exact keys: priority (one of: urgent, normal, spam), summary (one sentence, <= 25 words), "
    "callback_needed (boolean), category (one of: sales, support, personal, automated, other), "
    "caller_sentiment (one of: positive, neutral, negative). No prose, no markdown, JSON only."
)

The trick is "Return ONLY JSON" combined with strict schema in the same sentence. Models are surprisingly good at honoring this if you also keep temperature low (0.2 here).

Testing without a phone call

You can simulate the entire webhook flow with curl — useful for debugging or verifying the AI classification:

# Call comes in
curl -X POST http://localhost:5000/webhooks/voice \
  -H 'Content-Type: application/json' \
  -d '{"data":{"event_type":"call.initiated","payload":{"call_control_id":"v3:test","direction":"incoming","from":"+12125551234"}}}'

# Transcript arrives after recording
curl -X POST http://localhost:5000/webhooks/voice \
  -H 'Content-Type: application/json' \
  -d '{"data":{"event_type":"call.recording.transcription.saved","payload":{"call_control_id":"v3:test","transcription_text":"hey production server is down need callback asap"}}}'

# Caller hangs up
curl -X POST http://localhost:5000/webhooks/voice \
  -H 'Content-Type: application/json' \
  -d '{"data":{"event_type":"call.hangup","payload":{"call_control_id":"v3:test"}}}'

Then check the result:

curl http://localhost:5000/voicemails | python3 -m json.tool

Going further

This is a working demo. To make it production-ready:

  • Persist the voicemails — swap the in-memory list for a Postgres table or Redis hash.
  • Save the recordings — Telnyx emits a call.recording.saved webhook with an MP3 URL. Subscribe to it and push to Telnyx Cloud Storage or S3.
  • Multiple recipients — add a per-caller routing table so VIPs go to a different number.
  • Sentiment-aware escalation — when priority is urgent AND sentiment is negative, fire a second SMS to a backup number.

Try it yourself

Frequently Asked Questions

What does this guide cover?
Every voicemail system does the same thing: record audio, hope the recipient checks it. Most never do. Here's how I built a voicemail that calls an LLM, classifies the call (urgent? spam? meeting confirmation?), and texts me a one-line digest — in 11
What is what the finished product looks like?
When someone calls my Telnyx number and hangs up, I get a text like this within 3 seconds:
What is how the state machine works?
Telnyx Call Control is bidirectional. The platform sends you webhook events describing what happened, and your response tells it what to do next. Five events drive this app:
What is the triage prompt?
This is the entire prompt — it's deliberately compact:

Resources

  • No resources listed.

Ready to build with low-latency voice AI?

Join developers building the future of real-time conversations