Files
2026-06-04 13:26:50 -07:00

958 lines
34 KiB
JSON

{
"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 <will@wills-portal.com>",
"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"
}
]
}
}