▷ Case Study 03 / 06

Case Study 03

Marketing Automation Agent

A multi-channel system that turns scattered customer signal into routed action for a solo marketer.

  • Service Design
  • Internal Tools
  • AI Systems
Role
  • Solo marketer
  • System designer
Team
  • Solo build
  • feeds CEO, engineering, sales, marketing
Stack
  • Python
  • Anthropic API
  • Google APIs
  • Slack
  • JPD
Status
Live, operating daily on cron
Span
~6 months, iterative build

01

The Trigger

Customer signal arrived across four channels with no shared intake. Manual triage was eating the week.

Gmail
Slack
Discord
Lemlist replies

No shared
intake

One marketer,
manual triage

Gmail, Slack, Discord, and Lemlist replies all landed without a shared routing layer.

02

Research

Before committing to a build, I evaluated agent platforms (OpenClaw, Zapier, Relevance AI) against the unit economics of a custom cron + Anthropic API stack.

build vs buy

Hosted platformsCustom buildSemantic classificationPredictable cost at volumeOwns the storage layerNo code to maintain
Custom won on classification fidelity, cost predictability, and owning the storage layer. The platforms won on having nothing to maintain.

03

What I Tried First

The first attempt was MCP connectors and single-purpose Zapier flows. The thesis was simple: route raw messages into a CRM and trust manual review for the rest.

GmailMCP
SlackMCP
RoutingZapier

CRM,
raw messages

Manual
review

MCP for Gmail and Slack, plus Zapier for ad-hoc routing.
Gmail → Notion CRMPublish

1. New Email Matching Search

Gmail · Trigger

2. Only Continue If Keyword Match

Filter · Condition

N

3. Create Database Item

Notion · Action

One Zapier flow per source, none of them talking to each other. Recreated in HTML.
Marketing / Inbound messagesShare

Inbound messages

TableFilterSortNew
Aa NameSourceMessage

+ New

Raw messages dumped into a Notion database, manual triage on the other end. Recreated in HTML with sender names redacted.

04

What Broke

MCP couldn't reach the shared Drive. Zapier couldn't classify semantically. The bigger break was conceptual: capture, briefing, and outreach are one knowledge substrate, not three.

MCP connectors
auth gap
Shared Drive
Zapier flows
rules only
Semantic classification
MCP couldn't reach the shared Drive, and Zapier couldn't classify semantically.

05

What I Changed

I rebuilt the system around one knowledge substrate. Three layers handle the flow: Ingest, Knowledge, Interface. Claude Code in the same repo reads what the cron jobs write.

FigJam diagram of the V-1.0 architecture: data sources feed background automation on a Hetzner VPS, with a CLI interaction layer, a Google Drive knowledge base, and an output block for drafts, briefs, and reminders
The V-1.0 sketch in FigJam: sources feed scheduled automation on the VPS, outputs land as drafts, briefs, and reminders, and the Drive knowledge base sits between the layers.
Detailed FigJam map of the V-2.0 system: Claude Code, data sources, cron jobs with schedules, shared Drive folders, and outputs, all connected
The V-2.0 map: each cron job with its schedule, the Drive folders it reads and writes, and the outputs it feeds. Claude Code joined as the interactive layer on the same substrate.
cloudrift-automation — ✳ Review Discord updates and identify urgent team issues

Claude Code v2.1.132

claude-fable-5 · Claude Max

~/cloudrift-automation

The interactive layer reads the same files the cron jobs write. The session replays as it happened; it is recreated in HTML because the real output names customers and account details, and the yellow bars mark the redactions.

Design surfaces

Slack message from the review bot proposing the feature request 'Scope promo codes to specific GPU types', with instructions to react with a check mark to add it to the roadmap or an x to dismiss
Low-confidence feature requests wait in Slack for a reaction. A check mark files them to the roadmap, an x dismisses them. The human stays in the loop with one tap.

Quiet design choices

  • Service account with domain-wide delegation, not OAuth
  • Sonnet for classification, Opus for quality drafts
  • Memory persists user preferences across sessions
  • Briefs auto-trigger 45 minutes before recognized meetings

06

Result

Defensible numbers

  • 10 cron jobs across 4 inbound channels, plus JPD sync and brief generation
  • Auto-briefs for 4 recognized meeting types
  • Feature requests deduplicated semantically before JPD intake
Excerpt of an auto-generated Weekly Planning brief in Slack listing recurring customer issues, with names redacted
An auto-generated Weekly Planning brief, posted to Slack 45 minutes before the meeting. Names redacted.

Qualitative Impact

  • Solo marketer operating at the throughput of a small team
  • Covers capture, knowledge, and outreach end to end

What I didn't measure (and won't fake)

  • Time saved per week, variable depending on inbound volume
  • Conversion impact, not isolated from other changes
Slack message from the Discord scanner reporting one new friction point, an SSH bug from a community member, saved to the Friction Points folder in Drive
The Discord scanner reporting a new friction point in Slack, already classified and filed to the shared Drive.
- ObsidianVault - Obsidian
(inference startup)

(inference startup)

• Type: customer

• First seen: Jan 2026, Discord

• Last updated: Jun 2026

Profile

Runs vision-LLM batch jobs on RTX 4090s, deploys with vLLM through the OpenAI-compatible API.

Signals

• Jan 2026 · Discord: asked about batch pricing, compared us with

• Feb 2026 · Gmail: asked to split invoices across two teams

• Apr 2026 · Discord: rental-flow friction, filed to UX Materials

• Jun 2026 · Slack: evaluating H100 for a fine-tune run

ICP profiles accumulate context from inbound channels over months. Recreated in HTML with the identifying details redacted, since real profiles describe real customers.

07

What I Learned

Most of the value of this build came from collapsing three problems into one knowledge substrate. The reframes below are the ones I carry into the next agent system.

Carried over to the next system

  • Capture, briefing, and outreach share one knowledge substrate. Treating them as three separate systems forces duplicated state and opens the gaps where signal gets lost.
  • Capability tiering is a cost lever. Sonnet for classification, Opus for drafting, and the monthly bill stays predictable per channel.
  • Flat-file markdown in a shared Drive outlasts any agent platform's storage layer. The substrate survives provider churn, pricing shifts, and feature deprecation.

What I'd do differently

  • Build the ObsidianVault structure first, then the cron jobs that populate it. I built bottom-up and had to refactor the substrate once it stabilized.
  • Skip the MCP detour. The service-account-direct path works from day one and avoids the auth-gap surprise that broke the first attempt.
  • Move more of the cron-driven background work into prompted Claude Code agent loops. The agentic tool use Claude Code now ships with would thin out a chunk of the bespoke Python.

Case Study 03 Clear

Thanks for reading. If you want to talk about this work, reach out any time.