Every other build on this site has the same failure mode.
It stops producing output. Nothing throws, nothing turns red, no email arrives saying anything is wrong. The workflow runs on its schedule, finds nothing to do because a field was renamed or a credential expired, and finishes successfully having done nothing at all.
And an absence of output looks exactly like a quiet week.
Why an error workflow is not enough
You should have one. n8n lets you set an error workflow that fires when an execution fails, and it catches the loud failures.
It cannot catch the quiet ones, because nothing failed.
Here is the shape of it in practice. A Gmail trigger filters on has:attachment filename:pdf. A supplier switches to sending invoices as .PDF in capitals, or embeds them differently, and the filter stops matching. The workflow runs every fifteen minutes, forever, finding zero messages, succeeding every single time. Your invoice folder stops growing. Nobody notices until the quarter.
An error alert is silent throughout, correctly, because there was no error.
What to count
Not executions. Items.
An execution count tells you the workflow ran, which the schedule already guaranteed. What you want is how many things it actually processed: invoices filed, rows added, alerts sent, leads captured.
n8n’s API gives you executions, and you can either count items from the execution data or, more reliably, have each workflow write a one-line record of its own work into a sheet as it goes. The second is more work up front and much easier to trust, because it counts what you care about rather than what the platform happens to expose.
Start with the API version. Move to self-reported counts for the two or three workflows that really matter.
The build, six nodes
1. Schedule Trigger. Monday morning, once a week.
2. HTTP Request, called Executions. Against your own n8n:
GET https://your-n8n-host/api/v1/executions?limit=250&includeData=false
X-N8N-API-KEY: your keyCreate the key in Settings, then n8n API. Give it a label and an expiry, and put the expiry in your calendar, because an expired key makes the heartbeat itself fail without saying so, which would be a particularly annoying way to prove the point of this build.
includeData=false keeps the response small. You want counts, not payloads.
3. Code node, called Tally. Group by workflow and by week:
const runs = ($input.first().json.data || []);
const now = Date.now();
const week = 7 * 86400000;
const bucket = (t) => {
const age = now - new Date(t).getTime();
if (age < week) return 'this';
if (age < week * 2) return 'last';
return null;
};
const tally = {};
for (const r of runs) {
const b = bucket(r.startedAt);
if (!b) continue;
const name = r.workflowName || r.workflowId;
tally[name] = tally[name] || { this: 0, last: 0, failed: 0 };
tally[name][b]++;
if (b === 'this' && r.status === 'error') tally[name].failed++;
}
return Object.entries(tally).map(([name, counts]) => ({
json: { workflow: name, ...counts, change: counts.this - counts.last },
}));4. Code node, called Expected. The most important node here, and the one no API can give you.
A list you write by hand of which workflows should never report zero:
const EXPECTED = ['Invoice filing', 'Downtime check', 'Weekly positions'];
const seen = $input.all().map((i) => i.json.workflow);
const silent = EXPECTED.filter((name) => !seen.includes(name));
return [{ json: { silent, rows: $input.all().map((i) => i.json) } }];A workflow that ran zero times does not appear in the API response at all, so it cannot be missing from a list you did not write. Everything else in this build is arithmetic. This is the bit that carries the knowledge.
5. Code node, called Compose. Build the message. One line per workflow, this week against last, and the silent list at the top rather than the bottom.
SILENT THIS WEEK
Invoice filing expected daily, ran 0 times
Invoice filing 0 (last week 63)
Downtime check 2016 (last week 2011)
Weekly positions 1 (last week 1)
Lead alerts 11 (last week 9)6. Send Email, Slack or Telegram. Once a week, to somebody who will read it.
Read the zeros, not the totals
The instinct is to look at the big numbers and feel good. The big numbers are the ones that were always going to be fine.
The line that matters is the one that says zero, or the one that dropped by ninety percent. Set yourself one habit: scan the column for zeros and small numbers first, and only then look at the rest.
Better still, put the silent list at the top and in a different shape from the table, so the week it has an entry the email looks different at a glance. A message that looks identical every week gets skimmed identically every week.
The month it earns its keep
There will be a week when a line says zero and you will assume it is a bug in the heartbeat.
It usually is not. Check the workflow before you check the counter, because the counter is simple and the workflow has eight nodes and a credential in it.
Ours has caught two real failures this way, both of them the same shape: something upstream changed its output format, the workflow carried on running, and nothing anywhere threw an error. Both would have gone unnoticed for weeks without a number that should not have been zero.
Build this one first, honestly
It is the least interesting thing in this library and it is the one that makes everything else trustworthy.
Every other build here is a thing you set up and then stop thinking about, which is the point of them and also the risk. Without something counting, “I automated that months ago” and “that stopped working months ago” feel exactly the same from the inside.
An afternoon, once. Then the silence has to earn itself.
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 knowing when something breaks: The website downtime alert. Or go back to all 4 in this category.