How to automate
invoicing
with n8n + GPT-4o.
Practical guide with real code. By the end of this read you'll have a workflow that receives invoices by email, extracts data with AI, validates against your POs, and records in your ERP. Without typing.
TL;DR
An n8n workflow that listens to an invoices@ mailbox, sends each PDF to GPT-4o for structured extraction, validates the tax ID against your supplier master, cross-references the amount against open POs, and creates the entry in Holded/Odoo/Sage. Time per invoice: ~8 seconds. Replaces 4-7 minutes of manual work per invoice. This workflow is running today in real clients processing 8,000+ invoices/month.
Prerequisites
- n8n 1.40+ self-hosted (Docker recommended). If you don't have it,
npx n8nfor testing. - OpenAI API key with GPT-4o access (~$0.01 per typical invoice).
- ERP with REST API. We'll use Holded as example, but the logic is identical for Odoo, Sage, A3, Quipu, SAP B1.
- Dedicated IMAP mailbox for invoices (Gmail / Outlook / corporate).
- Basic knowledge of n8n: nodes, expressions, error handling.
01 · Prepare the n8n environment
Make sure you have n8n 1.40+ running self-host with IMAP credentials and OpenAI API key configured. Create a new workflow named "Supplier invoices → ERP".
02 · Configure the IMAP trigger
Add an Email Trigger (IMAP) node listening to your invoices@ mailbox. Filter by attachments with extension .pdf. Set check frequency every 5 minutes (or 1 min if you want near-real-time).
{
"mailbox": "INBOX",
"format": "resolved",
"options": {
"downloadAttachments": true,
"fileExtensions": ["pdf"]
}
}
03 · Extract data with GPT-4o Vision
Add an HTTP Request node calling OpenAI with a vision-capable model. Build the prompt to return strict JSON:
{
"model": "gpt-4o",
"messages": [
{"role": "system", "content": "You are an expert in commercial invoices. Extract the following fields and return ONLY valid JSON: tax_id, supplier_name, invoice_number, invoice_date, total_amount, vat_amount, line_items (array)."},
{"role": "user", "content": [
{"type": "text", "text": "Extract data from this invoice."},
{"type": "image_url", "image_url": {"url": "data:application/pdf;base64,{{$binary.attachment_0.data}}"}}
]}
],
"response_format": {"type": "json_object"}
}
04 · Validate against supplier master and POs
Validation in two steps:
- Tax ID validation: query your supplier master (Holded API or your DB). If it doesn't exist, escalate to human review with proposal to create a new supplier.
- Amount validation: cross-reference against open POs from this supplier. If the difference is > 1% of expected amount, flag as an exception.
05 · Post in the ERP
If everything validates, create the invoice in Holded with the original PDF attached. Use the official Holded API endpoint POST /api/invoicing/v1/documents/purchase.
06 · Escalate exceptions to a human
Anything that doesn't validate cleanly goes to a Slack/Teams notification with the summary and a "Review" link to a simple internal interface (or simply assigns the invoice to a Trello/Notion board). Never let exceptions drop silently.
Common errors
- OCR fails on poorly scanned PDFs: add a previous step with
pdf-to-imageat high resolution and re-process. - Total mismatch (sum of lines vs total): common in invoices with rounding. Define a tolerance of ±1 cent before flagging.
- Different tax ID for the same supplier: sometimes the supplier has multiple legal entities (Holding S.L. vs Operating S.L.U.). Use a fuzzy match on the name, not just exact tax ID.
- Hardcoded credentials: always use n8n credentials, never plain values in HTTP nodes.