Hi all,
I’m Jessy, and I build n8n workflows for small UK businesses. This one is free in the template library. I’m posting it because the interesting part comes after the extraction.
What it does
It watches one Gmail label for supplier invoices. It pulls the PDF text and has OpenAI return a fixed set of fields. Then it checks those fields in plain code before writing a single row.
Template: Extract and validate Gmail invoice PDFs into Google Sheets with OpenAI | n8n workflow template
How it runs
- Gmail Trigger on a single label, rather than the whole inbox. A filter rule puts the label on supplier emails, and the workflow never sees anything else. I’d rather the automation only had read access to what it actually needs.
- A filter keeps PDF attachments only, so signatures, logos and calendar invites drop out.
- Extract from File turns the PDF into plain text.
- OpenAI with a strict schema returns supplier, invoice number, dates, currency, net, VAT, gross and line items. A field the model can’t find should come back empty instead of guessed, which is why the schema is strict.
- A Code node validates the result. Required fields must be present. Net plus VAT must roughly equal gross (there’s a tolerance for rounding). Line items must sum to the net, and the VAT rate has to be one you’ve allowed. Negative totals get flagged, since a credit note looks just like an invoice with the sign flipped.
- Google Sheets gets two tabs. Anything that passes goes to Invoices. Anything that fails goes to Exceptions, with the reason written next to it.
Why the check lives in code
A model can read an invoice well and still get the sums wrong, and you can’t easily tell when. Arithmetic in a Code node is cheap and gives the same answer every time. So the model only does the reading, JavaScript does the sums, and when they disagree a person looks at it.
A fix I made after publishing
When I re-ran the published version on a fresh n8n 2.38.1, the OpenAI node failed with a 400 because it set temperature to 0 and the default model rejected that. Taking the temperature option out fixed it, and that version has been on the template page since late September. If you imported it before then and saw a 400 on the OpenAI step, that’s the cause.
What it doesn’t do
- It has no OCR step. Scanned or photographed invoices won’t extract well from the PDF text, so put an OCR step in front if you get those.
- It doesn’t check for duplicate invoice numbers. If suppliers chase and re-send, add a lookup against the Invoices tab before the append.
- It writes to Sheets and stops there. Swapping the last node for Xero or QuickBooks is the obvious next step.
Setup, briefly: a Gmail credential and the label ID, an OpenAI credential, and a Google Sheet with Invoices and Exceptions tabs whose columns match the extracted fields. The sticky notes on the canvas walk through it.
I’d like to hear how others handle the exceptions side. Do you send them to a sheet, to Slack or to a proper queue? And has anyone found a reliable way to catch the case where the total sits on page three of a multi-page invoice?