AutomationFlowsAI & RAG › Extract Invoice and Receipt Data From Gmail Pdfs to Google Sheets with Openai

Extract Invoice and Receipt Data From Gmail Pdfs to Google Sheets with Openai

ByZiad Karim @ziadkarim on n8n.io

Emails with PDF attachments are parsed automatically: AI extracts vendor, invoice number, dates and amounts into Google Sheets — validated in code (net + tax vs total), deduplicated against the sheet, and doubtful extractions are emailed to you flagged "Needs review".…

Event trigger★★★★☆ complexityAI-powered20 nodesGmail TriggerChain LlmOpenAI ChatGoogle SheetsGmail
AI & RAG Trigger: Event Nodes: 20 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow corresponds to n8n.io template #18083 — we link there as the canonical source.

This workflow follows the Chainllm → Gmail recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "id": "db2fc52c-f939-50",
  "name": "Extract invoice and receipt data from Gmail to Google Sheets with AI",
  "nodes": [
    {
      "id": "2fd80222-2e9d-5130-8999-03edbc5fc2dd",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1180,
        -260
      ],
      "parameters": {
        "width": 520,
        "height": 1300,
        "content": "## Extract invoice and receipt data from Gmail to Google Sheets with AI\n\n### How it works\n\nEvery email with a PDF attachment is checked automatically: the PDF's text is extracted right inside n8n (no OCR service, no per-page fees) and AI pulls out the fields that matter \u2014 vendor, invoice number, dates, currency, net, tax and total \u2014 into one clean row in Google Sheets.\n\nThis is built for real bookkeeping, not a demo: emails without a PDF are filtered out up front; the AI's answer is **validated in code** (strict JSON, currency normalized, and net + tax is checked against the total); the sheet is **checked for the invoice number first so nothing is ever logged twice**; and anything the AI isn't confident about \u2014 wrong arithmetic, missing total, a document that may not be an invoice \u2014 is still logged, but flagged **Needs review** and emailed to you for a human check. Clean extractions flow straight through untouched.\n\nWorks with invoices, receipts and order confirmations in any language the AI reads \u2014 which is most of them.\n\n### Setup steps\n\nTakes \u224810 minutes.\n\n- Connect your Gmail account in **When Invoice Email Received**, **Send Manual Review Alert** (one click each on n8n Cloud), and put your own address in the review node's *To* field.\n- Connect an OpenAI (or compatible) credential in **AI Invoice Analyzer**.\n- Create a spreadsheet with columns `Date, Vendor, Invoice number, Invoice date, Type, Currency, Net, Tax, Total, Status, Email subject` and select it in **Read Invoice Number from Sheets** and **Append Invoice to Sheets Log**.\n- Activate the workflow and email yourself a PDF invoice to watch the first row appear.\n\n### Customization\n\n- **Tighter trigger:** in the trigger's search filter, narrow `has:attachment` to e.g. `has:attachment (invoice OR receipt OR order)` or a specific sender to skip irrelevant PDFs.\n- **Stricter review:** the confidence threshold (0.7) and the arithmetic tolerance live in **Validate AI Invoice Data** \u2014 raise them and more rows go to a human first.\n- **More fields:** add `payment_terms`, `iban`, `line_items` or a VAT-id to the JSON schema in **Extract Invoice Fields with AI** and matching columns in the sheet.\n- **Scanned PDFs:** text extraction needs a text layer. For photo-scans, insert an OCR step (e.g. Mistral OCR) between extraction and the AI \u2014 the rest is unchanged.\n- **Accounting software:** swap Sheets for Xero, QuickBooks or a database node."
      },
      "typeVersion": 1
    },
    {
      "id": "af26c056-c603-5818-b59f-54cef7516070",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -580,
        -260
      ],
      "parameters": {
        "color": 7,
        "width": 760,
        "height": 620,
        "content": "## Watch Gmail and keep only PDFs\n\nPolls every minute for emails with attachments. A guard checks the attachment really is a PDF \u2014 newsletters with images and calendar invites are skipped before any AI cost is incurred."
      },
      "typeVersion": 1
    },
    {
      "id": "cc7f7caa-2543-5005-8109-9fa6daf306ac",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        236,
        -260
      ],
      "parameters": {
        "color": 7,
        "width": 280,
        "height": 420,
        "content": "## Read the PDF\n\nExtracts the text layer from the first PDF attachment directly inside n8n \u2014 no external OCR service and no per-document fees."
      },
      "typeVersion": 1
    },
    {
      "id": "91d0ab34-5b45-5419-8b25-7d1ea6b91d25",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        572,
        -260
      ],
      "parameters": {
        "color": 7,
        "width": 520,
        "height": 600,
        "content": "## Extract and validate the fields with AI\n\nThe AI returns strict JSON with the invoice fields plus its own confidence score. The Code node then validates it: currency normalized, net + tax checked against the total, and anything doubtful is marked for human review instead of being trusted blindly."
      },
      "typeVersion": 1
    },
    {
      "id": "12f0d0a7-2d88-5c59-ba3e-32b12a384f10",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1148,
        -260
      ],
      "parameters": {
        "color": 7,
        "width": 1000,
        "height": 600,
        "content": "## Never log the same invoice twice\n\nLooks the invoice number up in the sheet before writing. If it is already there \u2014 a forwarded copy, a reminder email, a duplicate send \u2014 the run stops instead of double-booking it."
      },
      "typeVersion": 1
    },
    {
      "id": "a05a7293-6dca-5067-890c-398e536c1526",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2204,
        -260
      ],
      "parameters": {
        "color": 7,
        "width": 520,
        "height": 620,
        "content": "## Human review only when it's needed\n\nDoubtful extractions are emailed to you with the reason and logged as 'Needs review'; clean ones are logged as 'Auto' and never bother you. One sheet, full audit trail."
      },
      "typeVersion": 1
    },
    {
      "id": "1215b091-858b-52bc-9652-01ca6c6283a4",
      "name": "When Invoice Email Received",
      "type": "n8n-nodes-base.gmailTrigger",
      "position": [
        -498,
        -53.60000000000002
      ],
      "parameters": {
        "simple": false,
        "filters": {
          "q": "has:attachment"
        },
        "options": {
          "downloadAttachments": true
        },
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "843c4d03-05c9-5fbf-bcd1-839eb6a5ee7e",
      "name": "Check for PDF Attachment",
      "type": "n8n-nodes-base.if",
      "position": [
        -258,
        -53.60000000000002
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-has-pdf",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ (($binary.attachment_0 || {}).mimeType || '').toLowerCase().includes('pdf') }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "649ad64c-b494-51db-b135-3cf99daf1d7a",
      "name": "Skip Non-PDF Emails",
      "type": "n8n-nodes-base.noOp",
      "position": [
        -18,
        146.39999999999998
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "1372a7fc-06f7-55ab-854e-8eee59f59ccf",
      "name": "Extract PDF Text",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        318,
        -53.60000000000002
      ],
      "parameters": {
        "options": {},
        "operation": "pdf",
        "binaryPropertyName": "attachment_0"
      },
      "typeVersion": 1
    },
    {
      "id": "d78dcbdb-2a15-57f0-9540-6fe918ae9abf",
      "name": "Extract Invoice Fields with AI",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        654,
        -53.60000000000002
      ],
      "parameters": {
        "text": "=Extract the invoice fields from this document text:\n\n{{ ($json.text || '').slice(0, 15000) }}",
        "messages": {
          "messageValues": [
            {
              "message": "You are an accounting data-extraction engine. Return ONLY one JSON object, no markdown fences, with exactly these keys:\n{\"vendor\": string, \"invoice_number\": string|null, \"invoice_date\": \"YYYY-MM-DD\"|null, \"document_type\": \"invoice\"|\"receipt\"|\"order\"|\"other\", \"currency\": string|null, \"net_amount\": number|null, \"tax_amount\": number|null, \"total_amount\": number|null, \"confidence\": number}\n\nRules: parse numbers as plain numbers without thousands separators; confidence is 0 to 1 and reflects how sure you are the fields are right; if the document is clearly not an invoice, receipt or order confirmation, return {\"vendor\": null, \"confidence\": 0}."
            }
          ]
        },
        "promptType": "define"
      },
      "typeVersion": 1.5
    },
    {
      "id": "797347b6-1952-56ab-90a0-ecbf250b4b70",
      "name": "AI Invoice Analyzer",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        654,
        146.39999999999998
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini",
          "cachedResultName": "gpt-4o-mini"
        },
        "options": {}
      },
      "typeVersion": 1.2
    },
    {
      "id": "bcd657ad-3120-5133-b0e0-e842bb4890de",
      "name": "Validate AI Invoice Data",
      "type": "n8n-nodes-base.code",
      "position": [
        894,
        -53.60000000000002
      ],
      "parameters": {
        "jsCode": "let s = String($input.first().json.text ?? '').trim();\ns = s.replace(/^```(?:json)?/i, '').replace(/```\\s*$/, '').trim();\nconst start = s.indexOf('{');\nconst end = s.lastIndexOf('}');\nif (start === -1 || end === -1) {\n  throw new Error('The AI did not return a JSON object.');\n}\nconst inv = JSON.parse(s.slice(start, end + 1));\n\nconst reasons = [];\nif (!inv.vendor) reasons.push('the AI does not think this is an invoice or receipt');\nif (typeof inv.confidence === 'number' && inv.confidence < 0.7) {\n  reasons.push('extraction confidence is only ' + inv.confidence);\n}\nconst net = inv.net_amount, tax = inv.tax_amount, total = inv.total_amount;\nif ([net, tax, total].every((v) => typeof v === 'number') && Math.abs(net + tax - total) > 0.02) {\n  reasons.push('net + tax does not match the total (' + net + ' + ' + tax + ' vs ' + total + ')');\n}\nif (total == null) reasons.push('no total amount found');\nif (inv.currency) inv.currency = String(inv.currency).toUpperCase();\n\nreturn [{ json: {\n  ...inv,\n  vendor: inv.vendor ?? '(not detected)',\n  needs_review: reasons.length > 0,\n  review_reason: reasons.join('; '),\n} }];"
      },
      "typeVersion": 2
    },
    {
      "id": "576720e2-c2ad-5cfc-82dc-7462460888ae",
      "name": "Read Invoice Number from Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1230,
        -53.60000000000002
      ],
      "parameters": {
        "options": {},
        "filtersUI": {
          "values": [
            {
              "lookupValue": "={{ $json.invoice_number ?? '__none__' }}",
              "lookupColumn": "Invoice number"
            }
          ]
        },
        "operation": "read",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "combineFilters": "AND"
      },
      "typeVersion": 4.5,
      "alwaysOutputData": true
    },
    {
      "id": "0887f7d9-75e9-5fb5-bab7-f04fb0881f53",
      "name": "Determine Duplicate Status",
      "type": "n8n-nodes-base.code",
      "position": [
        1470,
        -53.60000000000002
      ],
      "parameters": {
        "jsCode": "const inv = $('Validate AI Invoice Data').first().json;\nconst rows = $input.all().map((i) => i.json).filter((r) => r.row_number !== undefined);\nconst duplicate = Boolean(inv.invoice_number) && rows.length > 0;\nreturn [{ json: { ...inv, duplicate } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "bfa89a40-4582-55d6-865c-8a98c3c4ecbb",
      "name": "Check Invoice Logged Status",
      "type": "n8n-nodes-base.if",
      "position": [
        1710,
        -53.60000000000002
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-duplicate",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.duplicate }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "22daa828-f8a2-54d6-a5f6-3b0d1f559bf5",
      "name": "Bypass Logged Invoice",
      "type": "n8n-nodes-base.noOp",
      "position": [
        1950,
        146.39999999999998
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "f4eef704-592e-58bc-aa46-e7d726b7bb5f",
      "name": "Assess Need for Manual Review",
      "type": "n8n-nodes-base.if",
      "position": [
        2286,
        -53.60000000000002
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-needs-review",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.needs_review }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "2b0ffe1e-f097-56b6-92a8-b368e99641a8",
      "name": "Send Manual Review Alert",
      "type": "n8n-nodes-base.gmail",
      "position": [
        2526,
        146.39999999999998
      ],
      "parameters": {
        "sendTo": "user@example.com",
        "message": "=This extraction was flagged for a human check.\n\nReason: {{ $json.review_reason }}\n\nVendor: {{ $json.vendor }}\nInvoice number: {{ $json.invoice_number ?? '\u2014' }}\nDate: {{ $json.invoice_date ?? '\u2014' }}\nNet / Tax / Total: {{ $json.net_amount ?? '\u2014' }} / {{ $json.tax_amount ?? '\u2014' }} / {{ $json.total_amount ?? '\u2014' }} {{ $json.currency ?? '' }}\n\nSource email: {{ $('When Invoice Email Received').item.json.subject ?? '' }}\n\nIt was still logged to the sheet with status 'Needs review' \u2014 correct it there.",
        "options": {},
        "subject": "=\u26a0\ufe0f Invoice needs review: {{ $json.vendor }} \u2014 {{ $json.total_amount ?? '?' }} {{ $json.currency ?? '' }}"
      },
      "typeVersion": 2.1
    },
    {
      "id": "6db9dfe7-15f0-5ab5-a92f-4ccf45b4a524",
      "name": "Append Invoice to Sheets Log",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2526,
        -53.60000000000002
      ],
      "parameters": {
        "columns": {
          "value": {
            "Net": "={{ $json.net_amount }}",
            "Tax": "={{ $json.tax_amount }}",
            "Date": "={{ $now.toISO() }}",
            "Type": "={{ $json.document_type ?? '' }}",
            "Total": "={{ $json.total_amount }}",
            "Status": "={{ $json.needs_review ? 'Needs review' : 'Auto' }}",
            "Vendor": "={{ $json.vendor }}",
            "Currency": "={{ $json.currency }}",
            "Invoice date": "={{ $json.invoice_date }}",
            "Email subject": "={{ $('When Invoice Email Received').item.json.subject ?? '' }}",
            "Invoice number": "={{ $json.invoice_number }}"
          },
          "schema": [],
          "mappingMode": "defineBelow",
          "matchingColumns": []
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        }
      },
      "typeVersion": 4.5
    }
  ],
  "settings": {
    "executionOrder": "v1"
  },
  "connections": {
    "Extract PDF Text": {
      "main": [
        [
          {
            "node": "Extract Invoice Fields with AI",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Invoice Analyzer": {
      "ai_languageModel": [
        [
          {
            "node": "Extract Invoice Fields with AI",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Check for PDF Attachment": {
      "main": [
        [
          {
            "node": "Extract PDF Text",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Skip Non-PDF Emails",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate AI Invoice Data": {
      "main": [
        [
          {
            "node": "Read Invoice Number from Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Determine Duplicate Status": {
      "main": [
        [
          {
            "node": "Check Invoice Logged Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Invoice Logged Status": {
      "main": [
        [
          {
            "node": "Bypass Logged Invoice",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Assess Need for Manual Review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When Invoice Email Received": {
      "main": [
        [
          {
            "node": "Check for PDF Attachment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Assess Need for Manual Review": {
      "main": [
        [
          {
            "node": "Send Manual Review Alert",
            "type": "main",
            "index": 0
          },
          {
            "node": "Append Invoice to Sheets Log",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Append Invoice to Sheets Log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Invoice Fields with AI": {
      "main": [
        [
          {
            "node": "Validate AI Invoice Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Invoice Number from Sheets": {
      "main": [
        [
          {
            "node": "Determine Duplicate Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Emails with PDF attachments are parsed automatically: AI extracts vendor, invoice number, dates and amounts into Google Sheets — validated in code (net + tax vs total), deduplicated against the sheet, and doubtful extractions are emailed to you flagged "Needs review".…

Source: https://n8n.io/workflows/18083/ — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

This workflow automates the full lifecycle of a vehicle insurance claim — from an incoming Gmail email to a signed, watermarked PDF decision letter delivered back to the claimant.

Gmail Trigger, N8N Nodes Pdf Api Hub, Google Sheets +4
AI & RAG

AI Agents Vs AI Workflow. Uses lmChatOpenAi, gmailTrigger, gmail, gmailTool. Event-driven trigger; 30 nodes.

OpenAI Chat, Gmail Trigger, Gmail +7
AI & RAG

What this workflow does:

Gmail Trigger, OpenAI Chat, Google Sheets +6
AI & RAG

This workflow logs Gmail receipt attachments to Google Sheets by extracting structured fields with OpenAI (including category selection from your list), prevents duplicates by checking the ledger, and

Gmail Trigger, Google Sheets, OpenAI +4
AI & RAG

This template is designed for freelancers, lead generation specialists, and outreach managers; particularly those running email outreach campaigns for clients or personal lead-gen projects.

Gmail Trigger, OpenAI Chat, Chain Llm +2