This workflow corresponds to n8n.io template #9010 — we link there as the canonical source.
This workflow follows the Gmail → Gmail Trigger recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"nodes": [
{
"id": "631c332d-9840-419a-ae30-2717f7eee7a9",
"name": "Extract from File",
"type": "n8n-nodes-base.extractFromFile",
"position": [
-176,
288
],
"parameters": {
"options": {},
"operation": "xlsx",
"binaryPropertyName": "attachment_0"
},
"typeVersion": 1
},
{
"id": "84c8fd03-9fe3-4d7e-bd1d-db12fba92358",
"name": "Rename \"*_1\" keys for merge",
"type": "n8n-nodes-base.renameKeys",
"position": [
336,
192
],
"parameters": {
"keys": {
"key": [
{
"newKey": "start",
"currentKey": "Id\u0151b\u00e9lyeg"
},
{
"newKey": "AM",
"currentKey": "\u00c9rt\u00e9k_1"
}
]
},
"additionalOptions": {}
},
"typeVersion": 1
},
{
"id": "8131bd99-2aae-47ee-8065-f376ecbb86eb",
"name": "Get last 5 messages",
"type": "n8n-nodes-base.gmail",
"position": [
-1296,
320
],
"parameters": {
"limit": 5,
"filters": {
"sender": "user@example.com"
},
"operation": "getAll"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.1,
"alwaysOutputData": true
},
{
"id": "b939f7c5-3b21-4406-a9d3-f69125a96800",
"name": "Aggregate_id",
"type": "n8n-nodes-base.aggregate",
"position": [
-848,
336
],
"parameters": {
"options": {
"mergeLists": false
},
"fieldsToAggregate": {
"fieldToAggregate": [
{
"fieldToAggregate": "id"
},
{
"fieldToAggregate": "internalDate"
}
]
}
},
"typeVersion": 1
},
{
"id": "ddfbda91-1b00-4ab4-998c-93db8acd6a95",
"name": "Get a message[0]",
"type": "n8n-nodes-base.gmail",
"position": [
-624,
336
],
"parameters": {
"simple": false,
"options": {
"downloadAttachments": true
},
"messageId": "={{ $json.id[0] }}",
"operation": "get"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "249022c5-fc9d-4030-82f6-4065fe1c9ad7",
"name": "Rename \"*_1\" keys for merge1",
"type": "n8n-nodes-base.renameKeys",
"position": [
336,
384
],
"parameters": {
"keys": {
"key": [
{
"newKey": "start",
"currentKey": "Id\u0151b\u00e9lyeg"
},
{
"newKey": "1_8_0",
"currentKey": "\u00c9rt\u00e9k_2"
}
]
},
"additionalOptions": {}
},
"typeVersion": 1
},
{
"id": "42aac2f4-7b4b-4e48-91e2-ec9b4863443b",
"name": "Rename \"*_1\" keys for merge2",
"type": "n8n-nodes-base.renameKeys",
"position": [
336,
672
],
"parameters": {
"keys": {
"key": [
{
"newKey": "start",
"currentKey": "Id\u0151b\u00e9lyeg"
},
{
"newKey": "2_8_0",
"currentKey": "\u00c9rt\u00e9k_3"
}
]
},
"additionalOptions": {}
},
"typeVersion": 1
},
{
"id": "889c7b85-64c8-4f19-bd99-c2a56d6228b6",
"name": "Extract default data from source (+A)",
"type": "n8n-nodes-base.splitOut",
"position": [
112,
0
],
"parameters": {
"options": {},
"fieldToSplitOut": "Id\u0151b\u00e9lyeg,\u00c9rt\u00e9k"
},
"typeVersion": 1
},
{
"id": "c04db25d-4461-4db8-a420-c6a9a2fec53a",
"name": "Extract '*_1' data from source (-A)",
"type": "n8n-nodes-base.splitOut",
"position": [
112,
192
],
"parameters": {
"options": {},
"fieldToSplitOut": "Id\u0151b\u00e9lyeg,\u00c9rt\u00e9k_1"
},
"typeVersion": 1
},
{
"id": "091a5ad7-89b5-43f2-b59f-3abec3e13d7e",
"name": "Extract '*_2' data from source (1_8_0)",
"type": "n8n-nodes-base.splitOut",
"position": [
112,
384
],
"parameters": {
"options": {},
"fieldToSplitOut": "Id\u0151b\u00e9lyeg,\u00c9rt\u00e9k_2"
},
"typeVersion": 1
},
{
"id": "2a12d83f-353d-41ba-bb9e-137d56718052",
"name": "Extract '*_3' data from source (2_8_0)",
"type": "n8n-nodes-base.splitOut",
"position": [
112,
672
],
"parameters": {
"options": {},
"fieldToSplitOut": "Id\u0151b\u00e9lyeg,\u00c9rt\u00e9k_3"
},
"typeVersion": 1
},
{
"id": "cb8a13c8-e461-4ae0-80b6-b24d55b8197a",
"name": "Merge (+A; -A)",
"type": "n8n-nodes-base.merge",
"position": [
560,
96
],
"parameters": {
"mode": "combine",
"options": {},
"joinMode": "keepEverything",
"fieldsToMatchString": "['start']"
},
"typeVersion": 3.2
},
{
"id": "6e606abd-8594-4715-8c94-edfc0362798a",
"name": "Rename \"*_1\" keys for merge3",
"type": "n8n-nodes-base.renameKeys",
"position": [
336,
0
],
"parameters": {
"keys": {
"key": [
{
"newKey": "start",
"currentKey": "Id\u0151b\u00e9lyeg"
},
{
"newKey": "AP",
"currentKey": "\u00c9rt\u00e9k"
}
]
},
"additionalOptions": {}
},
"typeVersion": 1
},
{
"id": "8309f812-4ee1-4b71-ae15-019e0dc3cf87",
"name": "Merge (+A; -A)1",
"type": "n8n-nodes-base.merge",
"position": [
560,
480
],
"parameters": {
"mode": "combine",
"options": {},
"joinMode": "keepEverything",
"fieldsToMatchString": "['start']"
},
"typeVersion": 3.2
},
{
"id": "a59136fd-54c4-4965-94e0-ce89ef68ef97",
"name": "Merge (+A; -A)2",
"type": "n8n-nodes-base.merge",
"position": [
784,
192
],
"parameters": {
"mode": "combine",
"options": {},
"joinMode": "keepEverything",
"fieldsToMatchString": "['start']"
},
"typeVersion": 3.2
},
{
"id": "d3a58b39-51cd-41ff-b0a9-e8730119f92a",
"name": "Calculate hourly sum and",
"type": "n8n-nodes-base.code",
"position": [
1600,
192
],
"parameters": {
"jsCode": "function toNum(x, def = 0) {\n if (x === undefined || x === null || x === '') return def;\n const n = Number(x);\n return isNaN(n) ? def : n;\n}\n\n// --- group by hour ---\nconst grouped = {};\nfor (const item of items) {\n const j = item.json;\n const hour = j.start;\n\n if (!grouped[hour]) {\n grouped[hour] = { start: hour, AP: 0, AM: 0, m180: null, m280: null };\n }\n\n grouped[hour].AP += toNum(j.AP);\n grouped[hour].AM += toNum(j.AM);\n\n if (j['1_8_0'] !== undefined) grouped[hour].m180 = toNum(j['1_8_0']);\n if (j['2_8_0'] !== undefined) grouped[hour].m280 = toNum(j['2_8_0']);\n}\n\n// --- sort by time ---\nconst hours = Object.values(grouped).sort(\n (a, b) => Date.parse(a.start) - Date.parse(b.start)\n);\n\n// --- forward pass ---\nlet last180 = null;\nlet last280 = null;\n\nfor (const h of hours) {\n if (h.m180 !== null) last180 = h.m180;\n if (h.m280 !== null) last280 = h.m280;\n\n if (last180 === null) last180 = null; // defer until back-calc\n if (last280 === null) last280 = null;\n\n h.start180 = last180;\n h.start280 = last280;\n\n if (last180 !== null) last180 += h.AP;\n if (last280 !== null) last280 += h.AM;\n\n h.end180 = last180;\n h.end280 = last280;\n}\n\n\n// --- back calculation till first available 180 280 data ---\n\nconst firstMeterIndex = hours.findIndex(h => h.start180 !== null || h.start280 !== null);\n\nif (firstMeterIndex > 0) {\n\n // Take the first available meter values (if missing, rebuild them)\n let base180 = hours[firstMeterIndex].start180;\n let base280 = hours[firstMeterIndex].start280;\n\n // If one meter exists but the other doesn't, initialize cleanly.\n if (base180 === null) base180 = 0;\n if (base280 === null) base280 = 0;\n\n // Backwards reconstruction\n for (let i = firstMeterIndex - 1; i >= 0; i--) {\n const h = hours[i];\n\n base180 -= h.AP;\n base280 -= h.AM;\n\n h.start180 = base180;\n h.start280 = base280;\n h.end180 = base180 + h.AP;\n h.end280 = base280 + h.AM;\n }\n}\n\n\n// --- build output ---\n\nreturn hours.map(h => ({\n json: {\n start: h.start,\n AP: h.AP.toFixed(3),\n AM: h.AM.toFixed(3),\n '1_8_0': h.start180.toFixed(3),\n '2_8_0': h.start280.toFixed(3),\n }\n}));"
},
"typeVersion": 2
},
{
"id": "8fb7b834-6899-4c70-916d-1386fc2063db",
"name": "Spook: update +A hitorical data1",
"type": "n8n-nodes-base.homeAssistant",
"position": [
2416,
96
],
"parameters": {
"domain": "recorder",
"service": "import_statistics",
"resource": "service",
"operation": "call",
"serviceAttributes": {
"attributes": [
{
"name": "statistic_id",
"value": "sensor.grid_energy_import"
},
{
"name": "source",
"value": "recorder"
},
{
"name": "unit_of_measurement",
"value": "kWh"
},
{
"name": "has_mean",
"value": "={{false}}"
},
{
"name": "has_sum",
"value": "={{ true }}"
},
{
"name": "stats",
"value": "={{ $json.data }}"
}
]
}
},
"credentials": {
"homeAssistantApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "64031015-27d2-4ae5-9ccd-135ae36d7fcd",
"name": "Spook: update -A hitorical data1",
"type": "n8n-nodes-base.homeAssistant",
"position": [
2416,
288
],
"parameters": {
"domain": "recorder",
"service": "import_statistics",
"resource": "service",
"operation": "call",
"serviceAttributes": {
"attributes": [
{
"name": "statistic_id",
"value": "sensor.grid_energy_export"
},
{
"name": "source",
"value": "recorder"
},
{
"name": "unit_of_measurement",
"value": "kWh"
},
{
"name": "has_mean",
"value": "={{false}}"
},
{
"name": "has_sum",
"value": "={{ true }}"
},
{
"name": "stats",
"value": "={{ $json.data }}"
}
]
}
},
"credentials": {
"homeAssistantApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "49db5891-53f2-45ad-882c-ca65dc6b02ec",
"name": "Generate 1_8_0 list for stats",
"type": "n8n-nodes-base.set",
"position": [
1968,
96
],
"parameters": {
"mode": "raw",
"options": {},
"jsonOutput": "={\n \"start\": {{new Date($json.start)}},\n \"state\": {{ $json['1_8_0'] }},\n \"sum\": {{ $json['1_8_0'] }}\n}"
},
"typeVersion": 3.4
},
{
"id": "4e496d23-514a-4a0e-9903-ef0ded711fc6",
"name": "Generate 2_8_0 list for stats",
"type": "n8n-nodes-base.set",
"position": [
1968,
288
],
"parameters": {
"mode": "raw",
"options": {},
"jsonOutput": "={\n \"start\": {{new Date($json.start)}},\n \"state\": {{ $json['2_8_0'] }},\n \"sum\": {{ $json['2_8_0'] }}\n}"
},
"typeVersion": 3.4
},
{
"id": "ac9ea82a-3d2c-49ef-8440-82ab0aa47713",
"name": "Generate 1_8_0 stats",
"type": "n8n-nodes-base.aggregate",
"position": [
2192,
96
],
"parameters": {
"include": "specifiedFields",
"options": {},
"aggregate": "aggregateAllItemData",
"fieldsToInclude": "start,state, sum"
},
"typeVersion": 1
},
{
"id": "81712ea5-dfc9-4988-be79-238bb31f4a28",
"name": "Generate 2_8_0 stats",
"type": "n8n-nodes-base.aggregate",
"position": [
2192,
288
],
"parameters": {
"include": "specifiedFields",
"options": {},
"aggregate": "aggregateAllItemData",
"fieldsToInclude": "start,state, sum"
},
"typeVersion": 1
},
{
"id": "c86a07b6-5ade-4ba0-a317-e75da9c7c091",
"name": "Update input_number.exportt entity state1",
"type": "n8n-nodes-base.homeAssistant",
"position": [
2832,
288
],
"parameters": {
"state": "={{ $('Generate 2_8_0 stats').item.json.data.at(-1).state }}",
"entityId": "input_number.grid_export_meter",
"resource": "state",
"operation": "upsert"
},
"credentials": {
"homeAssistantApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "6a0092b7-172d-47a8-8fd2-1be198bf542e",
"name": "Update input_number.import entity state1",
"type": "n8n-nodes-base.homeAssistant",
"position": [
2832,
96
],
"parameters": {
"state": "={{ $('Generate 1_8_0 stats').item.json.data.at(-1).state }}",
"entityId": "input_number.grid_import_meter",
"resource": "state",
"operation": "upsert"
},
"credentials": {
"homeAssistantApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "ae61e4e5-4441-49a0-88a3-a660483cd958",
"name": "Gmail Trigger",
"type": "n8n-nodes-base.gmailTrigger",
"position": [
-1296,
512
],
"parameters": {
"filters": {
"sender": "user@example.com",
"labelIds": []
},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
}
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "b16e04c4-e7cc-4ea1-9328-ef26e9741602",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-1520,
320
],
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 14
}
]
}
},
"typeVersion": 1.2
},
{
"id": "96bed2da-2176-410f-9810-01a1607b07c7",
"name": "If attachment_0 is xlsx",
"type": "n8n-nodes-base.if",
"position": [
-400,
336
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "3b78aa20-1a72-4d43-9428-d754e9a51c55",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.subject }}",
"rightValue": "[EON-W1000]"
},
{
"id": "a046b65a-72b7-4eff-b97a-ad09acc1e753",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{$binary.attachment_0 ? true : false}}",
"rightValue": ""
},
{
"id": "4cfbc149-f429-4a68-a908-95e7db6906c6",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ [\"xls\", \"xlsx\"].includes($binary.attachment_0.fileExtension.toLowerCase()) }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "7f5378b1-aa34-4ba1-9375-302c4f242e64",
"name": "No Operation, do nothing1",
"type": "n8n-nodes-base.noOp",
"position": [
-176,
512
],
"parameters": {},
"typeVersion": 1
},
{
"id": "48003569-a556-4107-a48b-a9a13862e7f2",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1616,
-64
],
"parameters": {
"width": 1616,
"height": 896,
"content": "# Subject & Attachment checks\n- Gmail payload uses `Subject` (capital S), IMAP node uses `subject` (lowercase).\n- This template handles both: first **If** checks `$json.Subject` (Gmail), later **If** checks `$json.subject` (IMAP).\n- Attachment guard:\n - Ensures `attachment_0` exists\n - Ensures extension is `xls` or `xlsx`\n\n## Prerequisites\n- Configure your credentialt for the gmail and IMAP node (You can delete the IMAP node if you're not planning to use it)\n- Check the nodes for the correct `subject` and for the correct `sender`"
},
"typeVersion": 1
},
{
"id": "b4db1439-e373-411c-aacd-cf4757af58f6",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
32,
-336
],
"parameters": {
"width": 912,
"height": 1168,
"content": "# Column Mapping\nThe E.ON export reuses column names; n8n appends `_1`, `_2`, `_3`.\nWe normalize to:\n\n- Default (+A): `Id\u0151b\u00e9lyeg` \u2192 `start`, `\u00c9rt\u00e9k` \u2192 `AP`\n- *_1 (-A): `Id\u0151b\u00e9lyeg` \u2192 `start`, `\u00c9rt\u00e9k_1` \u2192 `AM`\n- *_2 (1_8_0): `Id\u0151b\u00e9lyeg` \u2192 `start`, `\u00c9rt\u00e9k_2` \u2192 `1_8_0`\n- *_3 (2_8_0): `Id\u0151b\u00e9lyeg` \u2192 `start`, `\u00c9rt\u00e9k_3` \u2192 `2_8_0`\n\nThree **Merge** nodes combine on `start`.\n"
},
"typeVersion": 1
},
{
"id": "2fc06f65-1720-414d-a945-3fd938f4e857",
"name": "Email Trigger (IMAP)",
"type": "n8n-nodes-base.emailReadImap",
"position": [
-624,
672
],
"parameters": {
"options": {
"customEmailConfig": "=[\"UNSEEN\",[\"OR\",[\"FROM\",\"noreply@eon.com\"],[\"SUBJECT\",\"[EON-W1000]\"]]]"
},
"downloadAttachments": true
},
"typeVersion": 2.1
},
{
"id": "a4e84c72-f998-45f3-949b-f55f3da3730a",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1616,
-848
],
"parameters": {
"color": 2,
"width": 688,
"height": 752,
"content": "# E.ON W1000 \u2192 n8n \u2192 Home Assistant (Spook) \u2014 Overview\n\n**Goal:** Parse E.ON W1000 email export (.xlsx), group 15-min +A/-A into hourly totals, reconstruct 1.8.0 / 2.8.0 hourly meter states, and push long-term stats into Home Assistant via `recorder.import_statistics`. Also update `input_number.grid_import_meter` / `input_number.grid_export_meter`.\n\n**High-level path**\n1) Triggers:\n - **Gmail Trigger** *or* **Email Trigger (IMAP)** \u2192 subject must match `[EON-W1000]` or the one that you set in the [E.ON e-portal](https://e-portal.eon-hungaria.com/w1000)\n - **Schedule Trigger** optional daily poll \u2192 runs **Get last 5 messages** + filter.\n2) Check for correct attachment:\n - **If** checks the subject; **If attachment_0 is xlsx** validates and ensures `.xls`/`.xlsx`.\n3) Parsing & shaping:\n - **Extract from File (xlsx)** \u2192 4 \u00d7 **Split Out** by columns \u2192 4 \u00d7 **Rename keys** \u2192 **Merge**\u00d73 \u2192 shaped rows `{ start, AP, AM, 1_8_0?, 2_8_0? }`.\n - **Convert Excel time** \u2192 serial date \u2192 **Convert datetime to Spook format** (`yyyy-MM-dd HH:00:00ZZ`).\n - **Code** node \u201cCalculate hourly sum and\u201d \u2192 groups 15-min into hourly sums; fills forward meter states.\n4) Stats payload:\n - **Generate 1_8_0 list for stats** and **Generate 2_8_0 list for stats** \u2192 `{start, state, sum}` lists.\n - **Aggregate** each list to `data` array \u2192 **Home Assistant: recorder.import_statistics** (two calls).\n - Finally updates `input_number.*_meter` with last state value.\n"
},
"typeVersion": 1
},
{
"id": "de554edc-2b14-4a22-b6e3-b890962e8142",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
-288,
-1184
],
"parameters": {
"color": 2,
"width": 624,
"height": 192,
"content": "# Credentials to configure\n- **Gmail OAuth2** *or* IMAP credentials (read-only)\n- **Home Assistant API** (Long-Lived Access Token)\n- Optional: adjust entity IDs if you renamed them in HA\n\n> Tip: Keep OAuth scopes minimal (read email only). Store secrets in n8n credentials, not in node parameters.\n"
},
"typeVersion": 1
},
{
"id": "6051e276-de9e-4629-8af3-3c278d301507",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1600,
272
],
"parameters": {
"color": 5,
"height": 208,
"content": "## Schedule trigger"
},
"typeVersion": 1
},
{
"id": "69312661-e8bb-45e4-ba65-056805504df5",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1376,
464
],
"parameters": {
"color": 5,
"height": 208,
"content": "## Gmail trigger"
},
"typeVersion": 1
},
{
"id": "07ef2161-472f-4d58-a0e3-d7cf56e40b91",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
-704,
624
],
"parameters": {
"color": 5,
"height": 208,
"content": "## IMAP trigger"
},
"typeVersion": 1
},
{
"id": "41d12eca-181d-4ed3-b0df-663659815142",
"name": "Check Email Subject",
"type": "n8n-nodes-base.if",
"position": [
-1072,
416
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "3b78aa20-1a72-4d43-9428-d754e9a51c55",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.Subject }}",
"rightValue": "[EON-W1000]"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "882d8e14-710c-4869-83d1-0ac06cb9c9c6",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
944,
-16
],
"parameters": {
"width": 448,
"height": 368,
"content": "# Time Conversion\n- E.ON Excel time is a serial day count; **Convert Excel time** adds to `1899-12-30`.\n- **Convert datetime to Spook format** rounds to the top of the hour and formats ISO with offset.\n- If your HA runs in a different timezone, ensure n8n\u2019s instance timezone matches HA, or adjust here.\n"
},
"typeVersion": 1
},
{
"id": "47b0ac45-6718-426c-8c5d-469d948daf94",
"name": "Convert datetime to Spook format",
"type": "n8n-nodes-base.dateTime",
"position": [
1232,
192
],
"parameters": {
"date": "={{ $json['start'] }}",
"format": "custom",
"options": {
"includeInputFields": true
},
"operation": "formatDate",
"customFormat": "yyyy-MM-dd HH:00:00ZZ",
"outputFieldName": "start"
},
"typeVersion": 2
},
{
"id": "b4f079d8-e58c-45d6-8a59-1954729d3c37",
"name": "Convert Excel time",
"type": "n8n-nodes-base.dateTime",
"position": [
1008,
192
],
"parameters": {
"options": {
"includeInputFields": true
},
"duration": "={{ $json['start'] + 0.00000001}}",
"timeUnit": "=days",
"magnitude": "1899-12-30",
"operation": "addToDate",
"outputFieldName": "start"
},
"typeVersion": 2
},
{
"id": "99a605b5-0e6c-41ca-aea6-394ef8b0b028",
"name": "Sticky Note8",
"type": "n8n-nodes-base.stickyNote",
"position": [
1408,
-288
],
"parameters": {
"width": 448,
"height": 640,
"content": "# Hourly Grouping Logic (Code node)\nINPUT: `{ start, AP, AM, 1_8_0?, 2_8_0? }` at hourly timestamps\n- Sums 15-min AP/AM into hourly totals\n- Resets meter baselines when a new `1_8_0`/`2_8_0` reading is present\n- If missing at the very beginning, initializes from 0 (edit here if you prefer to drop earliest hour)\n\nOUTPUT: rows like\n```\n{\n start: \"2025-09-01 10:00:00+02:00\",\n AP: \"0.975\",\n AM: \"0.000\",\n \"1_8_0\": \"32749.288\",\n \"2_8_0\": \"39627.868\"\n}\n```"
},
"typeVersion": 1
},
{
"id": "f93df5c7-2d9a-4a0b-aebe-ebc51d0bd488",
"name": "Sticky Note9",
"type": "n8n-nodes-base.stickyNote",
"position": [
1872,
-144
],
"parameters": {
"width": 816,
"height": 608,
"content": "# recorder.import_statistics payload\nWe build `{ start, state, sum }` arrays for each meter:\n- **start**: JS Date object\n- **state**: current meter state (kWh)\n- **sum**: same as state for total_increasing counters\n\nService: `recorder.import_statistics` \u2192 updates long-term stats\nRefs: Home Assistant Recorder docs. \n"
},
"typeVersion": 1
},
{
"id": "0884b829-04ad-4643-a412-ef2a986d242a",
"name": "Sticky Note10",
"type": "n8n-nodes-base.stickyNote",
"position": [
2704,
-176
],
"parameters": {
"width": 448,
"height": 640,
"content": "# Entity IDs updated\n- `sensor.grid_energy_import` / `sensor.grid_energy_export` \u2192 long-term stats via recorder\n- `input_number.grid_import_meter` / `input_number.grid_export_meter` \u2192 last known meter states\n\nIf you renamed entities, update here:\n- Home Assistant nodes: `statistic_id` and `entityId` fields\n- Template sensors in HA must be `device_class: energy` and `state_class: total_increasing`\n"
},
"typeVersion": 1
},
{
"id": "d7742880-2443-45eb-8838-859c6f613643",
"name": "Sticky Note11",
"type": "n8n-nodes-base.stickyNote",
"position": [
-288,
-768
],
"parameters": {
"color": 2,
"width": 624,
"height": 272,
"content": "# Troubleshooting\n- No rows after Extract? \u2192 Check that the email subject is `[EON-W1000]` and attachment is `.xlsx`.\n- Wrong times? \u2192 Verify timezone and Excel serial conversion (leap day quirks and locale).\n- HA not showing stats? \u2192 Confirm Recorder is enabled and entities are `total_increasing`.\n- 400/401 from HA node? \u2192 Regenerate Long-Lived Access Token; re-add credential in n8n.\n- Duplicate imports? \u2192 The code groups per hour; imports are idempotent if the same hour/state is sent.\n"
},
"typeVersion": 1
},
{
"id": "4a0c69b2-8e61-434e-aa6b-54e6b31975f8",
"name": "Sticky Note12",
"type": "n8n-nodes-base.stickyNote",
"position": [
-288,
-976
],
"parameters": {
"color": 2,
"width": 624,
"height": 192,
"content": "# Security & Privacy\n- Gmail/IMAP access is read-only; do not store raw attachments long-term.\n- Filter by sender (`noreply@eon.com`) + subject token `[EON-W1000]`.\n- Never hardcode tokens in nodes \u2014 use n8n Credentials."
},
"typeVersion": 1
},
{
"id": "7a95c640-56c2-4e8b-a1bd-923200046f94",
"name": "Sticky Note13",
"type": "n8n-nodes-base.stickyNote",
"position": [
-912,
-1184
],
"parameters": {
"color": 2,
"width": 608,
"height": 1088,
"content": "# Home Assistant prerequisites\n\nBefore running this workflow, make sure HA is prepared:\n\n1. **Recorder enabled** \n - Recorder integration must be active (default if you use Energy dashboard).\n\n2. **Spook integration installed** \n - Required for `recorder.import_statistics` service calls. \n - Install via HACS: search for \"Spook\". \n - Restart HA after installation.\n\n* **Helpers (input_number)** \nAdd these to your configuration.yaml (or via UI \u2192 Helpers):\n\n```yaml\ninput_number:\n grid_import_meter:\n name: grid_import_meter\n mode: box\n initial: 0\n min: 0\n max: 9999999999\n step: 0.001\n unit_of_measurement: kWh\n grid_export_meter:\n name: grid_export_meter\n mode: box\n initial: 0\n min: 0\n max: 9999999999\n step: 0.001\n unit_of_measurement: kWh\n```\n* **template sensors (sensor)**\nAdd these to your configuration.yaml (or via UI \u2192 Helpers):\n```yaml\ntemplate:\n - sensor:\n - name: \"grid_energy_import\"\n state: \"{{ states('input_number.grid_import_meter') | float(0) }}\"\n unit_of_measurement: \"kWh\"\n device_class: energy\n state_class: total_increasing\n - name: \"grid_energy_export\"\n state: \"{{ states('input_number.grid_export_meter') | float(0) }}\"\n unit_of_measurement: \"kWh\"\n device_class: energy\n state_class: total_increasing\n```\n\n[](https://my.home-assistant.io/redirect/helpers/)"
},
"typeVersion": 1
}
],
"connections": {
"Aggregate_id": {
"main": [
[
{
"node": "Get a message[0]",
"type": "main",
"index": 0
}
]
]
},
"Gmail Trigger": {
"main": [
[
{
"node": "Check Email Subject",
"type": "main",
"index": 0
}
]
]
},
"Merge (+A; -A)": {
"main": [
[
{
"node": "Merge (+A; -A)2",
"type": "main",
"index": 0
}
]
]
},
"Merge (+A; -A)1": {
"main": [
[
{
"node": "Merge (+A; -A)2",
"type": "main",
"index": 1
}
]
]
},
"Merge (+A; -A)2": {
"main": [
[
{
"node": "Convert Excel time",
"type": "main",
"index": 0
}
]
]
},
"Get a message[0]": {
"main": [
[
{
"node": "If attachment_0 is xlsx",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "Get last 5 messages",
"type": "main",
"index": 0
}
]
]
},
"Extract from File": {
"main": [
[
{
"node": "Extract default data from source (+A)",
"type": "main",
"index": 0
},
{
"node": "Extract '*_1' data from source (-A)",
"type": "main",
"index": 0
},
{
"node": "Extract '*_2' data from source (1_8_0)",
"type": "main",
"index": 0
},
{
"node": "Extract '*_3' data from source (2_8_0)",
"type": "main",
"index": 0
}
]
]
},
"Convert Excel time": {
"main": [
[
{
"node": "Convert datetime to Spook format",
"type": "main",
"index": 0
}
]
]
},
"Check Email Subject": {
"main": [
[
{
"node": "Aggregate_id",
"type": "main",
"index": 0
}
],
[
{
"node": "No Operation, do nothing1",
"type": "main",
"index": 0
}
]
]
},
"Get last 5 messages": {
"main": [
[
{
"node": "Check Email Subject",
"type": "main",
"index": 0
}
]
]
},
"Email Trigger (IMAP)": {
"main": [
[
{
"node": "If attachment_0 is xlsx",
"type": "main",
"index": 0
}
]
]
},
"Generate 1_8_0 stats": {
"main": [
[
{
"node": "Spook: update +A hitorical data1",
"type": "main",
"index": 0
}
]
]
},
"Generate 2_8_0 stats": {
"main": [
[
{
"node": "Spook: update -A hitorical data1",
"type": "main",
"index": 0
}
]
]
},
"If attachment_0 is xlsx": {
"main": [
[
{
"node": "Extract from File",
"type": "main",
"index": 0
}
],
[
{
"node": "No Operation, do nothing1",
"type": "main",
"index": 0
}
]
]
},
"Calculate hourly sum and": {
"main": [
[
{
"node": "Generate 1_8_0 list for stats",
"type": "main",
"index": 0
},
{
"node": "Generate 2_8_0 list for stats",
"type": "main",
"index": 0
}
]
]
},
"Rename \"*_1\" keys for merge": {
"main": [
[
{
"node": "Merge (+A; -A)",
"type": "main",
"index": 1
}
]
]
},
"Rename \"*_1\" keys for merge1": {
"main": [
[
{
"node": "Merge (+A; -A)1",
"type": "main",
"index": 0
}
]
]
},
"Rename \"*_1\" keys for merge2": {
"main": [
[
{
"node": "Merge (+A; -A)1",
"type": "main",
"index": 1
}
]
]
},
"Rename \"*_1\" keys for merge3": {
"main": [
[
{
"node": "Merge (+A; -A)",
"type": "main",
"index": 0
}
]
]
},
"Generate 1_8_0 list for stats": {
"main": [
[
{
"node": "Generate 1_8_0 stats",
"type": "main",
"index": 0
}
]
]
},
"Generate 2_8_0 list for stats": {
"main": [
[
{
"node": "Generate 2_8_0 stats",
"type": "main",
"index": 0
}
]
]
},
"Convert datetime to Spook format": {
"main": [
[
{
"node": "Calculate hourly sum and",
"type": "main",
"index": 0
}
]
]
},
"Spook: update +A hitorical data1": {
"main": [
[
{
"node": "Update input_number.import entity state1",
"type": "main",
"index": 0
}
]
]
},
"Spook: update -A hitorical data1": {
"main": [
[
{
"node": "Update input_number.exportt entity state1",
"type": "main",
"index": 0
}
]
]
},
"Extract '*_1' data from source (-A)": {
"main": [
[
{
"node": "Rename \"*_1\" keys for merge",
"type": "main",
"index": 0
}
]
]
},
"Extract default data from source (+A)": {
"main": [
[
{
"node": "Rename \"*_1\" keys for merge3",
"type": "main",
"index": 0
}
]
]
},
"Extract '*_2' data from source (1_8_0)": {
"main": [
[
{
"node": "Rename \"*_1\" keys for merge1",
"type": "main",
"index": 0
}
]
]
},
"Extract '*_3' data from source (2_8_0)": {
"main": [
[
{
"node": "Rename \"*_1\" keys for merge2",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
gmailOAuth2homeAssistantApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
2025-12-03 fix JS code in node
Source: https://n8n.io/workflows/9010/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
The goal is to reduce inbox noise and automatically organize repetitive types of emails so that imprtant messages remain visible while unsolicited or promotional emails are handled automatically. When
This template is built to be customized for your specific needs. This template has the core logic and n8n node specific references sorted to work with dynamic file names throughout the workflow. Store
This is an elite enterprise-grade solution for Talent Acquisition and HR Ops teams. It automates the high-volume task of resume screening by transforming unstructured PDF applications into structured
📘 Description
📩🤖 This workflow automatically processes emails received in Gmail, extracts their attachments, and organizes them into specific folders in Google Drive based on the sender's email address.