Weekly KPI report by email

Every Monday, get a short summary of last week's key numbers from your sheet.

Medium Saves ~3 hours/month Finance & AdminBy Flowngn team

Works with n8n · Make · Zapier

How it works

Trigger: Schedule — every Monday at 8:00
  1. Read last week's rows from your KPI sheet.
  2. Sum or average each metric and compare with the week before.
  3. Email a short report with ▲ / ▼ changes to you and your team.

Diagram

Every Monday 8:00Schedule TriggerGRead KPI sheetGoogle Sheets{ }Compare weeksCodeGEmail reportGmail
Trigger App Code

Code

JavaScript used in the Code step — already included in the template below.

Compare weeks · Code node
// 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.

Workflow JSON
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": []
}

Open in the playground to edit →

Tips

  • Add an AI step to write a two-sentence commentary on the trends.

Related flows