Weekly KPI report by email
Every Monday, get a short summary of last week's key numbers from your sheet.
How it works
- Read last week's rows from your KPI sheet.
- Sum or average each metric and compare with the week before.
- Email a short report with ▲ / ▼ changes to you and your team.
Diagram
Code
JavaScript used in the Code step — already included in the template below.
// Sheet columns: date | revenue | orders | signups
const METRICS = ["revenue", "orders", "signups"];
const DAY = 24 * 60 * 60 * 1000;
const now = Date.now();
const rows = $input.all().map((item) => item.json);
const between = (fromDaysAgo, toDaysAgo) =>
rows.filter((r) => {
const t = new Date(r.date).getTime();
return t >= now - fromDaysAgo * DAY && t < now - toDaysAgo * DAY;
});
const total = (list, metric) => list.reduce((sum, r) => sum + Number(r[metric] || 0), 0);
const thisWeek = between(7, 0);
const lastWeek = between(14, 7);
const lines = METRICS.map((m) => {
const a = total(thisWeek, m);
const b = total(lastWeek, m);
const pct = b ? Math.round(((a - b) / b) * 100) : 0;
return `${m}: ${a} (${pct >= 0 ? "▲" : "▼"} ${Math.abs(pct)}% vs last week)`;
});
return [{ json: { subject: "Weekly KPI report", body: lines.join("\n") } }];Build it
Pick your tool — same automation, step by step.
Copy and paste straight into an n8n canvas (Ctrl+V), or download and use Import from File. Then connect your own credentials and replace every YOUR_… placeholder.
Show code (142 lines)
{
"name": "Weekly KPI report by email (Flowngn)",
"nodes": [
{
"parameters": {
"content": "## Weekly KPI report by email\nEvery Monday, get a short summary of last week's key numbers from your sheet.\n\n**Setup**\n1. Open each node and connect your own credentials.\n2. Replace every YOUR_… placeholder.\n3. Test once, then activate.\n\nFree template from Flowngn",
"height": 260,
"width": 340
},
"id": "3f36aaf0-8bb5-49c1-a1a3-cee797bcf94a",
"name": "Setup notes",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-420,
-60
]
},
{
"parameters": {
"rule": {
"interval": [
{
"field": "weeks",
"triggerAtDay": [
1
],
"triggerAtHour": 8
}
]
}
},
"id": "3f36aaf0-8bb5-49c1-a1a3-cee797bcf94a",
"name": "Every Monday 8:00",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
0,
0
]
},
{
"parameters": {
"operation": "read",
"documentId": {
"__rl": true,
"value": "YOUR_SPREADSHEET_ID",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "KPIs",
"mode": "name"
},
"options": {}
},
"id": "3f36aaf0-8bb5-49c1-a1a3-cee797bcf94a",
"name": "Read KPI sheet",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.5,
"position": [
300,
0
]
},
{
"parameters": {
"jsCode": "// Sheet columns: date | revenue | orders | signups\nconst METRICS = [\"revenue\", \"orders\", \"signups\"];\nconst DAY = 24 * 60 * 60 * 1000;\nconst now = Date.now();\n\nconst rows = $input.all().map((item) => item.json);\nconst between = (fromDaysAgo, toDaysAgo) =>\n rows.filter((r) => {\n const t = new Date(r.date).getTime();\n return t >= now - fromDaysAgo * DAY && t < now - toDaysAgo * DAY;\n });\nconst total = (list, metric) => list.reduce((sum, r) => sum + Number(r[metric] || 0), 0);\n\nconst thisWeek = between(7, 0);\nconst lastWeek = between(14, 7);\n\nconst lines = METRICS.map((m) => {\n const a = total(thisWeek, m);\n const b = total(lastWeek, m);\n const pct = b ? Math.round(((a - b) / b) * 100) : 0;\n return `${m}: ${a} (${pct >= 0 ? \"▲\" : \"▼\"} ${Math.abs(pct)}% vs last week)`;\n});\n\nreturn [{ json: { subject: \"Weekly KPI report\", body: lines.join(\"\\n\") } }];"
},
"id": "3f36aaf0-8bb5-49c1-a1a3-cee797bcf94a",
"name": "Compare weeks",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
600,
0
]
},
{
"parameters": {
"sendTo": "you@yourcompany.com",
"subject": "={{ $json.subject }}",
"emailType": "text",
"message": "={{ $json.body }}",
"options": {
"appendAttribution": false
}
},
"id": "3f36aaf0-8bb5-49c1-a1a3-cee797bcf94a",
"name": "Email report",
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.1,
"position": [
900,
0
]
}
],
"connections": {
"Every Monday 8:00": {
"main": [
[
{
"node": "Read KPI sheet",
"type": "main",
"index": 0
}
]
]
},
"Read KPI sheet": {
"main": [
[
{
"node": "Compare weeks",
"type": "main",
"index": 0
}
]
]
},
"Compare weeks": {
"main": [
[
{
"node": "Email report",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"meta": {
"templateCredsSetupCompleted": false
},
"tags": []
}In Make, create a new scenario and add these modules in order:
- Schedule the scenario: Every Monday 8:00
- Google Sheets › Search Rows
- Tools › Set variable / Iterator + Array aggregator (same logic as the Code step)
- Gmail › Send an Email
- Every module run uses Make operations — the free plan includes a monthly quota.
- Make's built-in functions (map, sum, formatDate…) can replace most Code steps; for complex logic use a code module.
- Module and step names can differ slightly as Make and Zapier update their apps.
In Zapier, create a new Zap and add these steps in order:
- Schedule by Zapier — Every Monday 8:00
- Google Sheets — Lookup Spreadsheet Rows (or Code by Zapier)
- Code by Zapier — Run JavaScript (paste the code from the Code section)
- Gmail — Send Email
- Zaps with more than two steps need a paid Zapier plan.
- Webhooks, Filters/Paths and Code by Zapier are premium features on some Zapier plans.
- Code by Zapier runs JavaScript: reuse the code shown above, reading inputs from inputData instead of $json.
- Module and step names can differ slightly as Make and Zapier update their apps.
Tips
- Add an AI step to write a two-sentence commentary on the trends.
Related flows
Automatic reminders for overdue invoices
Every morning, clients with unpaid overdue invoices get a polite reminder email.
AI reads receipt emails into your expense sheet
AI pulls the vendor, amount, currency and date out of receipt emails and logs them.
Monthly expenses by category, emailed to your accountant
On the 1st of every month, total last month's expenses by category and email the summary to your accountant.