All RevOps

AI Client Reporting

A weekly reporting system for marketing agencies. It pulls ad data, flags problems, has Claude write the client note, and checks the data.

The problem

Every marketing agency sends its clients a weekly performance report. And someone has to build it.

That means opening Meta, Google Ads and GA4 for each client, copying numbers into a sheet, working out what moved and writing it up. Agency owners put it at 1-2 hours per client, every week.

The person who can explain a number is usually a strategist. So senior time goes into copy-paste.

Problems surface late, too. A campaign whose costs doubled can hide inside an account total that moved 9%, and overspend usually shows up first on the invoice.

What I built

One command that runs every Monday at 7am (cron or n8n kicks it off). For each client it:

  • Pulls 90 days of daily data from Meta Ads, Google Ads and GA4 through connectors I can swap out.
  • Compares this week with last week, month to date, per channel and per campaign, and checks budget pacing.
  • Runs 4 rules: CPA up more than 30%, ROAS down more than 25%, spend above 110% of pace, CTR down more than 20%.
  • Hands the flags to Claude, which writes the client note in the agency's voice.
  • Checks every number in that note, then writes everything to 5 Google Sheet tabs that feed Looker Studio.
  • Emails each client a branded report with a link to their live dashboard.

Rules find the problems, Claude explains them

I could've asked Claude to read the numbers and decide what's wrong. I didn't.

Rules cost next to nothing and give the same answer every run. The agency tunes them in 1 settings file.

Each rule ignores campaigns with too little data (under 10 conversions, say), so one bad Tuesday on a tiny campaign doesn't set off an alarm.

Claude gets the summary: totals, channels, top campaigns, pacing and the flags. It never sees the daily rows, so it can't quote a figure the code didn't calculate.

The code checks Claude

A wrong number in a client report costs the agency trust. So nothing goes out until the note passes 3 checks:

  • Structure: every section's there, the headline's under 90 characters, and there are 2 or 3 next steps.
  • Coverage: every flag shows up in exactly 1 of Claude's problem items, so Claude can't skip one or mention it twice.
  • Numbers: every % and $ figure in the note has to match a number Claude was given, within rounding.

When a check fails

Claude gets 1 retry with the errors spelled out.

If the structure or coverage check still fails, that client's report stops and the agency gets an alert. Every other client's report still goes out.

What it caught

I tested it on 3 made-up clients and hid 1 problem in each one's last 7 days. The system doesn't know where.

  • Northwind Outdoor (online store): ROAS was 3.43 against a target of 4, down 9%. That reads like a soft week. Underneath, one Meta campaign's cost per sale had gone from $42.78 to $93.22.
  • Halcyon Dental: cost per lead was on target. But Google Ads was at 127% of budget pace, on course for about $15,262 against a $12,000 month. Spend had run hot all month, so week-over-week comparisons saw nothing; the pacing rule caught it.
  • Brightline Software: one Meta ad's click-through rate slid from 1.07% to 0.74% over 10 days. That's an audience getting sick of an ad.

Adding clients and data sources

Each client is one block in a YAML file: budget, goals, tone of voice, banned words, recipients and dashboard link.

So the dental client's note comes out warm and calls leads "booked appointments". The online store's note is direct and leads with revenue.

Every connector returns data in the same shape. Swapping sample data for Windsor.ai, Supermetrics or the platform APIs means changing 1 file.

Where it stands

It's a working build on sample data. No agency is paying for it yet.

The full run works end to end and 46 automated tests pass. Live Google Sheets and email sending are built, but I haven't pointed them at real accounts.

And the 1-2 hours saved is the industry's number. I haven't measured it myself.

What's next

  • Connect real ad accounts, then bolt on Shopify, a CRM or CallRail so sales and leads come from where they actually happened.
  • An MCP layer, so someone can ask Claude "why did Northwind's CPA jump?" in Slack and get an answer from the same Sheet.
  • A 4-week pilot next to one agency's manual process, counting hours spent and problems caught on each side.

Stack

Python, the Claude API (structured output), Pydantic, Jinja, Google Sheets, Looker Studio, Resend or SMTP, and cron or n8n.

Hand-drawn diagram: ad platforms feed connectors into a weekly engine that calculates metrics, flags problems and has Claude write a note, which is fact-checked and sent to a Google Sheet, Looker Studio and the client's inbox. An optional MCP layer sits underneath.