A.O.Akinloluwa Olumuyide, home

ShippedDemo client

n8n workflow · 2026

Intelligent invoice processing

Invoices read from a Gmail inbox by Claude Haiku 4.5, checked against the email itself, de-duplicated and logged. Anything doubtful lands on its own tab, with the reason.

The number

1 min

from an invoice landing in the inbox to the workflow picking it up, when live

Client
Novus Realty
Role
Built end to end: spec, build, deploy
Timeline
2026
Stack
n8n · Gmail · Claude Haiku 4.5 · Google Sheets

Watch the walkthrough

the walkthroughWatch on Loom (opens in a new tab)

The problem

Invoices arrive by email in every shape: a PDF, a few lines in the message, a reply on an old thread, another language. Each one has to be read, checked and logged.

Anything that fails a check has to land where a person will see it, with the reason.

  • Nothing invented
  • No bill lost quietly

What I asked first

The brief left these open. I answered each one in the workflow itself.

  1. What counts as an invoice? A request to pay for work already done, with an amount due. Not a quote, a receipt for a payment already made, or a request to change bank details.
  2. What if a field isn't in the email? It stays empty. A plausible guess is worse than a blank, and every value must appear in the email it came from.
  3. What if the same invoice arrives twice? Same vendor, number and total is a duplicate. The same number with a new total is a correction, and it's kept.
  4. What about replies and forwards? Quoted replies are stripped first, so a field never comes from an older invoice sitting underneath.
  5. Which formats and languages? European and UK amounts, dates left empty when they're ambiguous, any currency but never a default, and invoices in other languages.
  6. When does an email start costing money? Only once it looks like an invoice. Cheap checks run first, and a re-run of the same email makes no AI call.

How it runs

Intelligent invoice processing · 28 n8n nodes in 7 stages
  • Start
  • Tool
  • Check
  • AI
  • Result
  • Error
  1. 01, Trigger:

    An email arrives

    Gmail

    Obvious non-invoices stop here.

    • Gmail Trigger
    • Normalise & Prefilter
    • Invoice Candidate?
    • Stop: Not A Candidate
  2. 02, Step:

    Read the invoice text

    PDF or email body

    No readable text? No AI call.

    • Has PDF Attachment?
    • Extract PDF Text
    • Attach PDF Text
    • Use Email Body
    • Text Quality Gate
    • Text Usable?
  3. 03, Logic:

    Seen it before?

    Google Sheets

    Each email is handled once.

    • Read Processed IDs
    • Decide: Already Seen
    • Already Processed?
    • Stop: Already Processed
  4. 04, AI:

    Classify and extract

    Claude Haiku 4.5

    Is it an invoice, and what does it say?

    • Build Claude Request
    • Claude: Classify & Extract
    • Parse Claude Response
    • Parsed OK?
    • Is Invoice?
    • Mark Not Invoice
  5. 05, Logic:

    Check it

    Code

    Fields checked, duplicates caught.

    • Validate Fields
    • Read Existing Invoices
    • Decide: Duplicate
    • Is Duplicate?

    Then: step 6; step 7 (fails a check).

  6. 06, Output:

    Log the invoice

    Google Sheets

    Clean invoices only.

    • Build Invoice Row
    • Append Invoice Row
  7. 07, Error:

    Log the exception

    Google Sheets

    Anything that fails a check, with the reason.

    • Build Exception Row
    • Append Exception Row

Enforced, not requested

  1. Nothing invented An invoice number that isn't in the email is rejected, not logged.
  2. Unreadable PDFs never reach the model A scan with no real text is flagged before any AI call.
  3. No AI cost until it looks like an invoice Invoice words or a PDF, checked first, for free.
  4. Each email once A re-run stops before the model, so it costs nothing.
  5. A correction isn't a duplicate Same number and total is a duplicate. A new total is kept.
  6. No default currency If the email doesn't say, the currency stays empty for a person.
  7. A cut-off answer isn't trusted If the model's reply is cut short, it goes to Exceptions.
  8. Every failure has a reason One Exceptions row per problem, labelled, with enough to act on.

The thing that nearly got past me

Incident

A scanned invoice logged as "not an invoice"

My first text check only asked how much text came back. A scanned PDF can carry a text layer that's nothing but a link, and that passed. The model read the link, found no invoice, and said so. A real bill would have disappeared without anyone seeing it.

The fixThe check now asks whether the text looks like a document: real words once links are removed, and numbers. And if a PDF gave up little text and the model says it isn't an invoice, it's logged as unread, for a person to check.

In numbers

  • 10

    emails in the test pack

  • 8

    cases the test pack covers

  • 0

    AI calls when the same email runs again. It stops before the model.

  • 12

    invoice words in English, French, Dutch and German, checked before any AI cost

  • 6

    named reasons an email can land on Exceptions

From the test pack and the workflow itself.

What it refuses to do

  • Invent a value A missing field stays empty.
  • Send an unreadable PDF to the model It's flagged first.
  • Log a duplicate as a new invoice Duplicates go to the Exceptions tab.
  • Read fields from an older email Quoted replies and forwards are stripped first.

What I took from it

A plausible guess is worse than a blank. Nothing downstream can tell a real invoice number from a made-up one.

The limit I'd fix first

if I did it again

  • Scanned invoices need OCR. A scan has no text to read, so it goes to Exceptions for a person.
  • One invoice per email. An email carrying several invoices only has its first PDF read.

Check it yourself

Contact

Got a messy problem?

Tell me what's broken, or what your team keeps doing by hand. Hiring? Tell me about the role. Or just book a call.