{
  "name": "AI Invoice Emails to Sheets",
  "nodes": [
    {
      "parameters": {
        "content": "## AI Invoice Email Extractor to Google Sheets\n\nEvery invoice or receipt that hits your inbox becomes a clean spreadsheet row - vendor, amount, due date, invoice number - without you opening the email.\n\n**Setup time:** 15 minutes  ·  **Level:** Intermediate\n\n**You'll need accounts/credentials for:** Email IMAP, OpenAI, Google Sheets\n\n**Replace these before running:**\n- `REPLACE_WITH_YOUR_SPREADSHEET`\n\n**Setup steps:**\n1. Connect the IMAP node to the mailbox that receives invoices (host, port 993, app password).\n2. Add your OpenAI API key.\n3. Connect Google Sheets (columns: Received, Vendor, Amount, Currency, InvoiceNumber, InvoiceDate, DueDate, Subject).\n4. Activate, then forward yourself an old invoice email to test.\n\nFree templates and updates: navkhan103.github.io",
        "height": 596,
        "width": 400,
        "color": 4
      },
      "id": "sticky-setup",
      "name": "Setup — read me first",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -440,
        -40
      ]
    },
    {
      "parameters": {
        "content": "### How it runs\n\nStarts from **Watch Inbox**.\n\n1. An Email (IMAP) trigger watches your inbox or a dedicated invoices@ address.\n2. A Code node pre-filters: only emails that look like invoices or receipts continue (keywords in subject or sender).\n3. An OpenAI node extracts vendor, amount, currency, invoice number, invoice date, and due date as JSON - or NOT_INVOICE if it's a false positive.\n4. A Code node validates the JSON and drops non-invoices.\n5. A Google Sheets node appends the row to your ledger.",
        "height": 374,
        "width": 400,
        "color": 7
      },
      "id": "sticky-how",
      "name": "How it runs",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -440,
        596
      ]
    },
    {
      "parameters": {
        "content": "### If something breaks\n\nThe read and AI nodes here **retry 3× automatically** (5s apart), which absorbs rate limits and brief timeouts.\n\nNodes that **send or write deliberately do not retry** — a send that reached the far end but lost its response would otherwise fire twice, so you'd get duplicate messages or duplicate rows.\n\nTo be told when one of those fails, build a workflow starting with an **Error Trigger** node that posts to Slack or email, then set it under **three-dot menu > Settings > Error Workflow**. Do it once and point every workflow at it.",
        "height": 400,
        "width": 400,
        "color": 3
      },
      "id": "sticky-fail",
      "name": "If something breaks",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -440,
        1010
      ]
    },
    {
      "parameters": {
        "format": "simple",
        "options": {}
      },
      "id": "b5c6d7e8-0001-4000-8000-000000000001",
      "name": "Watch Inbox",
      "type": "n8n-nodes-base.emailReadImap",
      "typeVersion": 2.1,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "const e = $input.first().json;\nconst hay = `${e.subject || ''} ${e.from || ''}`.toLowerCase();\nconst billing = /invoice|receipt|payment|billing|statement|order confirm/i.test(hay);\nif (!billing) return [];\nreturn [{ json: { subject: e.subject, from: e.from, date: e.date, text: (e.textPlain || e.textHtml || '').slice(0, 6000) } }];"
      },
      "id": "b5c6d7e8-0002-4000-8000-000000000002",
      "name": "Billing Emails Only",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        220,
        0
      ]
    },
    {
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-5.6-luna"
        },
        "messages": {
          "values": [
            {
              "content": "=Extract invoice data from this email. Reply with ONLY a JSON object, no prose:\n{\"vendor\": string, \"amount\": number, \"currency\": string, \"invoice_number\": string, \"invoice_date\": string, \"due_date\": string}\nUse \"\" for unknown fields. If this email is not an invoice or receipt, reply exactly: NOT_INVOICE\n\nFrom: {{ $json.from }}\nSubject: {{ $json.subject }}\n\n{{ $json.text }}",
              "role": "user"
            }
          ]
        }
      },
      "id": "b5c6d7e8-0003-4000-8000-000000000003",
      "name": "Extract with AI",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "typeVersion": 1.8,
      "position": [
        440,
        0
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000
    },
    {
      "parameters": {
        "jsCode": "const raw = $input.first().json.message.content.trim();\nif (raw === 'NOT_INVOICE') return [];\nlet data;\ntry { data = JSON.parse(raw.replace(/^```json?\\s*|```$/g, '')); } catch { return []; }\nconst src = $('Billing Emails Only').first().json;\nreturn [{ json: {\n  Received: src.date || new Date().toISOString(),\n  Vendor: data.vendor || '', Amount: data.amount || 0, Currency: data.currency || '',\n  InvoiceNumber: data.invoice_number || '', InvoiceDate: data.invoice_date || '', DueDate: data.due_date || '',\n  Subject: src.subject || ''\n} }];"
      },
      "id": "b5c6d7e8-0004-4000-8000-000000000004",
      "name": "Validate JSON",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        660,
        0
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "REPLACE_WITH_YOUR_SPREADSHEET"
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "Sheet1"
        },
        "columns": {
          "mappingMode": "autoMapInputData"
        }
      },
      "id": "b5c6d7e8-0005-4000-8000-000000000005",
      "name": "Append to Ledger",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        880,
        0
      ]
    }
  ],
  "connections": {
    "Watch Inbox": {
      "main": [
        [
          {
            "node": "Billing Emails Only",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Billing Emails Only": {
      "main": [
        [
          {
            "node": "Extract with AI",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract with AI": {
      "main": [
        [
          {
            "node": "Validate JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate JSON": {
      "main": [
        [
          {
            "node": "Append to Ledger",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}