{
  "name": "Daily KPI Email from Google Sheets",
  "nodes": [
    {
      "parameters": {
        "content": "## Daily KPI Email Report from Google Sheets\n\nYour key numbers - sales, signups, ad spend, whatever lives in your sheet - in your inbox at 8am every day without opening a spreadsheet.\n\n**Setup time:** 10 minutes  ·  **Level:** Beginner\n\n**You'll need accounts/credentials for:** Schedule, Google Sheets, Gmail\n\n**Replace these before running:**\n- `REPLACE_WITH_SPREADSHEET`\n\n**Setup steps:**\n1. Connect Google Sheets and select your spreadsheet and sheet - any columns work, the email builds itself from whatever is there.\n2. Connect Gmail and set the recipient address in the send node.\n3. Adjust the schedule to your preferred hour.\n4. Run once manually to see your first report, then activate.\n\nFree templates and updates: navkhan103.github.io",
        "height": 596,
        "width": 400,
        "color": 4
      },
      "id": "sticky-setup",
      "name": "Setup — read me first",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -440,
        -40
      ]
    },
    {
      "parameters": {
        "content": "### How it runs\n\nStarts from **Every Morning 8am**.\n\n1. A Schedule Trigger fires every morning at 8:00.\n2. A Google Sheets node reads your KPI sheet.\n3. A Code node grabs the most recent row and builds a small HTML table from its columns.\n4. A Gmail node sends the report to you (or a whole team list).",
        "height": 348,
        "width": 400,
        "color": 7
      },
      "id": "sticky-how",
      "name": "How it runs",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -440,
        596
      ]
    },
    {
      "parameters": {
        "content": "### If something breaks\n\nThe read and AI nodes here **retry 3× automatically** (5s apart), which absorbs rate limits and brief timeouts.\n\nNodes that **send or write deliberately do not retry** — a send that reached the far end but lost its response would otherwise fire twice, so you'd get duplicate messages or duplicate rows.\n\nTo be told when one of those fails, build a workflow starting with an **Error Trigger** node that posts to Slack or email, then set it under **three-dot menu > Settings > Error Workflow**. Do it once and point every workflow at it.",
        "height": 400,
        "width": 400,
        "color": 3
      },
      "id": "sticky-fail",
      "name": "If something breaks",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -440,
        984
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * *"
            }
          ]
        }
      },
      "id": "d0e1f2a3-0001-4000-8000-000000000001",
      "name": "Every Morning 8am",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "REPLACE_WITH_SPREADSHEET"
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "Sheet1"
        },
        "options": {}
      },
      "id": "d0e1f2a3-0002-4000-8000-000000000002",
      "name": "Read KPI Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        220,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "const rows = $input.all().map(i => i.json);\nconst latest = rows[rows.length - 1];\nconst cells = Object.entries(latest).map(([k, v]) => `<tr><td style=\"padding:6px 14px;border:1px solid #ddd;font-weight:600\">${k}</td><td style=\"padding:6px 14px;border:1px solid #ddd\">${v}</td></tr>`).join('');\nconst html = `<h2>Daily KPI Report</h2><table style=\"border-collapse:collapse;font-family:Arial\">${cells}</table><p style=\"color:#888;font-size:12px\">Sent automatically by n8n.</p>`;\nreturn [{ json: { html } }];"
      },
      "id": "d0e1f2a3-0003-4000-8000-000000000003",
      "name": "Build HTML Report",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        440,
        0
      ]
    },
    {
      "parameters": {
        "sendTo": "you@example.com",
        "subject": "=Daily KPI report - {{ $now.toFormat('ccc d LLL') }}",
        "message": "={{ $json.html }}",
        "options": {}
      },
      "id": "d0e1f2a3-0004-4000-8000-000000000004",
      "name": "Email Report",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        660,
        0
      ]
    }
  ],
  "connections": {
    "Every Morning 8am": {
      "main": [
        [
          {
            "node": "Read KPI Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read KPI Sheet": {
      "main": [
        [
          {
            "node": "Build HTML Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build HTML Report": {
      "main": [
        [
          {
            "node": "Email Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}