📢
← Back to Blog

I Built A Claude Code Automation That Turns Every Client Call Into Todoist Tasks — Here's The Build Log

John Aspinall · · 9 min read

I run roughly 18-22 calls a week across four ventures. Discovery calls, client check-ins, cohort sessions, podcast recordings, internal team. Every call generates 2-7 action items. For about 10 months I tracked those manually — I'd watch the Fathom recording back, copy the "Next Steps" into Todoist, attribute who-owes-what, paste the timestamp link. About 8-12 minutes per call. Call it 3 hours a week of pure transcription work.

Three weeks ago I burned a weekend building a Claude Code automation that does the whole thing while I sleep. Here's the build log — the architecture, the actual prompt, what broke, what I had to fix, and what it cost me.

The Problem (And What It Was Costing Me)

The manual version had three failure modes that mattered more than the time cost:

1. I'd forget. Calls on Tuesday wouldn't make it into Todoist until Thursday or Friday. By then I'd already missed two follow-up windows.

2. I'd cherry-pick. "I'll do the big ones, the small ones aren't important." Half the time the small ones were actually the unblock — a 2-min Loom I owed someone that was holding their week up.

3. Attribution would slip. A Fathom "Next Step" reads "send the audit doc to the team." Who sends it? Who's the team? Without context the task is useless. By the time I parsed it 3 days later I'd lost the context.

Cost math:

  • 20 calls/week × 10 min = 200 min = 3.3 hr/week manual transcription
  • ~15% of action items lost or delayed → roughly $4-8K/mo in client friction (slow follow-ups, missed renewals, dropped opportunities)
  • At an honest valuation of my time ($300/hr opportunity cost), the transcription itself was ~$50K/yr of drag

That's a real-money problem, not a productivity-porn problem.

The Build

I wanted three properties: idempotent (running it twice doesn't create duplicate tasks), stateful (it knows what it's already processed), and trustworthy (when something breaks, state doesn't advance and we re-cover the window on the next run).

The stack:

  • Fathom MCP — Fathom's official MCP server, gives me programmatic access to my meeting list, summaries, transcripts, and next-step deep links
  • Claude Code in headless modeclaude CLI run as a subprocess from Python, used only to talk to the Fathom MCP and return a strict JSON envelope
  • Python orchestrator — owns the state file, validates the envelope, writes to Todoist
  • Todoist REST API — for creating tasks (no MCP needed, the API is fine)
  • macOS launchd — runs the script every hour

Why Claude Code for the Fathom side instead of writing HTTP calls direct? Because Fathom's MCP server already handles auth, pagination, and the next-steps extraction. Reimplementing it in Python would have taken 4-6 hours. The headless Claude call is essentially a thin shim over an MCP server — costs ~$0.02 per run.

Why Python for the Todoist side? Because state and idempotency live in Python land. I trust Python to write to disk and check a seen-set. I don't want the agent re-deciding what's already been synced.

The split: agent reads, code writes. This is the pattern I'd recommend for anyone building agentic workflows in 2026. Let the agent do the messy fuzzy stuff (parse a meeting, attribute an action item to a person, extract a deep link). Let your code own state, deduplication, and anything that touches a write endpoint.

The Prompt

Here's the actual prompt the Python script sends to headless Claude (lightly trimmed for readability):

Call the Fathom MCP tool list_meetings with:
  created_after = "{ISO_TIMESTAMP}"
  include_action_items = true
  include_summary = true
  max_pages = 3

For any meeting in the response that has next-steps / action items
not already flattened into the list response, call get_meeting_summary
to pull the "Next Steps" list and the timestamped URLs.

Then output ONLY a single JSON object between the markers <JSON> and </JSON>,
no other text, no markdown fences, no commentary. Shape:

<JSON>
{
  "trace": "fathom-list: created_after=... returned=<N>",
  "meetings": [
    {
      "recording_id": <int>,
      "title": "<string>",
      "url": "<string>",
      "date": "<YYYY-MM-DD>",
      "attendees": ["<name>", ...],
      "action_items": [
        {"text": "<action item text>", "timestamp_url": "<https url or null>"}
      ]
    }
  ]
}
</JSON>

Three things matter in that prompt:

1. JSON markers, not JSON mode. I tried --output-format json first. It would occasionally wrap the JSON in commentary or include trailing prose. The <JSON>...</JSON> marker pattern with explicit "no other text" instruction is dramatically more reliable. Python regex-extracts between the markers.

2. Strict shape. Every field is named. No "etc." or "and so on." If Claude tries to add a field, Python's validator drops the envelope and state doesn't advance.

3. The trace field. This sounds redundant but it's saved my ass twice. When the script logs "returned=0" but I know there were 4 meetings yesterday, I have a fast signal that something is wrong with the MCP, not my code.

The State File

State lives in ~/.claude/state/fathom-todoist-seen.json:

{
  "last_run_at": "2026-05-28T13:00:00Z",
  "seen_recording_ids": [120448291, 120451829, ...]
}

Two fields, capped at 500 recording IDs (Fathom IDs are monotonic enough that 500 is overkill). The seen-set is the real idempotency guard. last_run_at is just an optimization to keep the Fathom API call narrow.

I overlap the window by 15 minutes on each pull. So if last run was at 13:00, next run looks for meetings created_after = 12:45. Belt-and-suspenders — if a meeting was being processed by Fathom right at 13:00, the next pull catches it. The seen-set prevents the duplicate task creation.

What Broke (And How I Fixed It)

I expected this build to be a clean afternoon. It was four debugging sessions over six days. The failures, in order:

Failure 1 — Action item attribution was wrong 40% of the time.

First version dumped every action item into Todoist with no owner. Useless. Second version asked Claude to attribute owners. It would attribute every item to me. Third version added a regex pre-filter in Python (OTHER_PARTY_RE matches "Sarah will..." or "Bobby should...") and let Claude attribute everything else.

Now ~85% of items get a correct owner. The 15% that don't, I fix manually in Todoist — and they go into a "review prompts" file that I use to improve the attribution heuristic over time.

Failure 2 — Duplicate tasks when Fathom re-processed a meeting.

Fathom occasionally re-processes a meeting (transcript correction, summary regeneration) and the action items list changes. My original idempotency key was recording_id only — once seen, never re-pulled. So if Fathom added a new action item 4 hours later, I missed it.

Fixed by changing the key to recording_id + sha256(action_items_text). If the action items hash changes, the meeting gets re-processed and Todoist gets only the new items (diffed against what we'd already created).

Failure 3 — Claude Code's headless mode would hang on MCP server cold-start.

About 1 in 8 runs, the Fathom MCP would take 40+ seconds to spin up. The Python subprocess had a 30-second timeout. Tasks would silently fail.

Fixed by bumping the subprocess timeout to 180s, and adding a retry-once-on-timeout. Also added a launchd-level alert if two consecutive runs fail (via a simple Pushover notification).

Failure 4 — Timestamp URLs occasionally returned null for action items that clearly had a transcript.

This was a Fathom MCP issue, not mine. I added a fallback — if timestamp_url is null, the task description uses the recording URL with no offset. Better than dropping the link entirely.

The Cost

I ran this script every hour for the last three weeks. Real numbers:

  • Headless Claude cost: ~$0.018 per run × 504 runs = $9.07 total
  • Build time: ~14 hours across the weekend + 4 debugging sessions
  • Time saved: 3.3 hr/week of manual transcription, plus the action items I no longer drop
  • Tasks created automatically: 287 over 21 days (~13.6/day)
  • Tasks manually corrected post-creation: 19 (~6.6%)

So I'm running this for under $0.50/week in API costs, getting back 3+ hours of personal time per week, and dropping vastly fewer action items.

Break-even on the 14 build hours: roughly week 5. We're past that now.

What An Operator Could Replicate

This pattern generalizes. The architecture — agent reads via MCP, Python owns state, code writes to the API — works for:

  • Slack → Todoist (action items from a specific channel get tasked to you)
  • Gmail → CRM (inbound replies to a specific thread sync structured data to Hubspot)
  • Fathom → Notion (meeting summaries auto-create a Notion page in the right database)
  • Amazon Brand Analytics → Google Sheets (weekly SQP report pulls, gets diffed, alerts on movement)

If you're an operator running 15+ calls a week, the Fathom → Todoist pattern specifically is the highest-ROI agentic workflow I've built. Here's the minimum viable version someone could replicate in an afternoon:

  1. Install the Fathom MCP in your Claude Code config. (Fathom has docs.)
  2. Get a Todoist API token from Todoist settings → integrations.
  3. Pick a project/inbox in Todoist as your sync target. Grab the project ID.
  4. Write the Python orchestrator — 200-300 lines, see the architecture above. Three pieces: load state, call headless Claude, validate envelope, write to Todoist, save state.
  5. Run it manually a few times until you trust it. Then schedule it (launchd on Mac, cron on Linux, Task Scheduler on Windows).

The mistake most people make building this stuff in 2026 is letting the agent do everything end-to-end. Don't. Let the agent do the parsing. Let your code do the writing. State files are the unsung heroes of every agentic workflow that actually runs in production.

What I'd Build Next

I'm three iterations into expanding this:

  1. Auto-draft follow-up emails for action items where I owe the prospect something — Gmail draft pre-written, I review and send.
  2. Slack notification on calls with specific clients, with the action items inline, so my team sees them in real-time without me forwarding.
  3. Weekly summary — Claude reads the last 7 days of completed action items and tells me what I'm consistently late on. Painful but useful.

If you're building agentic workflows for your own operation and want to compare notes, my DMs on LinkedIn are open. The pattern matters more than the tool — once you've internalized "agent reads, code writes," you'll see candidate workflows everywhere.

Want results like these for your listings?

Book a free visual strategy audit and see exactly what changes your marketplace listings need.

Get Your Free Audit