A weekly report builder on AWS for a few dollars a month
Most small-business owners can’t tell you on a Monday morning how last week actually went. The numbers exist — sales are in one place, new customers in another, cash in and out in the bank export, the top-selling items buried in the point-of-sale — but nobody has time to open four tabs, line them up against the week before, and work out what changed. So the question goes unanswered, week after week, until something is badly wrong and it’s obvious to everyone. This post walks through the design of a small builder that gathers all those numbers, compares them, writes a short plain-English summary of how the week went, and emails it every Monday — reporting only what’s really in your data.
Key takeaways
- Three pieces: a gather step, a writer step, and a sender step. The report goes out every Monday.
- Every figure is computed by plain Python from your data before any words are written.
- Each number is compared to last week and last month, so the report says what changed, not just what happened.
- One AI call a week turns the real numbers into a short paragraph; it never sources a number itself.
- Designed on AWS for about $2/month at typical small-business volume.
The whole system on one page
Before any code, here’s the shape of what we’re designing.
What you set up once (the outside)
- Your sources. The places your real numbers already live. A sales export in a Google Sheet, a Stripe or bank CSV dropped in a Drive folder, a point-of-sale daily summary. You don’t move any of this — you just point the builder at it. Each source is listed once in the config doc with where it lives, which columns hold the figures that matter (revenue, order count, new customers, refunds), and how often it updates. Adding a new source later is one new line in the doc.
- A config and voice folder. Two short Google Docs in a Drive folder. The config doc lists every source and the figures to pull from each, plus the thresholds that decide what counts as a notable change (“flag any week-over-week move bigger than 25%”) and what makes a number look off enough to flag. The voice doc holds the tone and shape of the summary paragraph — short, plain, owner-facing, lead with the headline number. Changing either is an edit to a doc, never a deploy.
- The owner. The person the report is for. They get the email every Monday morning in their own timezone, with the plain-English summary at the top, the numbers table right below it, the comparisons to last week and last month, and a Needs a look section for anything the checks flagged. No login, no dashboard to remember — it’s just in the inbox.
What runs every Monday (the inside)
- The gather. Reads each source. A small
source-syncLambda mirrors every source to S3 on a schedule, so the Monday run reads from S3 and never depends on a live API at 7am. The gather step normalizes each source into one clean shape, computes this week’s figures (total sales, order count, new customers, cash in and out, top items), and computes the same figures for last week and the four-week average — so every number arrives with its comparison already attached. This step is plain Python. No model. - The writer. Takes the already-computed figures and the comparisons and makes exactly one Bedrock Haiku 4.5 call to write a short paragraph: “here’s how the week went, here’s what changed.” The prompt hands the model the real numbers and tells it to describe only those numbers — no figure may appear in the summary that wasn’t computed in the gather step. The model writes prose, not data. Everything it says is checked back against the table before sending.
- The sender. Formats the email: the summary paragraph up top, the numbers table below it with this week / last week / four-week average columns, and the Needs a look section if anything tripped a check. Sends via SES outbound at the configured Monday time in the owner’s timezone. Every send is recorded in DynamoDB so the run is auditable and so next week’s comparison has a clean record of what was reported. A link in the footer hits a Function URL if the owner wants the full source table for any figure.
In plain words
It’s Monday, 7am. Over the weekend the bank export landed in the Drive folder, the point-of-sale rolled up Saturday’s numbers, and the sales sheet got its last few rows. The builder wakes up, reads all three, and works out the week: $18,400 in sales (up 12% on last week, a touch above the four-week average), 142 orders, 9 new customers (down from 14 — the slowest week in a month), cash out higher than usual because the quarterly insurance premium cleared. It writes one short paragraph saying exactly that, in plain words, and emails it to the owner with the table underneath. The owner reads it over coffee in ninety seconds and knows two things they wouldn’t otherwise: sales are fine, but new customers slowed down — worth a look at the ad spend.
The cost of running this is about $2 a month at SMB volume. The cost of not running it is the quarter where new-customer growth quietly stalled for six weeks and nobody noticed until the pipeline ran dry.
Design rules that shaped every decision
- Every figure is computed by plain Python from your data first. The writer only describes numbers that already exist.
- Every number ships with its comparison — last week and the four-week average — so the report says what changed.
- The summary and the table travel together. Any sentence can be checked against the numbers right below it.
- Anything missing, stale, or out of range is flagged, not guessed. A blank is never reported as a real result.
- The config lives in Drive. Adding a source or changing a threshold doesn’t need a deploy.
- Every send is logged. Pull up any past Monday and you can see exactly what was reported and why.
Why this shape
Most owners get their numbers in one of three ways: a stack of dashboards nobody opens, a bookkeeper’s month-end report that arrives three weeks too late to act on, or a gut feel that’s right until it isn’t. The dashboards fail because looking at them is a chore you have to remember to do. The month-end report fails because by the time it lands, the week it describes is ancient history. And gut feel fails the first week something moves quietly — new customers, refund rate, average order — in a direction the owner wasn’t watching.
The setup above leaves your numbers where they already live, but adds a small system that reads them every week and does the lining-up and comparing for you. The report comes early enough in the week to act on. It says what changed, not just what happened. It shows the table next to the words so you can trust any sentence. And it flags what looks off instead of papering over it. The builder is invisible six days a week; visible only on Monday, when it answers the one question the owner never has time to answer themselves.
The next four posts walk through each piece in turn: how the numbers get gathered, how the weekly report gets written, how the report reaches the owner, and how a number that looks off gets flagged. One diagram per post. A cost breakdown and a final engineering reference at the end.
All posts