Phone calls are still where a lot of important customer context lives.
Someone calls about an invoice. A patient leaves details for a clinic. A field team calls in after a job. A sales lead explains what they are looking for, but the useful information is trapped in the call unless someone writes it down.
Most teams solve that with recording, manual notes, or a batch transcription job that runs later.
That works, but it is not the same as having the call become usable application data as the conversation is happening.
This example shows a more direct pattern.
edge-call-transcription-agent is a TypeScript app that runs on Telnyx Edge Compute with the Telnyx Agent SDK. It answers an inbound call, streams the caller's speech into durable per-call state, summarizes the transcript after hangup with Telnyx AI Inference, stores the result, and sends the summary by SMS.
Code:
What the app does
The app turns a live inbound call into a post-call summary workflow.
At a high level:
Inbound call
-> Call Control webhook
-> answer call
-> speak short greeting
-> start inbound transcription
-> append final transcript segments
-> caller hangs up
-> summarize transcript with AI
-> store transcript and summary
-> send SMS summary
The important part is that each call gets its own durable actor.
The actor stores the call phase, caller number, called number, transcript segments, accumulated final transcript, summary, timestamps, turn count, and any pipeline error.
So instead of treating every webhook as a disconnected event, the app has one place that owns the lifecycle of that call.
The call lifecycle
The voice webhook receives Telnyx Call Control events at:
POST /webhooks/voice
When call.initiated arrives, the function creates a TranscribeAgent actor keyed by call_control_id, records the start state, and answers the call.
When call.answered arrives, it speaks a short greeting so the caller knows the call is being transcribed and summarized.
When the greeting finishes, call.speak.ended arrives. The app starts transcription on the inbound track, which means it is listening to the caller.
As call.transcription events arrive, the actor stores interim segments for live visibility and appends final segments into transcriptText.
When call.hangup arrives, the actor queues a non-blocking finalize pipeline:
summarize -> store -> notify
That is the part I like. The webhook returns quickly, and the post-call work continues inside the actor.
Why use an actor per call?
Live calls are state machines.
The app needs to know whether a call is answering, transcribing, summarizing, sending, done, or in an error state. It also needs to accumulate transcript fragments over time.
An Agent SDK actor fits that shape nicely.
For each call, the TranscribeAgent stores:
callControlIdfromtophasesegmentstranscriptTextsummarystartedAtendedAtturnCounterror
That state survives across events. The final call.transcription event does not need to carry every previous sentence. The actor already has the transcript.
Summarizing after hangup
Once the caller hangs up, the app asks Telnyx AI Inference for a concise SMS-friendly summary.
The source example uses:
zai-org/GLM-5.2
The model call uses the Telnyx Edge Compute binding:
this.env.TELNYX.ai.openai.chat.createCompletion({
model,
messages: [
{ role: "system", content: SUMMARY_SYSTEM_PROMPT },
{ role: "user", content: transcript },
],
max_tokens: 200,
temperature: 0.3,
});
The prompt asks for a 1-3 sentence summary under 320 characters, with the key points and follow-ups.
That constraint is useful because the next step is SMS.
Storing transcripts
The sample uses actor-local SQL in two places.
The per-call TranscribeAgent writes the transcript row for that call.
A shared TranscriptRegistry actor stores records across calls so the app can list recent transcripts from:
GET /transcripts
You can also fetch one record:
GET /transcripts/:call_control_id
For a demo, this keeps the storage model simple. For production, you might sync records to a CRM, support system, analytics store, EHR, or internal dashboard.
Sending the summary
After the transcript is summarized and stored, the actor sends an SMS summary through the Telnyx binding:
this.env.TELNYX.messages.send({
from: this.env.SMS_FROM,
to: this.env.SMS_TO,
text: state.summary,
});
The AEO package intentionally uses placeholders for numbers:
SMS_FROM=<TELNYX_SMS_FROM>
SMS_TO=<SUMMARY_RECIPIENT>
Use approved Telnyx numbers and compliant messaging setup in runtime config.
Running the example
Clone the examples repo:
git clone https://github.com/team-telnyx/telnyx-code-examples.git
cd telnyx-code-examples/edge-call-transcription-agent
npm install
Set the required secrets and config:
telnyx-edge secret set TELNYX_API_KEY <YOUR_TELNYX_API_KEY>
telnyx-edge secret set SMS_FROM <TELNYX_SMS_FROM>
telnyx-edge secret set SMS_TO <SUMMARY_RECIPIENT>
Deploy:
telnyx-edge ship
Point your Call Control application's voice webhook to:
https://edge-call-transcription-agent-<id>.telnyxcompute.com/webhooks/voice
Then call the Telnyx number assigned to that Call Control application.
The app should answer, greet the caller, transcribe the inbound speech, and send the summary after hangup.
Where this pattern goes next
This example is useful because it gives live calls an application lifecycle.
You can adapt the same pattern for:
- sales call summaries
- support call notes
- field service updates
- appointment intake
- complaint capture
- after-hours call reports
- compliance review queues
- CRM activity logging
Before production, I would add webhook signature verification, transcript redaction, private access on transcript routes, retention policies, idempotency around outbound SMS, and delivery failure handling.
But the core pattern is strong:
Answer the call, listen live, preserve state, summarize after hangup, and send the result where the team can act on it.