A contact form captures details, but it does not create a conversation. Someone still has to read the submission, contact the prospect, ask the right questions, write down the answers, and arrange a next step.
Sofia is a working Constant Labs prototype for handling that sequence as one bounded workflow. A visitor asks for a call, Sofia phones them, discusses the project in their preferred language, and turns the conversation into a structured recap. The website then shows the result, while a follow-up email provides the summary and a direct path to book a consultation.
The interesting part was not making a phone ring. It was making several asynchronous systems behave like one understandable product.
The operational problem
Lead intake often breaks down between systems. A website stores one version of the contact. A call contains the useful context. Notes live somewhere else. A calendar link is sent later, if somebody remembers. Every handoff introduces delay and loses information.
For Sofia, the intended outcome was specific:
- let a visitor request an immediate call;
- collect enough context to understand the project;
- preserve the call outcome as structured data;
- show useful feedback on the website;
- send one clear follow-up containing the recap and booking option.
This is deliberately narrower than a general sales agent. Sofia does not negotiate contracts or decide whether a project should be accepted. The agent collects context and makes the next human conversation easier.
Designing the workflow around states
Voice calls and post-call analysis do not complete in a single HTTP request. The browser starts the process, but the useful result arrives later through provider webhooks.
We modelled the user-facing lifecycle explicitly:
- the call is being initiated;
- the phone is ringing;
- the conversation is active;
- the call has ended and the recap is being prepared;
- the structured recap is available;
- the call failed or was not answered.
That distinction matters. A completed phone call does not mean the conversation summary already exists. Showing a generic completion screen too early made the interface look finished while the most useful data was still on its way. Sofia now shows a dedicated preparation state between call end and recap delivery.
The failure path is equally intentional. If the call cannot connect or the person does not answer, the interface offers the Cal.com booking option. After a successful call, the recap becomes the primary result instead of presenting two competing booking sections.
The architecture
The public experience is an Astro frontend backed by a Python service. The workflow connects four external capabilities:
- Telnyx AI Agents handles the outbound voice conversation and emits call and conversation-insight events.
- A state and webhook layer correlates provider identifiers with the local call, normalizes events, and exposes the current result to the browser.
- Cal.com provides the canonical consultation booking page.
- SendGrid sends the post-call email with the conversation summary and booking link.
The frontend starts the call and polls a local state endpoint for progress. Telnyx webhooks update that state independently. When conversation insights arrive, the backend extracts the recap, associates it with the original call, and makes it available to both the interface and the post-call workflow.
The model is only one component. Correlation, validation, idempotency, user feedback, and recovery are ordinary application responsibilities surrounding it.
The webhook lesson
The most instructive failure happened after the call itself was already working. Call-start and call-end events arrived correctly, but the structured summary did not appear where the application expected it.
The problem crossed several boundaries: provider event configuration, conversation identifiers, payload shape, local call identifiers, and the endpoint responsible for storing the recap. A 400 response at the recap endpoint was a symptom, not a useful diagnosis on its own.
We isolated the flow by recording each boundary:
- Was the conversation-insight event generated?
- Did it reach the public webhook?
- Which provider identifier did it contain?
- Could that identifier resolve the original local call?
- Was the recap extracted from the actual payload shape?
- Did the state endpoint return the stored result?
That process exposed the missing handoff between the generic Telnyx event route and the Sofia-specific recap path. The fix normalized the insight payload, correlated it through the call SID, and forwarded the recap into the post-call workflow.
This is a reusable engineering lesson for AI integrations: trace the complete event path before changing SDKs or rewriting working components. Provider SDKs can improve ergonomics, but they do not replace correct webhook configuration, identifier correlation, or state design.
Making retries safe
Webhook providers retry. Networks time out. Users refresh pages. A robust workflow has to assume that the same event may be processed more than once.
Sofia records calls and side effects with stable identifiers. Email and booking-related actions use idempotency keys so a repeated recap does not silently create duplicate follow-ups. Unknown call identifiers are rejected instead of being attached to whichever call happens to be active.
These choices are not visible in the happy-path demo, but they are what make an automation suitable for real operations.
What the visitor receives
When the complete path succeeds, the visitor gets:
- an immediate voice conversation in English, Dutch, or Italian;
- visible call progress rather than a frozen form;
- a structured recap containing the available lead and project context;
- a post-call email with the summary;
- a Cal.com link for choosing the consultation time.
When the call fails or is not answered, the experience falls back to direct scheduling. The workflow preserves a useful next step without pretending that a conversation occurred.
We validated this path with a controlled end-to-end call: the conversation completed, Telnyx generated structured insights, the recap appeared in the interface, and the post-call email arrived with the summary and booking link.
Sofia is an internal product and demonstration, so we do not attach invented conversion percentages or time-saving claims to it. Its value as a case study is architectural: it proves the complete workflow and exposes the operational details that a simple model demo leaves out.
What we would adapt for a client
The same pattern can support lead qualification, appointment intake, customer-service triage, or a structured follow-up after an inbound request. The implementation should change with the business:
- questions and qualification rules;
- languages, voice, and tone;
- CRM and internal-system integrations;
- data retention and consent requirements;
- escalation rules and human review;
- booking, email, or ticketing actions;
- reporting and operational ownership.
The right first version handles one well-defined job. It should collect only the data it needs, make its limits visible, and leave consequential decisions with the appropriate person.
The practical takeaway
Building Sofia reinforced a simple point: an AI agent becomes useful when the entire workflow is engineered, not when the model produces an impressive response.
The voice conversation matters. So do the webhook routes, state transitions, identifiers, retry behavior, loading feedback, fallback path, and email that arrives after the call. Together, those pieces turn an AI capability into a service someone can actually use.
You can try Sofia or review our custom AI agent development service. For the governance and runtime principles behind our JVM work, read why I built TramAI and the deeper Java production AI agents guide.
