CollegePlanning 101 helps high school students and their families navigate college admissions, which means its owner, Stephanie, is on the phone constantly — sales calls with prospective families, advising calls with enrolled students, several other call types besides. She was writing a personalized follow-up by hand after every one. I built the system that drafts that email in her own voice the moment the call ends, and deliberately stops one step short of sending it.
What was actually eating her time
Every call Stephanie takes gets transcribed automatically by Fathom. That part was already solved. What wasn't: turning a transcript into a follow-up email that sounds like her, addressed to the right person, sent from the right context — without her retyping it from scratch every single time. That's a task that's fine in week one and unsustainable once call volume is real.
It's also harder than it looks for one specific reason: Fathom's own webhook doesn't carry the booking contact's identity. When a call ends, the payload has a transcript, a meeting type, and timestamps — no name, no email tied back to the appointment that was actually booked on her GoHighLevel calendar. Any automation built straight off the transcript has an identity problem before it can draft a single word.
Two workflows, because the booking data and the call data live in different systems
A separate workflow — CP101 - GHL Appointment Intake — fires on a GHL webhook the instant an appointment is booked, and upserts the contact's name, email, and appointment details into an n8n data table. A second "reschedule" webhook upserts the same row rather than creating a duplicate when a time slot moves. That table is the only reason the second workflow can resolve who was actually on a call.
When the call-completed webhook fires, the workflow computes the call's time window and matches it against that same data table to recover exactly which booked appointment — and therefore which contact — the call belongs to. Nothing downstream can run until that match succeeds.
The routing step reads the call's meeting_type field to decide whether this is a Sales call or a Student call — each with its own branch, since the right follow-up reads completely differently depending on who's on the other end. The transcript, plus a sample of Stephanie's own past emails, goes to OpenAI to draft a reply that sounds like her rather than a template. The result is created directly as a Gmail draft, addressed using the contact pulled from the data table.
The system could close the loop completely on its own. I built it so it doesn't.
Every piece needed to auto-send was already in place — the draft, the contact, the Gmail credential. I added one deliberate gap instead: the workflow notifies Stephanie that a draft is ready, and she reads it, edits if needed, and sends it herself. A transcript-to-email pipeline can get tone, context, or a sensitive detail from the call wrong in ways that only a human catches before it goes out under her name — and an unreviewed email to a family evaluating a college-admissions service is exactly the kind of mistake that's expensive to make. The automation's job is to remove the blank-page problem, not the final judgment call.
Fathom's docs don't publish the literal webhook payload
Fathom's API documentation describes the /meetings REST object in detail, but doesn't publish the exact JSON shape of the webhook itself — so the integration was built against the documented object first, on the assumption the webhook payload matches it closely. Rather than ship on that assumption, I checked the routing field against live call traffic once calls started coming through: confirming the meeting_type values Fathom actually returns match Stephanie's calendar names in GoHighLevel — all 23 of them — before trusting that field over the title-keyword fallback the Code node falls back to if it's ever blank.
The live canvas
CP101 - Fathom Call Follow-Up Drafts, live in production. The Sales and Student branches run the identical pattern — prepare request, call OpenAI, parse response, create Gmail draft, notify Stephanie — in parallel.
Both workflows are live and published in production. Before the intake workflow existed, I also ran a one-time backfill — paging through all of Stephanie's GoHighLevel calendars to pull every historical appointment into the data table — so the lookup didn't start empty the day the system went live.