{
  "name": "Bills Inbox: AI Invoice Capture (Helm Systems)",
  "nodes": [
    {
      "parameters": {
        "content": "## Bills Inbox\n1. In Gmail, make a filter that applies the label **bills** to vendor invoices (for example anything sent to a dedicated address, or from your usual vendors).\n2. Fill in **Settings**. Connect Gmail, Google Sheets and the AI model credentials. Sheet columns: Logged, Vendor, Invoice #, Amount, Due date, Category, Confidence, From, Subject, Status.\n3. Each new labeled email is read, the first PDF is turned into text, the AI pulls out vendor, amount, due date and category, and a row is added to your sheet.\n4. Anything due within a week, or that the AI isn't sure about, is emailed to you to check.\n5. Limits: it reads text PDFs and the email body. Scans and photos of paper bills are not read. Always check the amount before paying.\nMade by Helm Systems: helmsystems.biz/field-kit",
        "width": 420,
        "height": 350,
        "color": 4
      },
      "id": "f71878a3-cd37-4e72-a548-828adf1d0592",
      "name": "Note 1",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        -418.00000000000006
      ]
    },
    {
      "parameters": {
        "pollTimes": {
          "item": [
            {
              "mode": "everyX",
              "value": 15,
              "unit": "minutes"
            }
          ]
        },
        "simple": false,
        "filters": {
          "readStatus": "unread",
          "q": "label:bills",
          "includeDrafts": false,
          "includeSpamTrash": false
        },
        "options": {
          "downloadAttachments": true,
          "dataPropertyAttachmentsPrefixName": "attachment_"
        }
      },
      "id": "ed26dfaf-8b57-4f64-a440-95f0701d3c64",
      "name": "Gmail Trigger",
      "type": "n8n-nodes-base.gmailTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "39c77475-872d-4ab7-ad14-f77d09cdf142",
              "name": "business",
              "value": "Your Business Name",
              "type": "string"
            },
            {
              "id": "c5e51f11-4181-4f5d-a7d0-478523754a82",
              "name": "owner",
              "value": "Your Name",
              "type": "string"
            },
            {
              "id": "b03cfb0e-efe1-417d-add5-04485d24bab6",
              "name": "ownerEmail",
              "value": "you@yourbusiness.com",
              "type": "string"
            },
            {
              "id": "41dce84e-90ab-4989-a929-93a03e087060",
              "name": "alertWithinDays",
              "value": 7,
              "type": "number"
            }
          ]
        },
        "options": {}
      },
      "id": "99286662-1334-4d3e-aa5a-7773c3f0f060",
      "name": "Settings",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        260,
        0
      ]
    },
    {
      "parameters": {
        "operation": "pdf",
        "binaryPropertyName": "attachment_0",
        "options": {}
      },
      "id": "aa48ad74-7e94-435c-a3ee-8bf245d12c83",
      "name": "Read the PDF",
      "type": "n8n-nodes-base.extractFromFile",
      "typeVersion": 1.1,
      "position": [
        520,
        0
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const mail = $('Gmail Trigger').item.json;\nconst pdf = typeof $json.text === 'string' ? $json.text : '';\nreturn { json: {\n  from: mail.from?.text || '',\n  subject: mail.subject || '',\n  text: [mail.text || '', pdf].join('\\n\\n').slice(0, 6000),\n} };"
      },
      "id": "5d4d681b-72de-4e22-a6c6-e9fab42a174f",
      "name": "Combine email and PDF",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        780,
        0
      ]
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=From: {{ $json.from }}\nSubject: {{ $json.subject }}\nText:\n{{ $json.text }}\n\nReturn JSON with the keys: is_invoice, vendor, invoice_number, amount, currency, issue_date, due_date, category, confidence.",
        "hasOutputParser": false,
        "messages": {
          "messageValues": [
            {
              "message": "=You extract fields from a vendor bill or invoice that was emailed to a small business.\n\nRules:\n- Use null for anything not stated. Never guess an amount or a date.\n- amount is a number with no currency symbol.\n- Dates are YYYY-MM-DD.\n- category is one of: materials, subcontractor, software, utilities, insurance, vehicle, office, other.\n- is_invoice is false for receipts for things already paid, newsletters, quotes and anything that is not a bill to pay.\n- confidence is a number from 0 to 1 for how sure you are of the amount and due date.\nReturn only JSON."
            }
          ]
        }
      },
      "id": "143d286f-c2a9-4f54-a6cf-57edf635e9cb",
      "name": "Pull out the details",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        1040,
        0
      ]
    },
    {
      "parameters": {
        "model": {
          "__rl": true,
          "value": "gpt-5-mini",
          "mode": "list",
          "cachedResultName": "gpt-5-mini"
        },
        "options": {}
      },
      "id": "759855f9-fa30-4211-aded-c61cb61a2418",
      "name": "Pull out the details model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.2,
      "position": [
        1040,
        266
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const raw = String($json.text ?? '').trim().replace(/^```(?:json)?/i, '').replace(/```$/, '').trim();\nlet out = {};\nlet ok = true;\ntry { out = JSON.parse(raw); } catch (e) { ok = false; }\nconst base = $('Combine email and PDF').item.json;\nconst s = $('Settings').first().json;\nconst due = out.due_date ? new Date(out.due_date) : null;\nconst daysToDue = due && !isNaN(due) ? Math.round((due - new Date()) / 86400000) : null;\nconst unsure = !ok || Number(out.confidence ?? 0) < 0.7 || out.amount == null;\nconst soon = daysToDue !== null && daysToDue <= Number(s.alertWithinDays);\nreturn { json: { ...base, ...out, is_invoice: ok && out.is_invoice === true, needsAttention: unsure || soon, why: unsure ? 'The AI was not sure about this one' : 'Due within ' + s.alertWithinDays + ' days' } };"
      },
      "id": "d59a80f4-3127-4b89-a9a0-114a6b8f3c61",
      "name": "Read the details",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1300,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "4f9dabac-152d-4402-a28b-358948e74b69",
              "leftValue": "={{ $json.is_invoice }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "99e47bf2-becf-4124-a189-688f524c9794",
      "name": "Is it a bill?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1560,
        0
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Logged": "={{ $now.toFormat('yyyy-LL-dd') }}",
            "Vendor": "={{ $json.vendor }}",
            "Invoice #": "={{ $json.invoice_number }}",
            "Amount": "={{ $json.amount }}",
            "Due date": "={{ $json.due_date }}",
            "Category": "={{ $json.category }}",
            "Confidence": "={{ $json.confidence }}",
            "From": "={{ $json.from }}",
            "Subject": "={{ $json.subject }}",
            "Status": "To pay"
          },
          "matchingColumns": [],
          "schema": []
        },
        "options": {}
      },
      "id": "47dc69c2-d300-44fa-afd4-753c4cdb49de",
      "name": "Add to the bills sheet",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        1820,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "cb8832b9-7d6d-4fd9-aef5-8c987ce9369c",
              "leftValue": "={{ $('Read the details').item.json.needsAttention }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "8a95d742-bc98-497a-a4c1-eabbc2559396",
      "name": "Needs a look?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2080,
        0
      ]
    },
    {
      "parameters": {
        "resource": "message",
        "operation": "send",
        "sendTo": "={{ $('Settings').first().json.ownerEmail }}",
        "subject": "={{ 'Bill to check: ' + ($('Read the details').item.json.vendor || $('Read the details').item.json.from) }}",
        "emailType": "text",
        "message": "={{ $('Read the details').item.json.why + '\\n\\nVendor: ' + $('Read the details').item.json.vendor + '\\nAmount: ' + $('Read the details').item.json.amount + '\\nDue: ' + $('Read the details').item.json.due_date + '\\nFrom: ' + $('Read the details').item.json.from }}",
        "options": {
          "appendAttribution": false
        }
      },
      "id": "35acfe5c-dd75-4ed0-a7ad-aee18458c4f7",
      "name": "Tell me",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        2340,
        0
      ]
    }
  ],
  "connections": {
    "Pull out the details model": {
      "ai_languageModel": [
        [
          {
            "node": "Pull out the details",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Gmail Trigger": {
      "main": [
        [
          {
            "node": "Settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Settings": {
      "main": [
        [
          {
            "node": "Read the PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read the PDF": {
      "main": [
        [
          {
            "node": "Combine email and PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combine email and PDF": {
      "main": [
        [
          {
            "node": "Pull out the details",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pull out the details": {
      "main": [
        [
          {
            "node": "Read the details",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read the details": {
      "main": [
        [
          {
            "node": "Is it a bill?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is it a bill?": {
      "main": [
        [
          {
            "node": "Add to the bills sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Add to the bills sheet": {
      "main": [
        [
          {
            "node": "Needs a look?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Needs a look?": {
      "main": [
        [
          {
            "node": "Tell me",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "tags": []
}
