{
  "name": "Auto-Capture Supplier Invoices from Gmail with n8n (free JSON)",
  "nodes": [
    {
      "id": "a0a29ec1-a984-48db-a476-a71b2eedee7e",
      "name": "New Invoice Email",
      "type": "n8n-nodes-base.gmailTrigger",
      "position": [
        0,
        0
      ],
      "parameters": {
        "simple": false,
        "filters": {
          "labelIds": [
            "YOUR_GMAIL_LABEL_ID"
          ],
          "hasAttachment": true
        },
        "options": {
          "downloadAttachments": true
        },
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "51b32d3b-9804-4e3f-b569-c76c55eb9800",
      "name": "Only PDF Attachments",
      "type": "n8n-nodes-base.filter",
      "position": [
        240,
        0
      ],
      "parameters": {
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $binary.attachment_0?.fileExtension }}",
              "rightValue": "pdf"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "758e0879-38aa-4bb1-9c52-22a7073016f5",
      "name": "PDF To Text",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        480,
        0
      ],
      "parameters": {
        "options": {},
        "operation": "pdf",
        "binaryPropertyName": "attachment_0"
      },
      "typeVersion": 1
    },
    {
      "id": "d4396ed2-7626-44b2-a359-3d564c4d1946",
      "name": "Read The Invoice",
      "type": "@n8n/n8n-nodes-langchain.informationExtractor",
      "position": [
        760,
        0
      ],
      "parameters": {
        "text": "={{ $json.text }}",
        "options": {
          "systemPromptTemplate": "You read supplier invoices and return structured data.\n\nRules that matter more than completeness:\n1. Return null for any field you cannot read with confidence. NEVER guess a number.\n2. Amounts are numbers, not strings. No currency symbols, no thousands separators.\n3. Dates are ISO yyyy-mm-dd. If the format is ambiguous between UK and US, assume UK (dd/mm/yyyy).\n4. A credit note has negative amounts. If the document says credit note, refund or negative total, return all amounts as negative numbers.\n5. Read the FINAL total, which on a multi-page invoice is usually on the last page. Do not take a subtotal from page one."
        },
        "schemaType": "fromJson",
        "jsonSchemaExample": "{\n  \"supplier\": \"Acme Supplies Ltd\",\n  \"invoice_number\": \"INV-2041\",\n  \"issue_date\": \"2026-08-12\",\n  \"due_date\": \"2026-09-11\",\n  \"currency\": \"GBP\",\n  \"net\": 1200.00,\n  \"vat\": 240.00,\n  \"gross\": 1440.00,\n  \"vat_rate\": 20,\n  \"line_items\": [{ \"description\": \"Consulting\", \"quantity\": 1, \"unit_price\": 1200.00, \"amount\": 1200.00 }]\n}"
      },
      "typeVersion": 1.2
    },
    {
      "id": "3c651ffa-195c-4c9e-a499-a065089077b0",
      "name": "Extraction Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        760,
        220
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1.3
    },
    {
      "id": "3a1fd8b2-1896-45e2-afdd-6a483d92ee20",
      "name": "Validate Before Booking",
      "type": "n8n-nodes-base.code",
      "position": [
        1120,
        0
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const d = $json.output ?? $json;\nconst fails = [];\nconst num = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : null);\nconst near = (a, b) => Math.abs(a - b) < 0.02;\n\nconst net = num(d.net), vat = num(d.vat), gross = num(d.gross);\n\nfor (const f of ['supplier', 'invoice_number', 'issue_date']) {\n  if (!d[f]) fails.push('missing ' + f);\n}\nif (net === null) fails.push('net unreadable');\nif (gross === null) fails.push('gross unreadable');\n\nif (net !== null && vat !== null && gross !== null && !near(net + vat, gross)) {\n  fails.push('net + VAT does not equal gross');\n}\n\nif (Array.isArray(d.line_items) && d.line_items.length && net !== null) {\n  const sum = d.line_items.reduce((t, li) => t + (num(li.amount) ?? 0), 0);\n  if (!near(sum, net)) fails.push('line items do not sum to net');\n}\n\nif (d.vat_rate != null && ![0, 5, 20].includes(Number(d.vat_rate))) {\n  fails.push('VAT rate ' + d.vat_rate + '% is not a UK rate');\n}\n\nif (gross !== null && gross < 0) fails.push('negative total, looks like a credit note');\n\nreturn { json: { ...d, ok: fails.length === 0, problems: fails.join('; ') || null, source_email: $('New Invoice Email').item.json.id } };"
      },
      "typeVersion": 2
    },
    {
      "id": "380ff8c3-4c67-45b0-aa64-2a3e9b79c218",
      "name": "Did It Reconcile?",
      "type": "n8n-nodes-base.if",
      "position": [
        1360,
        0
      ],
      "parameters": {
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.ok }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "aec681c6-03f8-46c4-9aab-d1efd2e51612",
      "name": "Append To Books",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1620,
        -120
      ],
      "parameters": {
        "columns": {
          "value": {},
          "mappingMode": "autoMapInputData",
          "matchingColumns": []
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Invoices"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "YOUR_SPREADSHEET_URL"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "f190f073-1ea3-4dae-a70a-b000dd588714",
      "name": "Send To Exceptions Queue",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1620,
        120
      ],
      "parameters": {
        "columns": {
          "value": {},
          "mappingMode": "autoMapInputData",
          "matchingColumns": []
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Exceptions"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "YOUR_SPREADSHEET_URL"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "7e3e6152-cc56-4ab0-9a5c-30c1b3162072",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -60,
        -780
      ],
      "parameters": {
        "color": 1,
        "width": 760,
        "height": 720,
        "content": "## Supplier invoices from a Gmail label to Google Sheets\n\nReads PDF invoices that arrive under one Gmail label, extracts the main fields with an OpenAI model, checks the arithmetic in code, and logs each invoice to one of two sheet tabs: Invoices (checks passed) or Exceptions (checks failed, with the reason).\n\n### How it works\n\n1. New Invoice Email: watches one Gmail label for emails with attachments.\n2. Only PDF Attachments: keeps the email only if its first attachment is a PDF.\n3. PDF To Text: turns that PDF into text.\n4. Read The Invoice: an OpenAI model returns supplier, number, dates, currency, net, VAT, gross, VAT rate and line items.\n5. Validate Before Booking: checks required fields, net + VAT = gross, line items = net, the VAT rate, and negative totals.\n6. Did It Reconcile?: sends the result to Invoices or Exceptions.\n\n### Setup\n\n1. Connect your Gmail, OpenAI and Google Sheets credentials.\n2. In New Invoice Email, select your invoice label (the placeholder YOUR_GMAIL_LABEL_ID must be replaced).\n3. In both Google Sheets nodes, replace YOUR_SPREADSHEET_URL with your spreadsheet address. Create the tabs Invoices and Exceptions. Put these column names in row 1 of both tabs: supplier, invoice_number, issue_date, due_date, currency, net, vat, gross, vat_rate, line_items, ok, problems, source_email.\n4. Test with a few real invoices before activating, and read the Exceptions tab.\n\n### Customize\n\n- The accepted VAT rates (0, 5, 20) are in Validate Before Booking. Change them for your country.\n- Add a duplicate check on supplier plus invoice number before you rely on it.\n\nSource: based on a template from the n8n Library at n8n.io, documented by LeenLabs.\nNot tested on live APIs."
      },
      "typeVersion": 1
    },
    {
      "id": "590f3c3d-10c2-4f96-8032-d04371beab33",
      "name": "Sticky Note 63eefec7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        740,
        -800
      ],
      "parameters": {
        "color": 7,
        "width": 400,
        "height": 480,
        "content": "### Read the invoice\n\nThe model is asked for a fixed set of fields and told to return null for anything it cannot read. The model is not named in this file, so the node uses its default. Check which model is selected."
      },
      "typeVersion": 1
    },
    {
      "id": "4d0bd788-acfa-45ac-8389-0901ab486d83",
      "name": "Sticky Note 69a5617a",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1180,
        -800
      ],
      "parameters": {
        "color": 7,
        "width": 460,
        "height": 520,
        "content": "### Check before booking\n\nThe code checks the arithmetic: line items against net, net plus VAT against gross, the VAT rate, and negative totals. Passing these checks does not prove an invoice is genuine or that it has not been entered before.\n\n**Before going live:** add a duplicate check on supplier plus invoice number."
      },
      "typeVersion": 1
    },
    {
      "id": "9b1d4e62-7a35-4c80-b2f1-6e8a0c3d5f47",
      "name": "Before you turn it on",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -60,
        -1500
      ],
      "parameters": {
        "color": 3,
        "width": 760,
        "height": 680,
        "content": "## Before you turn it on\n\n1. Only the first attachment of each email is checked and read. If the PDF is not the first attachment, the email is skipped. Only the first PDF is processed.\n2. If a step fails before the check (for example the AI step), the run stops, nothing is written to either tab, and the email is not tried again.\n3. The code is built for UK invoices: it accepts VAT rates 0, 5 and 20 only, and assumes day/month/year when a date is unclear.\n4. If VAT cannot be read (null), the net-plus-VAT check is skipped, so an invoice can pass with a gross that differs from the net.\n5. An invoice that reconciles is not proof that it is real. Check unusual suppliers and bank details yourself.\n6. Credit notes (negative totals) are always sent to Exceptions.\n7. Supplier invoices are sent to OpenAI and written to Google Sheets. Use only the label you intend, and review who can send to it.\n8. Run it by hand on a few emails first. It checks the label every minute once switched on."
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "PDF To Text": {
      "main": [
        [
          {
            "node": "Read The Invoice",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extraction Model": {
      "ai_languageModel": [
        [
          {
            "node": "Read The Invoice",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Read The Invoice": {
      "main": [
        [
          {
            "node": "Validate Before Booking",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Did It Reconcile?": {
      "main": [
        [
          {
            "node": "Append To Books",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send To Exceptions Queue",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "New Invoice Email": {
      "main": [
        [
          {
            "node": "Only PDF Attachments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Only PDF Attachments": {
      "main": [
        [
          {
            "node": "PDF To Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Before Booking": {
      "main": [
        [
          {
            "node": "Did It Reconcile?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}