Monthly SEO reporting: the template we auto-generate
The monthly client report is the part of agency work nobody enjoys and everybody bills for. It's the most repetitive deliverable you ship, and the one clients judge you on. So we built agents to generate it. This is the exact template we auto-produce every month, what goes in each section and why, why monthly is the right cadence, and the pipeline that turns raw data into a report a client actually reads.
What monthly SEO reporting should contain
Good monthly SEO reporting answers one question for the client: did the money you spent this month move the work forward, and what did it move? Concretely that's six things - traffic and rankings versus last month, what we actually shipped, what it changed, what broke, what's next, and a plain-language summary at the top that a non-SEO can read in thirty seconds. Everything else is decoration.
Most reports get this backwards. They open with forty rows of keyword positions and bury the one sentence the client cares about somewhere on page three. Ahrefs makes the point well: a report "mixes insights, actions, data, strategy, and communication all into one document" (Ahrefs, 2024). The data is the easy half. The insight and the communication are where reports earn their keep, and where almost every automated tool falls down, because dumping numbers into a dashboard is not the same as telling someone what the numbers mean.
Why monthly, not weekly or quarterly
Monthly is the right cadence because SEO moves on a monthly clock and clients budget on a monthly clock. Weekly reporting reports noise: rankings bounce day to day, and a week isn't long enough for published work to show up in traffic. Quarterly is too slow to catch a problem before it costs three months. Monthly lines up the reporting rhythm with both how the channel actually behaves and how the invoice goes out.
There's a second reason that only shows up once you run reports at volume. A weekly report tempts you to react to every wiggle, and that's how you end up changing a title tag four times in a month chasing a ranking that was always going to settle on its own. The monthly window forces a longer look. You compare this month to last, and the quarter-over-quarter trend in the same view, which is exactly the comparison frame the better reporting guides recommend tracking.
We still pull data daily into our own store. That's a separate thing from reporting it. Internally, rank changes and crawl errors get watched continuously so we catch a tanking page in days, not at month-end. The client gets the monthly story; the agent gets the daily signal. Conflating "monitor often" with "report often" is the mistake that buries clients in noise.
The template, section by section
Here's the structure we generate every month, in order. The order matters as much as the contents, because it puts the answer first and the evidence after, which is how a busy person reads.
| Section | What it answers | Source |
|---|---|---|
| TL;DR summary | Did this month go well, in three sentences | Generated from the data below |
| Traffic & conversions | Organic sessions and goal completions vs last month | GA4 |
| Visibility & rankings | Tracked keywords up, down, new, lost | Rank tracker + Search Console |
| Work shipped | What we published, fixed, or built this month | Task log |
| Impact | Which shipped work moved which metric | Joined task log + analytics |
| Issues & risks | What broke or needs the client's call | Crawl + monitoring |
| Next month | The plan, in priority order | Roadmap |
The two sections that separate a useful report from a data dump are "work shipped" and "impact." Anyone can paste a traffic chart. Tying a 14 percent lift on a category page to the fact that you rewrote its on-page copy and added eight internal links three weeks earlier is the part that makes a client renew. That join is also the hardest to automate, because it needs your task log and your analytics in the same place, keyed to the same dates and URLs.
The visibility section pulls from a live ranking store rather than a screenshot. If you want the mechanics of how that data layer gets built and queried, we wrote it up separately in our piece on the SEO reporting dashboard we run behind these reports. The on-page work that shows up in the "work shipped" row comes out of the same system we use for on-page SEO at scale, so the report isn't describing work in the abstract, it's reading from the log of what the agents actually did.
The part most reports skip: the narrative
The single highest-value sentence in a monthly report is the one that explains a number, not the number itself. "Organic traffic is up 11 percent" is data. "Organic traffic is up 11 percent, driven by the three buyer-intent pages we published in April finally indexing and ranking" is a report. The second one is what justifies the retainer.
This is the part I was sure couldn't be automated, and the part where I was most wrong. The trick isn't asking a model to "write a summary." That gets you fluent nonsense. The trick is giving it the joined data - the metric deltas next to the task log next to the date stamps - and asking it to explain only the movements it can actually attribute, and to say "no clear driver this month" when there isn't one. A report that admits a flat month honestly reads as more credible than one that invents a reason for every wiggle. Clients can smell a manufactured narrative, and it costs you more trust than a quiet month ever would.
Google's own guidance on content lands in the same place, even though it's aimed at web pages, not client reports: prioritize "helpful, reliable information that's created to benefit people, and not content that's created to manipulate" (Google Search Central, 2024). A monthly report written to make the agency look busy fails the same test as a page written to game the algorithm. Write the report for the person reading it.
The auto-generation pipeline
The pipeline is four stages: pull each data source into a normalized store, join the metrics to the task log by date and URL, have an agent draft the narrative from that joined data, then render the whole thing into a branded template. A human reviews the draft before it goes out. The agent does the assembly; a person still owns the send.
Stage one is collection. Each source - analytics, rank data, Search Console, the crawl, the task log - lands in one place with a consistent shape, keyed by client, date, and URL. The whole report stands on this: if traffic and tasks aren't keyed to the same URLs and dates, you can't attribute anything, and you're back to a dashboard with no story.
# one normalized row shape per metric, per client, per day
report_input = {
"client": "acme",
"period": "2026-05",
"traffic": {"organic_sessions": 18240, "prev": 16410},
"rankings": {"up": 31, "down": 9, "new": 7, "lost": 2},
"shipped": [
{"date": "2026-05-06", "url": "/collections/boots",
"task": "rewrote on-page copy + 8 internal links"},
],
}
Stage two is the join, and it's the one that earns the report. For each shipped task, the pipeline looks up the metric movement on that URL in the weeks after the change. That join is what turns a list of tasks and a list of numbers into cause-and-effect the agent can write about.
def attribute(shipped, rankings_by_url):
"""Match each shipped task to the movement on its URL."""
out = []
for task in shipped:
delta = rankings_by_url.get(task["url"])
out.append({**task, "effect": delta or "no clear movement yet"})
return out
Stage three hands that joined object to an agent with one instruction: explain only what the data supports, flag what it can't attribute, and never invent a driver. Stage four drops the approved narrative and the charts into a branded HTML or PDF template, one per client, same layout every month so the client learns where to look. The reason this works at volume is that stages one, two, and four are deterministic - same input, same output - and only stage three uses a model, on data that's already been cleaned and joined. We run agents doing this kind of finished work across client accounts in production, and the reports that hold up are the ones where the model writes about verified data, never the ones where it's asked to reason from raw inputs.
Four ways auto-reports go wrong
- Vanity metrics up top. Impressions and "total keywords tracked" look impressive and mean nothing. Lead with traffic, conversions, and the movements tied to work you did.
- Narrative the data doesn't support. If the model attributes a traffic jump to a page that hadn't indexed yet, you've shipped a confident lie. Constrain it to attributable movements and let it say "no clear driver."
- A new layout every month. Clients read faster when the report is in the same shape each time. Lock the template; vary only the contents.
- No human in the loop. The pipeline can be wrong - a broken join, a misparsed date. A two-minute human read before send is the cheapest insurance you'll ever buy.
None of this is exotic. The reason agencies don't do it isn't that it's hard, it's that building the data join once feels like more work than copy-pasting a report this month. It is more work this month. It's far less work every month after, and the reports are better because the narrative is read off real attribution instead of guessed at by a tired account manager at 6pm. The repetitive part is exactly the part a well-built agent should own.
If you run an agency and the monthly report is eating a day per client, we'll run a free teardown of your current one: we'll take a recent report, map what's data-dump versus what's insight, and show you the auto-generation pipeline against your own accounts so you can see the join working on real data, not a demo. Get a free audit on one client site - one domain, every finding checkable against the live site, no contract.
Pavle Lazic is the founder of Scalably, where he builds and runs multi-tenant Claude agent platforms in production for real businesses. He writes about the Claude Agent SDK, MCP servers, and what it actually takes to put AI agents to work on real operations. See the platform.