{ "updatedAt": "2026-05-14T00:02:05.677Z", "createdAt": "2026-03-18T05:20:48.223Z", "id": "9sFwRyUDz51csAp7", "name": "IMAP Inbox Triage + Obsidian Notes", "description": null, "active": true, "isArchived": false, "nodes": [ { "parameters": { "rule": { "interval": [ { "field": "minutes", "minutesInterval": 15 } ] } }, "id": "n1", "name": "Schedule Trigger", "type": "n8n-nodes-base.scheduleTrigger", "typeVersion": 1, "position": [ 240, 304 ] }, { "parameters": { "jsCode": "// DEFINITE NOISE - never worth seeing\nconst NOISE_SENDERS = [\n 'discord', 'plex', 'spotify', 'youtube',\n 'lodge at redmond ridge', 'flex +',\n 'seattle jeep',\n 'no-reply', 'noreply', 'do-not-reply', 'donotreply',\n 'newsletter', 'marketing',\n];\nconst NOISE_SUBJECTS = [\n 'bulletin board', 'daily digest', 'weekly digest',\n 'most watchlisted', 'newsletter',\n 'mentioned you in',\n 'looking to see what your car',\n 'take your favorite music',\n 'introducing the take',\n];\n\n// DEFINITE SIGNAL - always pass through, skip LLM\nconst SIGNAL_PATTERNS = [\n 'login attempt', 'unauthorized', 'unusual sign',\n 'invoice', 'payment due', 'receipt',\n 'urgent', 'action required',\n 'password reset', 'verify your',\n 'github', 'gitea',\n];\n\nconst items = $input.all();\nif (items.length === 0) return [];\n\n// Ignore schedule/no-email pass-through items from polling mode\nconst emailish = items.filter(item => {\n const j = item.json || {};\n return !!(j.from || j.subject || j.text || j.textPlain || j.textHtml || j.html || j.headers || j.messageId);\n});\nif (emailish.length === 0) return [];\n\n\nconst definiteSignal = [];\nconst needsJudgement = [];\n\nfor (const item of items) {\n const from = (item.json.from || '').toLowerCase();\n const subject = (item.json.subject || '').toLowerCase();\n const combined = from + ' ' + subject;\n\n // Definite signal - fast path, no LLM needed\n if (SIGNAL_PATTERNS.some(p => combined.includes(p))) {\n definiteSignal.push({ ...item.json, _stage1: 'definite_signal', _account: item.json._account || 'unknown' });\n continue;\n }\n\n // Definite noise - drop\n const isNoise = \n NOISE_SENDERS.some(n => combined.includes(n)) ||\n NOISE_SUBJECTS.some(n => new RegExp(n, 'i').test(combined));\n if (isNoise) continue;\n\n // Everything else - send to LLM for judgement\n needsJudgement.push({ ...item.json, _stage1: 'needs_judgement', _account: item.json._account || 'unknown' });\n}\n\n// Return all items for next node; tag them so we can route\nconst all = [...definiteSignal, ...needsJudgement];\nif (all.length === 0) return [{ json: { _empty: true } }];\nreturn all.map(j => ({ json: j }));" }, "id": "n2", "name": "Stage 1 - Static Filter", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 464, 304 ] }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "loose" }, "conditions": [ { "id": "c1", "leftValue": "={{ $json._empty }}", "rightValue": true, "operator": { "type": "boolean", "operation": "notEquals" } } ], "combinator": "and" }, "options": {} }, "id": "n3", "name": "Any Left?", "type": "n8n-nodes-base.if", "typeVersion": 2, "position": [ 688, 304 ] }, { "parameters": { "conditions": { "string": [ { "value1": "={{ $json._stage1 }}", "value2": "needs_judgement" } ] } }, "id": "n4", "name": "Needs LLM Judgement?", "type": "n8n-nodes-base.if", "typeVersion": 1, "position": [ 912, 208 ] }, { "parameters": { "method": "POST", "url": "http://172.19.0.1:18806/v1/chat/completions", "sendHeaders": true, "headerParameters": { "parameters": [ { "name": "Content-Type", "value": "application/json" } ] }, "sendBody": true, "contentType": "raw", "rawContentType": "application/json", "body": "={\"model\": \"gemma-4-26B-A4B-it-UD-IQ2_M.gguf\", \"temperature\": 0, \"max_tokens\": 256, \"messages\": [{\"role\": \"system\", \"content\": \"You are an email triage assistant for a software developer. Emails may be in any language \\u2014 translate mentally before judging. Reply with JSON only: {\\\"signal\\\": true|false, \\\"priority\\\": 1|2|3, \\\"reason\\\": \\\"one short phrase\\\"}. Priority: 1=act now, 2=read today, 3=FYI. Signal=false means drop silently. Always mark security alerts (login attempts, account access, suspicious activity) as signal priority 1, regardless of language.\"}, {\"role\": \"user\", \"content\": \"From: {{ $json.from }}\\nSubject: {{ $json.subject }}\"}]}", "options": { "response": { "response": { "responseFormat": "json" } }, "timeout": 15000 } }, "id": "n5", "name": "Judge with Local LLM", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4, "position": [ 1120, 128 ] }, { "parameters": { "jsCode": "const item = $input.first();\nconst inputItem = $('Needs LLM Judgement?').first();\n\ntry {\n let content = '';\n const j = item.json || {};\n\n if (j.choices && j.choices[0] && j.choices[0].message) {\n content = j.choices[0].message.content || '';\n } else if (j._readableState && j._readableState.buffer && j._readableState.buffer[0] && j._readableState.buffer[0].data) {\n const bytes = j._readableState.buffer[0].data;\n const raw = Buffer.from(bytes).toString('utf8');\n const parsed = JSON.parse(raw);\n content = parsed.choices[0].message.content || '';\n }\n\n content = content.trim();\n if (!content) {\n return [{ json: { ...inputItem.json, _stage2: 'llm_empty', _priority: 3, _reason: 'no llm response' } }];\n }\n\n // Strip markdown code fences\n const cleaned = content.replace(/^[^\\{]*/, '').replace(/[^\\}]*$/, '').trim();\n const result = JSON.parse(cleaned);\n\n if (!result.signal) return [];\n\n return [{ json: { ...inputItem.json, _stage2: 'llm_signal', _priority: result.priority || 3, _reason: result.reason || '' } }];\n} catch(e) {\n return [{ json: { ...inputItem.json, _stage2: 'llm_parse_error', _priority: 3, _reason: 'parse error: ' + e.message } }];\n}" }, "id": "n6", "name": "Parse LLM Result", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1344, 128 ] }, { "parameters": { "jsCode": "const results = [];\nfor (const item of $input.all()) {\n const j = item.json || {};\n results.push({\n json: {\n from: String(j.from || ''),\n subject: String(j.subject || ''),\n date: String(j.date || ''),\n textPlain: String(j.textPlain || j.text || '').substring(0, 500),\n messageId: String(j.messageId || ''),\n _account: String(j._account || 'unknown'),\n _stage1: 'definite_signal',\n _stage2: 'definite_signal',\n _priority: 1,\n _reason: 'pattern match'\n }\n });\n}\nreturn results;" }, "id": "n7", "name": "Tag Definite Signal", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1120, 304 ] }, { "parameters": { "aggregate": "aggregateAllItemData", "destinationFieldName": "messages", "options": {} }, "id": "n8", "name": "Merge All Signal", "type": "n8n-nodes-base.aggregate", "typeVersion": 1, "position": [ 1568, 208 ] }, { "parameters": { "jsCode": "const messages = ($input.first().json.messages || [])\n .sort((a, b) => (a._priority || 3) - (b._priority || 3));\n\nif (messages.length === 0) return [];\n\nconst PRIORITY_EMOJI = { 1: '🔴', 2: '🟡', 3: '🔵' };\n\nconst lines = messages.map((m, i) => {\n const from = (m.from || '(unknown)').replace(/<[^>]+>/g, '').trim().substring(0, 50);\n const subject = (m.subject || '(no subject)').trim().substring(0, 75);\n const emoji = PRIORITY_EMOJI[m._priority] || '🔵';\n const reason = m._reason && m._reason !== 'pattern match' ? ` — _${m._reason}_` : '';\n const acct = m._account && m._account !== 'unknown' ? ` [${m._account}]` : '';\n return `${emoji} ${subject}\\n ${from}${acct}${reason}`;\n});\n\nconst text = `📬 *${messages.length} new email${messages.length > 1 ? 's' : ''}*\\n\\n${lines.join('\\n\\n')}`;\nreturn [{ json: { text } }];" }, "id": "n9", "name": "Format & Send", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1792, 128 ] }, { "parameters": { "chatId": "8367012007", "text": "={{ $json.text }}", "additionalFields": { "parse_mode": "Markdown" } }, "id": "n10", "name": "Send to Telegram", "type": "n8n-nodes-base.telegram", "typeVersion": 1, "position": [ 2000, 128 ], "webhookId": "795a0fc5-c932-4265-bd0d-095dd410f8a8", "credentials": { "telegramApi": { "id": "aox4dyIWVSRdcH5z", "name": "Telegram Bot (OpenClaw)" } } }, { "parameters": {}, "id": "n11", "name": "Silent Stop", "type": "n8n-nodes-base.noOp", "typeVersion": 1, "position": [ 688, 464 ] }, { "parameters": { "jsCode": "const wrapper = $input.first().json;\nconst messages = wrapper.messages || [];\nconst results = [];\n\nfor (const item of messages) {\n const now = new Date();\n const date = now.toISOString().split('T')[0];\n const subject = (item.subject || 'No Subject').replace(/[\\/\\\\?%*:|\"<>]/g, '-').substring(0, 80);\n const from = (item.from || 'unknown').replace(/<[^>]+>/g, '').trim();\n const snippet = (item.textPlain || '').substring(0, 500);\n const priority = item._priority || 3;\n const reason = item._reason || '';\n const PRIORITY_LABEL = {1: 'high', 2: 'medium', 3: 'low'};\n const PRIORITY_TAG = {1: 'priority-high', 2: 'priority-medium', 3: 'priority-low'};\n const frontmatter = '---\\ntitle: \"' + subject + '\"\\narea: notes\\ntags: [email, imap, ' + PRIORITY_TAG[priority] + ']\\ncreated: ' + date + '\\nupdated: ' + date + '\\nstatus: active\\nfrom: \"' + from + '\"\\npriority: ' + PRIORITY_LABEL[priority] + '\\nsignal_reason: \"' + reason + '\"\\n---';\n const content = frontmatter + '\\n\\n# ' + subject + '\\n\\n**From:** ' + from + '\\n**Date:** ' + date + '\\n**Priority:** ' + PRIORITY_LABEL[priority] + (reason ? ' — ' + reason : '') + '\\n\\n## Snippet\\n\\n' + snippet + '\\n\\n## Notes\\n\\n_Add notes here_\\n';\n results.push({ json: { path: 'Notes/' + date + ' ' + subject + '.md', content, subject, from, priority, date } });\n}\nreturn results;" }, "id": "n12", "name": "Format Email Notes", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1792, 304 ] }, { "parameters": { "method": "PUT", "url": "=http://172.19.0.1:27123/vault/{{ encodeURIComponent($json.path).replace(/%2F/g, \"/\") }}", "authentication": "genericCredentialType", "genericAuthType": "httpHeaderAuth", "sendBody": true, "contentType": "raw", "rawContentType": "text/markdown", "body": "={{ $json.content }}", "options": { "response": { "response": { "neverError": true } } } }, "id": "n13", "name": "Write Email to Vault", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4, "position": [ 2000, 304 ], "credentials": { "httpHeaderAuth": { "id": "465Swz2b71O2KRAK", "name": "Obsidian Local REST API" } }, "onError": "continueRegularOutput" }, { "parameters": { "options": {} }, "id": "n1a", "name": "Read Unseen Emails", "type": "n8n-nodes-base.emailReadImap", "typeVersion": 2, "position": [ 352, 656 ], "credentials": { "imap": { "id": "5qGEXTjFtPUZL8BB", "name": "wills_portal IMAP" } } } ], "connections": { "Stage 1 - Static Filter": { "main": [ [ { "node": "Any Left?", "type": "main", "index": 0 } ] ] }, "Any Left?": { "main": [ [ { "node": "Needs LLM Judgement?", "type": "main", "index": 0 } ], [ { "node": "Silent Stop", "type": "main", "index": 0 } ] ] }, "Needs LLM Judgement?": { "main": [ [ { "node": "Judge with Local LLM", "type": "main", "index": 0 } ], [ { "node": "Tag Definite Signal", "type": "main", "index": 0 } ] ] }, "Judge with Local LLM": { "main": [ [ { "node": "Parse LLM Result", "type": "main", "index": 0 } ] ] }, "Parse LLM Result": { "main": [ [ { "node": "Merge All Signal", "type": "main", "index": 0 } ] ] }, "Tag Definite Signal": { "main": [ [ { "node": "Merge All Signal", "type": "main", "index": 0 } ] ] }, "Merge All Signal": { "main": [ [ { "node": "Format & Send", "type": "main", "index": 0 }, { "node": "Format Email Notes", "type": "main", "index": 0 } ] ] }, "Format & Send": { "main": [ [ { "node": "Send to Telegram", "type": "main", "index": 0 } ] ] }, "Format Email Notes": { "main": [ [ { "node": "Write Email to Vault", "type": "main", "index": 0 } ] ] }, "Schedule Trigger": { "main": [ [ { "node": "Read Unseen Emails", "type": "main", "index": 0 } ] ] }, "Read Unseen Emails": { "main": [ [ { "node": "Stage 1 - Static Filter", "type": "main", "index": 0 } ] ] } }, "settings": { "executionOrder": "v1", "callerPolicy": "workflowsFromSameOwner", "availableInMCP": false, "saveDataSuccessExecution": "all", "saveDataErrorExecution": "all", "saveManualExecutions": true }, "staticData": { "node:Schedule Trigger": { "recurrenceRules": [] }, "node:Read Unseen Emails": {}, "node:Read wills_portal": { "lastMessageUid": 8770 }, "node:Read squareffect": {}, "node:Schedule wills_portal": { "recurrenceRules": [] }, "node:Schedule squareffect": { "recurrenceRules": [] }, "node:Email Trigger": {} }, "meta": null, "pinData": {}, "versionId": "8b39192f-1924-42d0-a421-afe88cdee3cf", "activeVersionId": "8b39192f-1924-42d0-a421-afe88cdee3cf", "versionCounter": 3824, "triggerCount": 2, "shared": [ { "updatedAt": "2026-03-18T05:20:48.224Z", "createdAt": "2026-03-18T05:20:48.224Z", "role": "workflow:owner", "workflowId": "9sFwRyUDz51csAp7", "projectId": "WGdp8QunI1tHpjXa", "project": { "updatedAt": "2026-03-11T21:08:10.005Z", "createdAt": "2026-03-11T21:05:11.541Z", "id": "WGdp8QunI1tHpjXa", "name": "will will ", "type": "personal", "icon": null, "description": null, "creatorId": "5ad50ead-6e6a-4d12-ab5b-e5db15835bb5" } } ], "tags": [ { "updatedAt": "2026-03-19T04:40:29.921Z", "createdAt": "2026-03-19T04:40:29.921Z", "id": "R9u3nhZlt6Vanvus", "name": "telegram" }, { "updatedAt": "2026-03-19T04:40:29.892Z", "createdAt": "2026-03-19T04:40:29.892Z", "id": "VfqIkUpiu2YMBSHw", "name": "obsidian-sync" }, { "updatedAt": "2026-03-19T04:40:29.877Z", "createdAt": "2026-03-19T04:40:29.877Z", "id": "qu6qwIegC1LgLKoA", "name": "email-triage" }, { "updatedAt": "2026-03-19T04:40:29.909Z", "createdAt": "2026-03-19T04:40:29.909Z", "id": "r3vsVtTwe9UfLrGi", "name": "imap" }, { "updatedAt": "2026-03-19T04:40:29.926Z", "createdAt": "2026-03-19T04:40:29.926Z", "id": "zKN5N7wCrUuKB7rV", "name": "llm" } ], "activeVersion": { "updatedAt": "2026-05-14T00:02:05.678Z", "createdAt": "2026-05-14T00:02:05.678Z", "versionId": "8b39192f-1924-42d0-a421-afe88cdee3cf", "workflowId": "9sFwRyUDz51csAp7", "nodes": [ { "parameters": { "rule": { "interval": [ { "field": "minutes", "minutesInterval": 15 } ] } }, "id": "n1", "name": "Schedule Trigger", "type": "n8n-nodes-base.scheduleTrigger", "typeVersion": 1, "position": [ 240, 304 ] }, { "parameters": { "jsCode": "// DEFINITE NOISE - never worth seeing\nconst NOISE_SENDERS = [\n 'discord', 'plex', 'spotify', 'youtube',\n 'lodge at redmond ridge', 'flex +',\n 'seattle jeep',\n 'no-reply', 'noreply', 'do-not-reply', 'donotreply',\n 'newsletter', 'marketing',\n];\nconst NOISE_SUBJECTS = [\n 'bulletin board', 'daily digest', 'weekly digest',\n 'most watchlisted', 'newsletter',\n 'mentioned you in',\n 'looking to see what your car',\n 'take your favorite music',\n 'introducing the take',\n];\n\n// DEFINITE SIGNAL - always pass through, skip LLM\nconst SIGNAL_PATTERNS = [\n 'login attempt', 'unauthorized', 'unusual sign',\n 'invoice', 'payment due', 'receipt',\n 'urgent', 'action required',\n 'password reset', 'verify your',\n 'github', 'gitea',\n];\n\nconst items = $input.all();\nif (items.length === 0) return [];\n\n// Ignore schedule/no-email pass-through items from polling mode\nconst emailish = items.filter(item => {\n const j = item.json || {};\n return !!(j.from || j.subject || j.text || j.textPlain || j.textHtml || j.html || j.headers || j.messageId);\n});\nif (emailish.length === 0) return [];\n\n\nconst definiteSignal = [];\nconst needsJudgement = [];\n\nfor (const item of items) {\n const from = (item.json.from || '').toLowerCase();\n const subject = (item.json.subject || '').toLowerCase();\n const combined = from + ' ' + subject;\n\n // Definite signal - fast path, no LLM needed\n if (SIGNAL_PATTERNS.some(p => combined.includes(p))) {\n definiteSignal.push({ ...item.json, _stage1: 'definite_signal', _account: item.json._account || 'unknown' });\n continue;\n }\n\n // Definite noise - drop\n const isNoise = \n NOISE_SENDERS.some(n => combined.includes(n)) ||\n NOISE_SUBJECTS.some(n => new RegExp(n, 'i').test(combined));\n if (isNoise) continue;\n\n // Everything else - send to LLM for judgement\n needsJudgement.push({ ...item.json, _stage1: 'needs_judgement', _account: item.json._account || 'unknown' });\n}\n\n// Return all items for next node; tag them so we can route\nconst all = [...definiteSignal, ...needsJudgement];\nif (all.length === 0) return [{ json: { _empty: true } }];\nreturn all.map(j => ({ json: j }));" }, "id": "n2", "name": "Stage 1 - Static Filter", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 464, 304 ] }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "loose" }, "conditions": [ { "id": "c1", "leftValue": "={{ $json._empty }}", "rightValue": true, "operator": { "type": "boolean", "operation": "notEquals" } } ], "combinator": "and" }, "options": {} }, "id": "n3", "name": "Any Left?", "type": "n8n-nodes-base.if", "typeVersion": 2, "position": [ 688, 304 ] }, { "parameters": { "conditions": { "string": [ { "value1": "={{ $json._stage1 }}", "value2": "needs_judgement" } ] } }, "id": "n4", "name": "Needs LLM Judgement?", "type": "n8n-nodes-base.if", "typeVersion": 1, "position": [ 912, 208 ] }, { "parameters": { "method": "POST", "url": "http://172.19.0.1:18806/v1/chat/completions", "sendHeaders": true, "headerParameters": { "parameters": [ { "name": "Content-Type", "value": "application/json" } ] }, "sendBody": true, "contentType": "raw", "rawContentType": "application/json", "body": "={\"model\": \"gemma-4-26B-A4B-it-UD-IQ2_M.gguf\", \"temperature\": 0, \"max_tokens\": 256, \"messages\": [{\"role\": \"system\", \"content\": \"You are an email triage assistant for a software developer. Emails may be in any language \\u2014 translate mentally before judging. Reply with JSON only: {\\\"signal\\\": true|false, \\\"priority\\\": 1|2|3, \\\"reason\\\": \\\"one short phrase\\\"}. Priority: 1=act now, 2=read today, 3=FYI. Signal=false means drop silently. Always mark security alerts (login attempts, account access, suspicious activity) as signal priority 1, regardless of language.\"}, {\"role\": \"user\", \"content\": \"From: {{ $json.from }}\\nSubject: {{ $json.subject }}\"}]}", "options": { "response": { "response": { "responseFormat": "json" } }, "timeout": 15000 } }, "id": "n5", "name": "Judge with Local LLM", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4, "position": [ 1120, 128 ] }, { "parameters": { "jsCode": "const item = $input.first();\nconst inputItem = $('Needs LLM Judgement?').first();\n\ntry {\n let content = '';\n const j = item.json || {};\n\n if (j.choices && j.choices[0] && j.choices[0].message) {\n content = j.choices[0].message.content || '';\n } else if (j._readableState && j._readableState.buffer && j._readableState.buffer[0] && j._readableState.buffer[0].data) {\n const bytes = j._readableState.buffer[0].data;\n const raw = Buffer.from(bytes).toString('utf8');\n const parsed = JSON.parse(raw);\n content = parsed.choices[0].message.content || '';\n }\n\n content = content.trim();\n if (!content) {\n return [{ json: { ...inputItem.json, _stage2: 'llm_empty', _priority: 3, _reason: 'no llm response' } }];\n }\n\n // Strip markdown code fences\n const cleaned = content.replace(/^[^\\{]*/, '').replace(/[^\\}]*$/, '').trim();\n const result = JSON.parse(cleaned);\n\n if (!result.signal) return [];\n\n return [{ json: { ...inputItem.json, _stage2: 'llm_signal', _priority: result.priority || 3, _reason: result.reason || '' } }];\n} catch(e) {\n return [{ json: { ...inputItem.json, _stage2: 'llm_parse_error', _priority: 3, _reason: 'parse error: ' + e.message } }];\n}" }, "id": "n6", "name": "Parse LLM Result", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1344, 128 ] }, { "parameters": { "jsCode": "const results = [];\nfor (const item of $input.all()) {\n const j = item.json || {};\n results.push({\n json: {\n from: String(j.from || ''),\n subject: String(j.subject || ''),\n date: String(j.date || ''),\n textPlain: String(j.textPlain || j.text || '').substring(0, 500),\n messageId: String(j.messageId || ''),\n _account: String(j._account || 'unknown'),\n _stage1: 'definite_signal',\n _stage2: 'definite_signal',\n _priority: 1,\n _reason: 'pattern match'\n }\n });\n}\nreturn results;" }, "id": "n7", "name": "Tag Definite Signal", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1120, 304 ] }, { "parameters": { "aggregate": "aggregateAllItemData", "destinationFieldName": "messages", "options": {} }, "id": "n8", "name": "Merge All Signal", "type": "n8n-nodes-base.aggregate", "typeVersion": 1, "position": [ 1568, 208 ] }, { "parameters": { "jsCode": "const messages = ($input.first().json.messages || [])\n .sort((a, b) => (a._priority || 3) - (b._priority || 3));\n\nif (messages.length === 0) return [];\n\nconst PRIORITY_EMOJI = { 1: '🔴', 2: '🟡', 3: '🔵' };\n\nconst lines = messages.map((m, i) => {\n const from = (m.from || '(unknown)').replace(/<[^>]+>/g, '').trim().substring(0, 50);\n const subject = (m.subject || '(no subject)').trim().substring(0, 75);\n const emoji = PRIORITY_EMOJI[m._priority] || '🔵';\n const reason = m._reason && m._reason !== 'pattern match' ? ` — _${m._reason}_` : '';\n const acct = m._account && m._account !== 'unknown' ? ` [${m._account}]` : '';\n return `${emoji} ${subject}\\n ${from}${acct}${reason}`;\n});\n\nconst text = `📬 *${messages.length} new email${messages.length > 1 ? 's' : ''}*\\n\\n${lines.join('\\n\\n')}`;\nreturn [{ json: { text } }];" }, "id": "n9", "name": "Format & Send", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1792, 128 ] }, { "parameters": { "chatId": "8367012007", "text": "={{ $json.text }}", "additionalFields": { "parse_mode": "Markdown" } }, "id": "n10", "name": "Send to Telegram", "type": "n8n-nodes-base.telegram", "typeVersion": 1, "position": [ 2000, 128 ], "webhookId": "795a0fc5-c932-4265-bd0d-095dd410f8a8", "credentials": { "telegramApi": { "id": "aox4dyIWVSRdcH5z", "name": "Telegram Bot (OpenClaw)" } } }, { "parameters": {}, "id": "n11", "name": "Silent Stop", "type": "n8n-nodes-base.noOp", "typeVersion": 1, "position": [ 688, 464 ] }, { "parameters": { "jsCode": "const wrapper = $input.first().json;\nconst messages = wrapper.messages || [];\nconst results = [];\n\nfor (const item of messages) {\n const now = new Date();\n const date = now.toISOString().split('T')[0];\n const subject = (item.subject || 'No Subject').replace(/[\\/\\\\?%*:|\"<>]/g, '-').substring(0, 80);\n const from = (item.from || 'unknown').replace(/<[^>]+>/g, '').trim();\n const snippet = (item.textPlain || '').substring(0, 500);\n const priority = item._priority || 3;\n const reason = item._reason || '';\n const PRIORITY_LABEL = {1: 'high', 2: 'medium', 3: 'low'};\n const PRIORITY_TAG = {1: 'priority-high', 2: 'priority-medium', 3: 'priority-low'};\n const frontmatter = '---\\ntitle: \"' + subject + '\"\\narea: notes\\ntags: [email, imap, ' + PRIORITY_TAG[priority] + ']\\ncreated: ' + date + '\\nupdated: ' + date + '\\nstatus: active\\nfrom: \"' + from + '\"\\npriority: ' + PRIORITY_LABEL[priority] + '\\nsignal_reason: \"' + reason + '\"\\n---';\n const content = frontmatter + '\\n\\n# ' + subject + '\\n\\n**From:** ' + from + '\\n**Date:** ' + date + '\\n**Priority:** ' + PRIORITY_LABEL[priority] + (reason ? ' — ' + reason : '') + '\\n\\n## Snippet\\n\\n' + snippet + '\\n\\n## Notes\\n\\n_Add notes here_\\n';\n results.push({ json: { path: 'Notes/' + date + ' ' + subject + '.md', content, subject, from, priority, date } });\n}\nreturn results;" }, "id": "n12", "name": "Format Email Notes", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1792, 304 ] }, { "parameters": { "method": "PUT", "url": "=http://172.19.0.1:27123/vault/{{ encodeURIComponent($json.path).replace(/%2F/g, \"/\") }}", "authentication": "genericCredentialType", "genericAuthType": "httpHeaderAuth", "sendBody": true, "contentType": "raw", "rawContentType": "text/markdown", "body": "={{ $json.content }}", "options": { "response": { "response": { "neverError": true } } } }, "id": "n13", "name": "Write Email to Vault", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4, "position": [ 2000, 304 ], "credentials": { "httpHeaderAuth": { "id": "465Swz2b71O2KRAK", "name": "Obsidian Local REST API" } }, "onError": "continueRegularOutput" }, { "parameters": { "options": {} }, "id": "n1a", "name": "Read Unseen Emails", "type": "n8n-nodes-base.emailReadImap", "typeVersion": 2, "position": [ 352, 656 ], "credentials": { "imap": { "id": "5qGEXTjFtPUZL8BB", "name": "wills_portal IMAP" } } } ], "connections": { "Stage 1 - Static Filter": { "main": [ [ { "node": "Any Left?", "type": "main", "index": 0 } ] ] }, "Any Left?": { "main": [ [ { "node": "Needs LLM Judgement?", "type": "main", "index": 0 } ], [ { "node": "Silent Stop", "type": "main", "index": 0 } ] ] }, "Needs LLM Judgement?": { "main": [ [ { "node": "Judge with Local LLM", "type": "main", "index": 0 } ], [ { "node": "Tag Definite Signal", "type": "main", "index": 0 } ] ] }, "Judge with Local LLM": { "main": [ [ { "node": "Parse LLM Result", "type": "main", "index": 0 } ] ] }, "Parse LLM Result": { "main": [ [ { "node": "Merge All Signal", "type": "main", "index": 0 } ] ] }, "Tag Definite Signal": { "main": [ [ { "node": "Merge All Signal", "type": "main", "index": 0 } ] ] }, "Merge All Signal": { "main": [ [ { "node": "Format & Send", "type": "main", "index": 0 }, { "node": "Format Email Notes", "type": "main", "index": 0 } ] ] }, "Format & Send": { "main": [ [ { "node": "Send to Telegram", "type": "main", "index": 0 } ] ] }, "Format Email Notes": { "main": [ [ { "node": "Write Email to Vault", "type": "main", "index": 0 } ] ] }, "Schedule Trigger": { "main": [ [ { "node": "Read Unseen Emails", "type": "main", "index": 0 } ] ] }, "Read Unseen Emails": { "main": [ [ { "node": "Stage 1 - Static Filter", "type": "main", "index": 0 } ] ] } }, "authors": "will will", "name": null, "description": null, "autosaved": false, "workflowPublishHistory": [ { "createdAt": "2026-05-14T00:02:07.948Z", "id": 1469, "workflowId": "9sFwRyUDz51csAp7", "versionId": "8b39192f-1924-42d0-a421-afe88cdee3cf", "event": "activated", "userId": "5ad50ead-6e6a-4d12-ab5b-e5db15835bb5" }, { "createdAt": "2026-05-14T00:02:06.050Z", "id": 1468, "workflowId": "9sFwRyUDz51csAp7", "versionId": "8b39192f-1924-42d0-a421-afe88cdee3cf", "event": "deactivated", "userId": "5ad50ead-6e6a-4d12-ab5b-e5db15835bb5" } ] } }