Invoice Intake & Anomaly Detection
Catches invoice math errors and duplicate payments before they cost money, with zero manual data entry.
Fully built and tested end to end in n8n, including Gmail intake, Gemini extraction, math validation, and duplicate prevention. Three test invoices (two valid, one duplicate resend) are included in the repo to demonstrate each path. Next phase is a real approve/reject loop back into the sheet, rather than resolving flagged invoices by hand.
A Gmail trigger polls for emails with a PDF attachment and 'invoice' in the subject line. The attached PDF goes to a Gemini-backed extraction step that returns invoice number, client name, vendor email, total amount, line item sum, invoice date, and due date as structured fields.
From there the workflow does two independent checks. A Code node recalculates the line items and compares that figure to the stated total, catching any mismatch down to the exact dollar difference. In parallel, a Sheets lookup checks whether that invoice number has already been logged. A Merge node brings both results back together before anything gets written anywhere.
Valid, non-duplicate invoices get one row in the Invoices tab and nothing else happens, no alert, no noise. A math discrepancy gets logged and triggers a Slack alert to the billing channel, since that's a real decision someone needs to make. A duplicate gets redirected entirely, it's written to a separate Duplicates Log tab instead of the main ledger, so there's still a full audit trail, but it never creates a second payable-looking row and never interrupts anyone in Slack.
- Duplicate detection matches on invoice number alone rather than a vendor plus invoice number composite key, a simpler design that's easier to reason about and reliable enough for a small, known vendor list
- A Filter node sits between the ledger write and the Slack notification so only a genuine anomaly reaches the channel, valid invoices never generate a message
- Duplicates get their own log tab instead of either flooding Slack or disappearing into n8n's execution history where only a developer would find them
- The first duplicate check design would have collided on any two vendors that happened to both issue an INV-1001. Weighed a composite key against the actual scope of the project and kept the simpler check, documented as a tradeoff rather than an oversight
- The first working version of the Slack alert fired on every processed invoice, which floods the channel fast and trains people to ignore it. Fixed by gating the alert behind a Filter node that only lets a real anomaly through
- Every external-call node (Gemini, Gmail, Sheets, Slack) has retry logic, three tries with a 4000ms backoff
- A dedicated Error Workflow is attached under this workflow's Settings, so a node failure alerts separately from the business-logic Slack alerts
- A flagged invoice still gets a row in the sheet even when it's flagged, nothing about a mismatch stops the record from existing
- Gmail Trigger polls every 60 seconds, so there's a small, predictable lag rather than a missed email
- External API calls retry three times with a 4-second wait before the error workflow takes over
- Only the first PDF attachment on an email is processed, deliberate scope limit rather than a silent gap
- Totals are parsed as plain numbers with currency symbols stripped, single-currency assumption stated up front rather than hidden
- No OCR fallback yet, a scanned invoice with no text layer fails extraction rather than being flagged for manual review, noted as a known limitation, not a surprise
Separating visibility from alerting. A duplicate still gets recorded somewhere a human can find it, but it never has to interrupt anyone, because there's no decision left for a person to make once the system has already handled it correctly.
Getting the duplicate check design right without over-engineering it. It would have been easy to build a compound vendor-plus-invoice-number key from day one. The harder, better call was recognizing the simpler check was actually the right size for this project.
- Slack approve/reject buttons that write a decision back to the ledger
- A weekly digest summarizing total dollars processed and flags caught
- Multi-currency normalization
- Vendor trust scoring based on historical discrepancy rate
- OCR fallback for scanned, non-text PDFs
- Budget-threshold routing so invoices above a set amount require manager sign-off
The Global Error Handler
Every workflow on this site, including this one, reports into the same error handler instead of failing silently. When any node in any workflow throws, n8n's Error Trigger catches it, a small code step pulls out the workflow name, the node that failed, and the actual error message, and it lands in my inbox as a plain email alert. One shared piece of infrastructure instead of duplicating error-handling logic four separate times.