Every agency says they do automation. Usually that means a Zapier account, a Google Sheet, and a promise. Nobody shows you the wiring, because the wiring is where the embarrassing parts live.
Here is ours. The stack, the rules we use to decide what runs where, and the four ways automation fails quietly enough that you find out from a client instead of a dashboard.
The framing we use with clients is a car. The website is the car. The automations are how it runs. AI is the engine that got replaced last year. This post is about the middle layer, the part nobody photographs.
Four layers, and the rule for picking one
Almost every automation request falls into one of four buckets. The bucket is decided by two questions: what triggers it, and who owns it when it breaks.
Event-driven work runs on Make.com. A form gets submitted. A CMS record gets published. An email lands in an inbox we watch. Something happened in the world and a reaction needs to fire in the next few seconds.
Every client form on a Tuscan site posts to a server-side API route on that site, and the route forwards to the Make webhook. The webhook URL never reaches the browser. That is not a security nicety, it is the difference between a form and an open pipe that anyone can spray junk into.
Scheduled work runs on GitHub Actions. Forty-five workflow files in our automation repo carry a cron schedule: content scrapes, market data snapshots, newsletter builds, page-health sweeps, digest emails. Each one runs a Python entry point that calls the Claude API with a job-specific prompt.
They run on Sonnet, not Opus. A job that summarizes yesterday's leads does not need the expensive model, and the daily bill is the reason we can afford to have forty-five of them instead of five.
They run on GitHub's infrastructure, not on a machine in my office, and that was a lesson rather than a decision. We had a Mac mini running scheduled jobs. It went dark in May. Some of those jobs were not noticed as missing for weeks, because a machine that stops does not send you an error, it sends you nothing. A laptop that closes is not infrastructure.
Product work runs on Vercel crons inside the product. The Tuscan CMS publishes scheduled posts hourly, ingests podcast feeds hourly, refreshes Instagram feeds every six hours, transcribes audio nightly, and runs a retention cleanup daily. All of that lives in the CMS repo and deploys with the CMS. The whole set costs about twenty cents a month.
The rule there: if a job is part of how the product works, it ships with the product. Pulling it into a separate scheduler creates a second thing to deploy and a second place to look when it stops.
Judgment work runs in Claude Code, through skills. There are 41 skills in my .claude/skills directory right now. Importing a client article into the CMS with images rehosted and schema attached. Auditing whether a site is readable by AI crawlers. Building a quarterly review deck from live GA4 and Search Console data. Generating a contract.
These are the jobs that need a decision made, not a record moved. I wrote about what is actually in that directory a few months back, and the count has gone up since.
The part nobody builds
Building the automation is the easy half. The hard half is knowing, every morning, whether it is still working.
We run 26 checks we call doctors. Each one asserts a single specific thing about the fleet and shuts up when it is true. A few of them:
- sitemap-doctor confirms every client sitemap is complete, not truncated by a partial CMS read.
- tracking-doctor confirms analytics initialize identically across sites, which is how mis-attributed traffic gets caught before a monthly report is wrong.
- lead-arrival-doctor confirms leads are still landing where they should.
- dns-doctor and firewall-doctor confirm nothing changed at the edge that we did not change.
- secret-scan-doctor confirms no credential got committed.
- env-drift-doctor confirms a site's deployed environment still matches what its code expects.
They run at 7:41 every morning, with a deeper pass on Sundays, and a separate alerter checks for failed runs every hour. Everything reports to one Slack channel. One destination, not six, because an alert stream split across six places is six places nobody is looking.
The uncomfortable truth about monitoring is that a check nobody trusts is worse than no check. If a doctor cries wolf daily, it gets muted, and it takes its real findings with it. So each doctor asserts exactly one condition with a real consequence, and anything that fails loudly in front of a human on its own is deliberately left off the list.
Four ways automation fails quietly
These are ours. Every one of them cost us something before it got a check.
1. Make disables a scenario after three consecutive errors. Silently. The toggle flips off, the scenario stops, and nothing tells you. One of our form intakes sat off for six weeks with a live form on a live site pointed at it. Every submission in that window was gone before it existed.
The fix is a must-be-active list, currently 55 scenarios, checked daily. Not "is it erroring," which is a different question. Is it on.
2. A failed run with no stored record leaves no evidence. Make has a setting called "allow storing of incomplete executions." With it off, a run that fails mid-flight vanishes. No queue item, no dead letter, no count for a health check to read.
It is now on for 42 webhook intakes across the fleet. That has a real tradeoff and I would rather state it than hide it: a retry re-runs the entire scenario, so a failure that happens after a partial success can duplicate a row or re-send an email. We took that trade on purpose. A possible duplicate beats a guaranteed lost lead.
The payoff was immediate. During an email provider outage this summer, a client signup queued, retried, and landed. The same outage against an unprotected intake would have destroyed the lead with no trace, and no health check could have flagged it, because the failure counter stays at zero when storage is off.
3. An unattached webhook returns 410, it does not queue. This one surprises people who assume a stopped automation just backs up. It does not. Turn off the wrong scenario and the inbound event is refused and destroyed at the door. That is why we watch the feeder legs of a pipeline as carefully as the publishing legs.
4. Error counts lie. Make logs one errored execution per retry attempt, so ten errors is usually one item retried on exponential backoff, not ten lost records. We learned to read the executions before counting the errors, because the alternative is a panic over a number that means one thing failed briefly and then worked.
If it is not in the registry, it does not exist
The last piece is boring and it is the one that holds the rest together. Every scheduled job is written down in a single file. Every client is mapped in a single YAML registry: brain folder to code path to live domain to Make team to Slack channel.
That registry is not documentation. It is an input. Every fleet-wide check discovers which sites to inspect by reading it. Which means the registry is also a loaded gun: blanking one client's code paths once quietly moved a shared reader from twelve repos to eleven, and it reported success the entire time. A check that inspects fewer things always passes faster.
So there is a doctor for the registry too. It fails the daily run if an active client has no code path or a placeholder domain. Stale documentation is treated as a bug, with the same urgency as broken code, because in this setup it functions as broken code.
What to take from this if you are not running an agency
You do not need forty-five workflows. You probably need three. But whoever builds them owes you an answer to three questions, and most automation sold to small businesses cannot answer the second one.
What happens when this fails? Who finds out? How fast?
"It just works" is not an answer. Every automation in this post fails sometimes. The difference between a good setup and a bad one is not failure rate, it is detection time. A lead form that breaks and pages someone in an hour is a small problem. The same form breaking silently for six weeks is a quarter of pipeline you will never be able to reconstruct, and you will not find out from your dashboard. You will find out from a customer who says they filled out your form and never heard back.
Cost is not what keeps most businesses from having this. The scheduled layer described above runs on a mid-tier model and the CMS crons cost pocket change. Attention is the expensive part, which is exactly why it is worth paying somebody to hold it.
If you want to see what this looks like on your stack, start a project.

