6 min read

I rage-built email automation because HubSpot wanted me to talk to sales

A SaaS pricing frustration turned into a real production email automation system and a lesson in trusting specs, checks, and repo-owned guardrails.

Muted geometric architectural forms with vertical towers against a cloudy sky

I am a fractional marketing CMO now, but I used to be a full-time engineer. I can read logs. I know the difference between “this probably works” and “this is safe enough to run.”

But I have not lived inside modern web infrastructure for a long time. The default stack looked very different when I was doing serious hands-on engineering management. I have never deployed a production system on Cloudflare Workers.

And yet, over about 10 days, I got a production email automation system built, reviewed, deployed, guarded, and running. The reason is one most marketers should recognize: I got annoyed at HubSpot.

I had set up HubSpot for CRM collection and light automation. Then I wanted the next obvious step: triggered follow-up. I did not need a sprawling marketing operations universe. I needed enough logic to say: when this happens, send the next email, respect the rules, keep state, and do not do anything stupid.

HubSpot could do it, of course, if you move far enough up the pricing page. The automation tier came with an expensive upsell and a mandatory, multi-thousand-dollar professional services fee. I did not need a services package. I needed a small workflow I understood.

That was the first moment of rage. The second came when I tried to solve it myself. Like every person with a little technical background and too much confidence, I started building a tool locally with AI. I could describe the behavior and get code. Then reality showed up: a local tool is not a production system.

The hard part was not getting an AI model to write TypeScript. It was everything around the code: deployment, state, secrets, review, activation gates, safety checks, and whether I would trust this thing to touch real customers.

I needed to get this into production, so I collaborated with Charlie’s CEO to put the basics in place: a Cloudflare account, a private GitHub repo, and the initial shape of the project. Then he more or less sent me off on my own to do the rest.

At first, I did what many semi-technical people do when dropped into infrastructure. I edited things in the Cloudflare UI and treated the console like the source of truth. I was recreating the exact mess that makes production systems hard to reason about.

Eventually I got the gentle reminder I needed: Charlie could do that work too. Not just the code. The operational glue. That was the shift.

Instead of making one-off dashboard changes, I started asking Charlie to drive the work through the repo. The desired system lived in written English. Pull requests captured changes. GitHub Actions proved things. Daemons watched the safety boundaries. Cloudflare state became something to reconcile against the plan, not tweak by hand until it looked plausible.

TEE operations templates page listing reviewed Git-backed onboarding email templates for 24-hour, five-day, and 72-hour follow-ups, including their required unsubscribe URL variable.

The email content lived in reviewed, Git-backed templates instead of a separate dashboard.

I installed a few Daemons in the repo to keep things protected. A Daemon is closer to a written job description: watch this surface, enforce this expectation, respond to this change, stay inside these boundaries. Instead of hoping the AI remembered what mattered, the repo contained durable instructions for what “safe” meant.

TEE operations workflows page for the enabled no-Charlie-ID onboarding workflow, showing its definition hash, live-capable runtime mode, and three scheduled follow-up steps.

Each workflow version exposed its definition, runtime posture, and scheduled follow-ups for review.

Once that was in place, I got comfortable with the workflow. Charlie opened PRs. I read summaries. GitHub Actions ran. When something failed, Charlie investigated. When deployment needed explicit approval, the system asked. When production state needed comparison with intended state, Charlie reconciled it.

TEE operations overview for a transactional email engine, showing live-capable automatic follow-ups, global sending enabled, dry run disabled, a rollout cutoff, and two enabled bundled workflows.

The read-only operations dashboard made live state and its safety gates visible without becoming another place for one-off changes.

At first, I wanted a hand on everything. Then I granted more autonomy. Run the Actions. Merge when checks pass. Make the follow-up fix. Update the docs. Re-run activation. Come back only for a real decision.

That is when I realized the actual product was not the TypeScript code. At one point, I realized I did not even know it was TypeScript and could not name the dependencies. A decade ago, that would have made me feel irresponsible. How can you own a production system if you do not know the stack?

But I did know the spec: what the system could and could not do, what “ready” and “activated” meant, what evidence I expected before live sending, and that ambiguity should stop the system rather than cause it to improvise.

The written English documentation and specification were the heart of the project. The code and infrastructure mattered, obviously. But the thing I could trust was not a pile of source files I had personally memorized. It was the chain from intent to artifact to check to deployment evidence.

Charlie did not make infrastructure fake. It made infrastructure manageable. The human writes and reviews the intent. The agent does the implementation work. Daemons and automation preserve the guardrails. The repo becomes the place where product judgment, operational safety, and code meet.

I am not non-technical, but I am not current like a hands-on infrastructure engineer. I still do not know every moving piece. But I could drive the project because its important decisions appeared in forms I could evaluate: PRs, specs, workflow runs, activation gates, reconcile output, and documented safety rules.

Over around 10 days, we had a fully running system. Not a prototype or local script. A real production system that we will probably open source if there is enough interest. Judgment, clear requirements, and knowing when to ask for proof still matter. But you no longer need every piece of implementation knowledge in your own head before you can move.

That is the lesson I did not expect from rage-building email automation. I thought the story would be about escaping vendor lock-in or getting AI to write code faster. It became a story about trusting the spec, the checks, the repo, and that when the system is not ready, it will say no.

The deeper thing I got was a reminder that a lot of software is stuck between “I know exactly what I need” and “I can safely get this into production.” That gap is filled with operational glue: drifting specs, hand-clicked dashboards, memory-dependent deploys, and implied safety checks. Charlie worked through that glue, wrote it down, and kept it attached to the project.

That is why this felt different from using AI to generate code locally. Local generation gave me files. The Agent + Daemons workflow gave me a production process I could trust. I still have not become a modern web infrastructure engineer again. But I did become the person who could safely approve infrastructure that matters and will make a real impact on my client’s business. Thanks, Charlie.