{
  "name": "Inbox Agent (ep11)",
  "nodes": [
    {
      "parameters": {
        "mailbox": "EP11",
        "postProcessAction": "nothing",
        "format": "resolved",
        "downloadAttachments": false,
        "options": {
          "customEmailConfig": "[\"ALL\"]",
          "forceReconnect": 10,
          "trackLastMessageId": true
        }
      },
      "id": "e1100000-0000-4000-8000-000000000008",
      "name": "Email Trigger (IMAP)",
      "type": "n8n-nodes-base.emailReadImap",
      "typeVersion": 2.1,
      "position": [
        200,
        300
      ],
      "credentials": {
        "imap": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const fs=require('fs'),B=$env.EP11_DIR,m=$json;\nconst hdr=(k)=>String((m.headers&&(m.headers[k]||m.headers[k.toLowerCase()]))||'').replace(/^[\\w-]+:\\s*/,'').trim();\nconst addr=(s)=>String(s).match(/[^\\s<>,\"]+@[^\\s<>,\"]+/)?.[0]?.toLowerCase()||'';\nconst uid=String(m.attributes?.uid??hdr('message-id')).replace(/[^\\w.-]/g,'_');\nconst text=String(m.textPlain||m.text||m.textHtml||'')\n  .replace(/^>.*$/gm,'')\n  .replace(/^On .+ wrote:\\s*$/gm,'')\n  .replace(/[ \\t]+/g,' ').replace(/\\n{3,}/g,'\\n\\n').trim().slice(0,4000);\nconst out={\n  uid, messageId:hdr('message-id')||uid,\n  from:addr(hdr('from')), replyTo:addr(hdr('reply-to'))||addr(hdr('from')),\n  subject:String(m.subject||'').slice(0,200), date:hdr('date'),\n  listUnsub:!!hdr('list-unsubscribe'), autoSub:String(hdr('auto-submitted')||'no'),\n  text,\n};\nfs.writeFileSync(`${B}/mail/${uid}.json`,JSON.stringify(out,null,1));\nreturn {json:out};"
      },
      "id": "e1100000-0000-4000-8000-000000000001",
      "name": "normalize",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        420,
        300
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const fs=require('fs'),B=$env.EP11_DIR,d=$json;\nconst LED=JSON.parse(fs.readFileSync(`${B}/replied.json`,'utf8'));\nconst NEVER=/^(no-?reply|donotreply|do-not-reply|bounce|mailer-daemon|postmaster)/i;\nconst local=d.from.split('@')[0];\nlet stop=null;\nif(d.listUnsub||d.autoSub.toLowerCase()!=='no') stop='NOTICE_BULK';\nelse if(NEVER.test(local)) stop='NOTICE_NOREPLY';\nelse if(LED[d.messageId]) stop='DONE_ALREADY_REPLIED';\nreturn {json:{...d,stop,category:stop?'NOTICE':null,confidence:0}};"
      },
      "id": "e1100000-0000-4000-8000-000000000002",
      "name": "prefilter",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        640,
        300
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const fs=require('fs'),https=require('https'),B=$env.EP11_DIR,d=$json;\nif(d.stop) return {json:d};\nconst CATS='BOOKING,SUPPORT,PRICING,COLD_PITCH,BILLING,PERSONAL';\nconst prompt=`Classify this email. Reply with JSON only: {\"category\":one of [${CATS}],`\n  +`\"confidence\":0..1,\"reason\":\"one line\"}\\n\\nFrom: ${d.from}\\nSubject: ${d.subject}\\n\\n${d.text}`;\nconst res=await new Promise((ok,er)=>{const r=https.request('https://fal.run/fal-ai/any-llm',{method:'POST',headers:{Authorization:'Key '+$env.FAL_KEY,'Content-Type':'application/json'}},s=>{let x='';s.on('data',c=>x+=c);s.on('end',()=>ok({h:s.headers,d:x}))});r.on('error',er);r.end(JSON.stringify({model:'meta-llama/llama-4-scout',prompt}))});\nfs.writeFileSync(`${B}/raw/${d.uid}-classify.json`,res.d);\nfs.appendFileSync(`${B}/receipts.jsonl`,JSON.stringify({uid:d.uid,call:'classify',units:res.h['x-fal-billable-units']||null,at:new Date().toISOString()})+'\\n');\nlet o={},j={};try{o=JSON.parse(res.d)}catch(e){}\ntry{j=JSON.parse(String(o.output||'').match(/\\{[\\s\\S]*\\}/)?.[0]||'{}')}catch(e){}\nreturn {json:{...d,category:j.category||'PERSONAL',confidence:Number(j.confidence)||0,reason:String(j.reason||'').slice(0,160)}};"
      },
      "id": "e1100000-0000-4000-8000-000000000003",
      "name": "classify-call",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        860,
        300
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const fs=require('fs'),https=require('https'),B=$env.EP11_DIR,d=$json;\nif(d.stop||!['BOOKING','SUPPORT'].includes(d.category)) return {json:d};\nconst KB=fs.readFileSync(`${B}/knowledge.md`,'utf8');\nconst prompt=`You reply to email using ONLY the facts below. If the answer is not `\n  +`there, say you will follow up \u2014 invent nothing.\\n\\n=== FACTS ===\\n${KB}\\n=== END ===\\n\\n`\n  +`Email from ${d.from}:\\nSubject: ${d.subject}\\n${d.text}\\n\\n`\n  +`Reply with JSON only: {\"reply\":\"...\",\"facts\":[\"exact sentence copied from FACTS\", ...]}`;\nconst res=await new Promise((ok,er)=>{const r=https.request('https://fal.run/fal-ai/any-llm',{method:'POST',headers:{Authorization:'Key '+$env.FAL_KEY,'Content-Type':'application/json'}},s=>{let x='';s.on('data',c=>x+=c);s.on('end',()=>ok({h:s.headers,d:x}))});r.on('error',er);r.end(JSON.stringify({model:'meta-llama/llama-4-scout',prompt}))});\nfs.writeFileSync(`${B}/raw/${d.uid}-draft.json`,res.d);\nfs.appendFileSync(`${B}/receipts.jsonl`,JSON.stringify({uid:d.uid,call:'draft',units:res.h['x-fal-billable-units']||null,at:new Date().toISOString()})+'\\n');\nlet o={},j={};try{o=JSON.parse(res.d)}catch(e){}\ntry{j=JSON.parse(String(o.output||'').match(/\\{[\\s\\S]*\\}/)?.[0]||'{}')}catch(e){}\nreturn {json:{...d,to:d.from,parsed:!!j.reply,draft:{reply:String(j.reply||''),facts:Array.isArray(j.facts)?j.facts.map(String):[]}}};"
      },
      "id": "e1100000-0000-4000-8000-000000000004",
      "name": "draft-call",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1080,
        300
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const fs=require('fs'),B=$env.EP11_DIR,d=$json,b=d.draft?.reply||'',F=d.draft?.facts||[];\nconst KB=fs.readFileSync(`${B}/knowledge.md`,'utf8'),LED=JSON.parse(fs.readFileSync(`${B}/replied.json`,'utf8'));\nconst QUOTE=KB.split('\\n').filter((l)=>!/^\\s*(#|INTERNAL:|TODO)/.test(l)).join('\\n');\nconst strip=(s)=>s.replace(/\\d{4}-\\d{2}-\\d{2}(T[\\d:.]+Z?)?/g,' ').replace(/\\bv?\\d+\\.\\d+(\\.\\d+)?\\b/g,' ').replace(/llama-[\\w.-]+/gi,' ');\nconst tok=(s)=>strip(s).match(/\\$?\\d[\\d,.]*%?|https?:\\/\\/[^\\s\"'<>)]+/g)||[];\nconst src=strip(F.join(' ')+' '+(d.text||''));\nconst RULES=[\n  ['CATEGORY_NOT_SENDABLE',()=>['BOOKING','SUPPORT'].includes(d.category)],\n  ['LOW_CONFIDENCE',       ()=>d.confidence>=0.75],\n  ['NO_FACTS_CITED',       ()=>F.length>0],\n  ['FACT_NOT_IN_KB',       ()=>F.every((f)=>KB.includes(f.trim()))],\n  ['FACT_NOT_QUOTABLE',    ()=>F.every((f)=>QUOTE.includes(f.trim()))],\n  ['UNSUPPORTED_TOKEN',    ()=>tok(b).every((t)=>src.includes(t))],\n  ['RECIPIENT_MISMATCH',   ()=>d.to===d.from],\n  ['ALREADY_REPLIED',      ()=>!LED[d.messageId]]];\nconst rule=d.stop||(RULES.find(([,ok])=>!ok())||[null])[0],verdict=rule?'BLOCKED':'SEND';\nfs.appendFileSync(`${B}/decisions.jsonl`,JSON.stringify({uid:d.uid,messageId:d.messageId,from:d.from,category:d.category,confidence:d.confidence,parsed:!!d.parsed,verdict,rule,at:new Date().toISOString()})+'\\n');\nreturn {json:{...d,verdict,rule}};"
      },
      "id": "e1100000-0000-4000-8000-000000000005",
      "name": "send-gate",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1300,
        300
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const fs=require('fs'),B=$env.EP11_DIR,items=$input.all().map(i=>i.json);\nconst by=(k)=>items.reduce((a,x)=>(a[x[k]||'none']=(a[x[k]||'none']||0)+1,a),{});\nconst sent=items.filter(x=>x.verdict==='SEND');\nconst lines=[\n  `messages: ${items.length}`,`sendable: ${sent.length}`,\n  `blocked by rule: ${JSON.stringify(by('rule'))}`,`by category: ${JSON.stringify(by('category'))}`,\n  '','THIS GATE CANNOT VERIFY:',\n  '- whether the sender is who they say they are',\n  '- whether a plausible-sounding request is a real one',\n  '- anything the transport adds after this node runs'];\nfs.writeFileSync(`${B}/digest.txt`,lines.join('\\n'));\nfs.appendFileSync(`${B}/digests.log`,`=== ${new Date().toISOString()}\\n${lines.join('\\n')}\\n`);\nreturn $input.all();"
      },
      "id": "e1100000-0000-4000-8000-000000000006",
      "name": "digest",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1520,
        300
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const fs=require('fs'),B=$env.EP11_DIR,ME=$env.EP11_OPERATOR;\nif($env.EP11_MODE!=='live') return [];\nconst LED=JSON.parse(fs.readFileSync(`${B}/replied.json`,'utf8'));\nconst out=[{json:{to:ME,replyTo:ME,subject:`EP11 run \u2014 ${new Date().toISOString()}`,body:fs.readFileSync(`${B}/digest.txt`,'utf8')}}];\nfor(const {json:d} of $input.all()){\n  if(d.verdict!=='SEND'||LED[d.messageId]) continue;\n  LED[d.messageId]={to:d.to,at:new Date().toISOString()};\n  fs.writeFileSync(`${B}/approved/${d.uid}.txt`,d.draft.reply);\n  out.push({json:{to:d.to,replyTo:ME,subject:`Re: ${d.subject}`,body:d.draft.reply,uid:d.uid}});\n}\nfs.writeFileSync(`${B}/replied.json`,JSON.stringify(LED,null,1));\nreturn out;"
      },
      "id": "e1100000-0000-4000-8000-000000000007",
      "name": "outbox",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1740,
        300
      ]
    },
    {
      "parameters": {
        "fromEmail": "Ships Itself <you@example.com>",
        "toEmail": "={{ $json.to }}",
        "subject": "={{ $json.subject }}",
        "emailFormat": "text",
        "text": "={{ $json.body }}",
        "options": {
          "replyTo": "={{ $json.replyTo }}"
        }
      },
      "id": "e1100000-0000-4000-8000-000000000009",
      "name": "Send reply",
      "type": "n8n-nodes-base.emailSend",
      "typeVersion": 2.1,
      "position": [
        1960,
        300
      ],
      "credentials": {
        "smtp": {
          "name": "<your credential>"
        }
      }
    }
  ],
  "connections": {
    "Email Trigger (IMAP)": {
      "main": [
        [
          {
            "node": "normalize",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "normalize": {
      "main": [
        [
          {
            "node": "prefilter",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "prefilter": {
      "main": [
        [
          {
            "node": "classify-call",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "classify-call": {
      "main": [
        [
          {
            "node": "draft-call",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "draft-call": {
      "main": [
        [
          {
            "node": "send-gate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "send-gate": {
      "main": [
        [
          {
            "node": "digest",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "digest": {
      "main": [
        [
          {
            "node": "outbox",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "outbox": {
      "main": [
        [
          {
            "node": "Send reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}