{
  "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"
      }
    ]
  }
}
