Skip to content
Non-Tech Founders

Weekly Reporting Automation: The Founder's Monday Morning Setup

Pull metrics from Stripe, Shopify, HubSpot, and GA4 every Sunday night. Generate a Monday-morning markdown summary with Claude. Email it to yourself. The exact setup we run for ourselves and most of...

By WitsCode10 min read

Most founders I know open Monday morning the same way. They log into Stripe and scroll revenue, then Shopify for orders, then HubSpot for pipeline, then GA4 for traffic, and by the time they have a rough picture of last week it is forty-five minutes later and they have already been pulled into three Slack threads. The information was always going to be the same. The act of collecting it was the tax, and the tax was paid every single week.

We stopped paying that tax about two years ago and rolled the same setup out to most of our clients after it proved itself on our own team. A cron job fires late Sunday night, hits four APIs in parallel, stashes the raw JSON somewhere safe, feeds it to Claude with a specific first-person prompt, and drops a short email in the inbox before the kettle has boiled on Monday. Ten years of reading these reports has taught me that the format matters as much as the data, so this article covers both: the plumbing that pulls the numbers, and the narrative layer that makes the numbers actually get read.

Why The Sunday Night Cron Is Not The Obvious Part

The tempting first instinct is to build a dashboard. Dashboards fail the weekly review job for a specific reason, which is that they require the founder to go to them. A dashboard is a pull. A report is a push. Push wins on Monday morning because the attention budget is small and everything else is competing for it.

The second tempting instinct is to schedule the run for Monday morning itself, maybe six a.m., so the numbers are as fresh as possible. We tried that for a month before we moved it to Sunday night at eleven. Two reasons. First, API quotas and rate limits are kinder at that hour, which matters when GA4 is slow and Stripe is paginated. Second, Claude occasionally needs a retry when a request hiccups, and having a seven-hour buffer between the run and the time the email should land means a retry is a non-event. Nothing is on fire at four a.m. Sunday. Everything is on fire at six a.m. Monday.

The run takes about three minutes end to end on a normal week. The first ninety seconds are API pulls, the next thirty seconds are JSON normalization, the next sixty seconds are the Claude call, and the remaining time is markdown rendering and the SMTP hop. We log every run into a Postgres table so a missed week is obvious in the audit log rather than discovered by absence.

The Four APIs And What To Actually Pull

The scope creep on these reports is real. The first version we built pulled thirty metrics, and the result was an email nobody read. The version that stuck pulls exactly the metrics a founder would care about if they only got to ask one question per system.

From Stripe we pull weekly revenue, new subscriptions, churned subscriptions, net new MRR, and failed payment volume. The Stripe API paginates, so the request loop needs a cursor, and the timestamps need to be pinned to the exact seven-day window or Monday reports will drift.

const stripe = require('stripe')(process.env.STRIPE_KEY);
const end = new Date(); end.setHours(0,0,0,0);
const start = new Date(end); start.setDate(end.getDate() - 7);

const charges = await stripe.charges.list({
  created: { gte: Math.floor(start/1000), lt: Math.floor(end/1000) },
  limit: 100
});

From Shopify we pull order count, gross revenue, refund volume, and average order value for the same window. Shopify's Admin API is friendlier than Stripe's for date ranges but strict about scopes, so the app token needs read_orders and read_products at minimum. We also pull top three products by revenue, because the narrative layer will reference them.

From HubSpot we pull new contacts created, deals opened, deals closed-won, and deals closed-lost. The HubSpot pipeline object is the messy one. Every client has a different pipeline schema, and the property internal names rarely match the display names. A short mapping file per client keeps the n8n workflow portable.

From GA4 we pull sessions, users, bounce rate, and top five landing pages by conversion. GA4's Data API is slow and its quota is stingy, so this is the one that forces the Sunday-night window. A failed GA4 call on Monday at six is a real problem. A failed GA4 call on Sunday at eleven is a retry three minutes later.

The n8n Workflow That Stitches It Together

The orchestrator is n8n, self-hosted, because the data in flight includes customer emails and revenue figures and we would rather not route that through a vendor's webhook infrastructure. The workflow has five stages and about fourteen nodes.

The cron node fires at 23:00 every Sunday in the account's local timezone. It triggers four HTTP Request nodes in parallel, one per data source, each reading credentials from n8n's encrypted credentials store rather than from a config file. The parallel pattern matters because sequential pulls would run close to six minutes and risk the email being late if anything retries.

Each HTTP branch pipes its response into a Function node that normalizes the shape. The normalized shape is the same for every source and looks like this.

{
  "source": "stripe",
  "window": { "start": "2026-04-14", "end": "2026-04-20" },
  "metrics": {
    "revenue": 48210.50,
    "new_mrr": 2140.00,
    "churn_mrr": 680.00,
    "failed_payments": 3
  },
  "prior_week": { "revenue": 41980.00, "new_mrr": 1820.00 }
}

The prior-week block is the trick that unlocks the anomaly layer. Without it Claude cannot tell you that failed payments spiked sixty percent, because it has no baseline. We store the prior week alongside the current week in the same object and carry both through the rest of the pipeline.

After normalization, a Merge node combines all four payloads into a single composite object keyed by source. This composite is written to S3 as raw JSON with the ISO week number in the filename. We keep fifty-two weeks of raw pulls on hand for two reasons. One, if the Claude prompt changes we can re-run any historical week to see how the old narrative would have read today. Two, when a founder asks "what did we ship the week MRR dipped" we have the data to answer in seconds rather than archaeology.

The composite object then flows to an HTTP Request node that calls Claude. The response is a markdown block. A final SMTP node sends it to the configured recipient list.

The Anomaly Threshold And Why Twenty Percent Is The Right Line

Every automated report we have ever seen dies of one of two causes. Either it cries wolf on every tiny fluctuation and the founder learns to ignore it, or it never flags anything and the founder wonders why they are paying for the cron. The cure is a deliberate anomaly threshold baked into the prompt and the pre-processing.

Before the Claude call, a Function node walks every metric and computes week-over-week change. If the absolute change exceeds twenty percent in either direction, the metric is added to an anomalies array in the payload. Twenty percent is the right line for most early-stage and mid-stage businesses because normal weekly variation on revenue, traffic, and pipeline tends to sit in the five to fifteen percent band. A twenty percent move is either a real signal or a data issue, and either one deserves eyes on it.

function flagAnomalies(current, prior) {
  const flagged = [];
  for (const [key, val] of Object.entries(current)) {
    const old = prior[key];
    if (!old || old === 0) continue;
    const delta = (val - old) / old;
    if (Math.abs(delta) >= 0.20) {
      flagged.push({ metric: key, delta: delta, current: val, prior: old });
    }
  }
  return flagged;
}

We cap the anomaly list at three per report even when the raw count is higher. A founder who sees nine flagged items treats them the way they treat a Slack channel with forty unreads, which is to scroll past. Three is digestible. The prompt ranks the anomalies by absolute dollar or traffic impact, not by percentage, so a sixty percent swing on a tiny metric does not crowd out a twenty-two percent swing on revenue.

For tiny-denominator metrics we add a floor. A move from two failed payments to four is a hundred percent increase, but it is not news. The floor is set per metric in the config file and blocks anomalies where the prior-week value is below a minimum, for example ten for payment counts and five hundred for GA4 sessions.

The First-Person Summary Prompt

The narrative layer is the reason the whole thing works. A table of numbers is inert. A three-sentence summary written as if a chief of staff was handing it over is something a founder will read in six seconds and actually remember. The prompt we settled on after a lot of iteration treats Claude as the founder's analyst, not as a neutral observer.

You are the founder's chief of staff writing the Monday morning brief.
Read the attached JSON covering last week's metrics across Stripe,
Shopify, HubSpot, and GA4. Compare to the prior week included in the
payload.

Produce three sections in order. First, a KPI table in markdown with
six rows: Revenue, New MRR, Orders, AOV, New Contacts, Sessions. Show
the current value, prior value, and week-over-week change as a
percentage. Second, a three-sentence narrative summary written in
first person plural ("we shipped", "our traffic", "our MRR") as if
you are the founder reviewing your own week. No hedging language.
State what happened. Third, a list of up to three flagged anomalies
from the payload's anomalies array, each explained in one sentence
with the likely cause given the surrounding data.

Do not invent causes you cannot support from the data. If you do not
know why a metric moved, say so in plain language. The reader is the
founder. They have three minutes.

The first-person framing matters more than it sounds. When Claude writes "sessions fell twenty-two percent" it reads like a report card. When it writes "our sessions fell twenty-two percent, almost entirely driven by the blog, and we still do not know why" it reads like a teammate flagging a problem. That is the tone that gets acted on.

The "do not invent causes" clause is load-bearing. Without it Claude will happily speculate that a revenue dip was "likely seasonal" when in fact nothing in the payload says so. The no-hedging rule covers the opposite failure, where the summary buries a clear signal in softening language. Short sentences. Verbs. Numbers.

What The Monday Email Actually Looks Like

The email arrives in the founder's inbox at 06:30 Monday local time. Subject line is always [YourCo] Weekly Brief, Week of April 14. The body is plain markdown rendered to HTML by a simple template.

The first block is the KPI table. Six rows, three columns, no color coding, no sparklines. A founder's eye resolves a six-row table in under two seconds, which is the attention budget that exists on a Monday morning.

The second block is the three-sentence narrative. It reads like a person wrote it. Something like "Our revenue came in at forty-eight thousand last week, up fifteen percent over the prior week and driven almost entirely by a Friday spike in subscription renewals. We added eleven new contacts, three of which are now deals in HubSpot's qualification stage. Sessions were flat but bounce rate climbed to sixty-two percent, which is worth a look."

The third block is the anomaly list. Three items, each one sentence, each explaining both the number and the probable cause using only what the data shows. The last line of the email is a link to the raw JSON in S3 for the week, so a founder who wants to drill down can do so in one click rather than logging into four dashboards.

The whole email is about three hundred words. It gets read. It gets forwarded to the executive team. It gets screenshotted into the investor update.

The Total Cost And What Tends To Break

The cost is small enough to not matter for most businesses. n8n self-hosted runs on a five-dollar-a-month droplet that also handles a dozen other automations. Claude API calls for fifty-two reports a year at a few thousand tokens each is under ten dollars annually. The time cost is the build, which is six to ten hours for a founder who is comfortable in n8n or a weekend for someone learning it.

The failure modes are predictable. GA4 rate limits hit once every few months, usually the week a site has a traffic spike, and the retry logic handles it. HubSpot property renames break the pipeline silently, because a missing field in the API response just returns null rather than an error, so we added a schema check at normalization time that logs a warning if any expected field comes back empty. Stripe occasionally serves a partial page when the account has a large volume of test-mode activity mixed with live, and pinning the request to livemode: true fixes it.

If you want this exact setup installed on your stack, with the n8n workflow export, the Claude prompt tuned to your voice, the anomaly thresholds set to your baseline, and the email template branded to your company, WitsCode ships a weekly reporting setup as a fixed-scope build. Two weeks from kickoff to the first Monday brief landing in your inbox, configured against your actual Stripe, Shopify, HubSpot, and GA4 accounts, with a runbook your ops team can own afterward. The report you read on Monday is the one you build the rest of the week around. It should not be the thing you spend the first hour of Monday assembling by hand.

Get weekly field notes.

Practical writing on shipping products, straight to your inbox. No spam.

Need help with this?

Custom Web Applications

We design and build web apps, MVPs, and SaaS products. Talk to us about what you are working on.

Talk to us

Want to discuss non-tech founders for your business?

Start a project and we'll talk through where you are, what's working, and the highest-leverage moves for the next 90 days.