Every accounting package on earth will produce you a monthly summary. Most small businesses still do not read one, because it arrives in a portal they have to log into and it opens on a screen with fourteen other numbers on it.
This one arrives as an email, on the third of the month, and it is short enough to read on a phone.
What goes in it
Four sections, in this order, and the order is deliberate.
What is missing. First, because it is the only part that asks you to do something.
The total, and last month’s next to it.
Where it went, by supplier, biggest first, top ten and then an “everything else” line.
Anything odd. A payment more than double what that supplier usually gets, a new supplier appearing for the first time, a duplicate that looks like the same invoice twice.
Notice what is not first. Most generated reports open with a big friendly total, which is decoration: you cannot act on it and by the third month you stop reading past it. Opening with the gaps means the email always has a point.
The build, six nodes and an optional seventh
1. Schedule Trigger. Cron for the third of the month at eight in the morning. The third rather than the first, because the first is often a weekend and a bank holiday, and because a few stragglers arrive in the first two days.
2. Google Sheets, Get Rows. The whole sheet. Filtering by date in the sheet node is fiddly and the next node has to look at two months anyway.
3. Code node, called Crunch. All the arithmetic lives here. No model, no rounding surprises, no cost:
const rows = $input.all().map((i) => i.json);
const month = (d) => String(d || '').slice(0, 7);
const now = new Date();
const thisM = new Date(now.getFullYear(), now.getMonth() - 1, 1)
.toISOString().slice(0, 7);
const prevM = new Date(now.getFullYear(), now.getMonth() - 2, 1)
.toISOString().slice(0, 7);
const inMonth = (m) => rows.filter((r) => month(r.date) === m);
const sum = (rs) => rs.reduce((t, r) => t + Number(r.total || 0), 0);
const cur = inMonth(thisM);
const bySupplier = {};
for (const r of cur) {
const key = (r.supplier || 'unknown').trim();
bySupplier[key] = (bySupplier[key] || 0) + Number(r.total || 0);
}
const EXPECTED = ['Rent', 'Insurance', 'Van lease', 'Mobile'];
const seen = Object.keys(bySupplier).map((s) => s.toLowerCase());
const missing = EXPECTED.filter(
(e) => !seen.some((s) => s.includes(e.toLowerCase()))
);
return [{ json: {
month: thisM,
total: sum(cur).toFixed(2),
previous: sum(inMonth(prevM)).toFixed(2),
count: cur.length,
missing,
suppliers: Object.entries(bySupplier)
.sort((a, b) => b[1] - a[1])
.map(([name, amount]) => ({ name, amount: amount.toFixed(2) })),
}}];EXPECTED is the list you have to write yourself, and it is the most valuable four lines in the workflow. Everything you pay every month without fail. Rent, insurance, the lease, the phone. If one of those has no row, either the invoice never arrived or it never got filed, and both are worth knowing on the third rather than in April.
4. Code node, called Odd Things. Flags anything more than double that supplier’s three-month average, any supplier appearing for the first time, and any two rows with the same supplier, date and amount. Duplicates are the most common real error in these sheets and the easiest to miss.
5. Basic LLM Chain, optional, and not in the template. One sentence at the top, and only if you want it. Hand it the finished figures and ask for a summary in one or two sentences, plainly, no adjectives. Never ask it to do the arithmetic, and if it starts producing sentences like “spending remained broadly stable”, drop this node. It is not earning its place.
6. Code node, called Compose. Build the email as simple HTML. A heading, the missing list as bullets, the total against last month, a table of suppliers. Keep it plain: this is read on a phone by somebody with a mug in the other hand.
7. Send Email. To yourself, and to your bookkeeper if you have one. They will spot a gap you skim past.
The section that earns it
The missing list.
Everything else in this email you could get from a spreadsheet in about ninety seconds. The missing list is a comparison against a fact only you know, which is which suppliers bill you monthly without exception, and no software can work that out for you because there is nothing in the data that says so.
Keep it short. Four to eight names. Adding everything you have ever paid turns it into a list of false alarms and you will stop reading it by March.
What it cannot see
Anything that never got captured.
This is the honest limit on the whole of this category. The workflow reads a sheet, and the sheet holds what the filing builds put in it. A receipt somebody left in a jacket pocket does not appear, and the summary will present a total with total confidence and be wrong by the amount of that receipt.
The missing-suppliers list closes part of that gap. Nothing closes all of it. If you want the number to be right, somebody still has to photograph the receipt, and no amount of automation moves that job.
One change worth making after three months
Add the previous three months, not just one, and put them side by side.
Month to month is noisy in a small business. One quarter’s insurance renewal makes August look alarming and September look brilliant, and neither is true. Four months in a row is where the actual shape shows up, and it costs one extra line in the Crunch node.
Published 28 August 2026. Written by the people who run this sort of thing for clients, on n8n, including our own lead pipeline.
Next in paperwork that files itself: Invoices out of the inbox, into a folder and a sheet. Or go back to all 4 in this category.