BrowseWiz Logo
BrowseWiz on Discord
Abstract artwork of a four-step workflow on a deep indigo background: a lightning-bolt trigger, a receipt, a glowing AI sparkle and an upload arrow, joined by thick connectors

n8n Invoice Processing Workflow (with Flowchart): Collect Invoices and Receipts from Gmail

Tom
Tom

This n8n invoice processing workflow collects invoices and receipts from Gmail for a date range you choose. It downloads every PDF attachment, asks an OpenAI model whether each file is an invoice or a receipt, uploads the matches to a new Google Drive folder and, if you ask it to, emails the batch to your accountant. The flowchart below shows the path node by node, and because a Webhook node starts it, you can run it from a BrowseWiz chat message, curl or an NFC tag.

I published the workflow as n8n template 3719 in April 2025. This version uses the node names and settings n8n shows as of September 2026 and fixes three bugs in the original.

The n8n invoice processing workflow as a flowchart

n8n invoice processing workflow flowchart: Webhook, Drive folder, Gmail attachments, PDF text, OpenAI check, upload and email

The table adds the helper nodes the diagram leaves out.

Step

n8n node

What it does

1

Webhook

Receives startDate, endDate and sendEmail (POST)

2

Google Drive: Create a folder

Creates a folder named after the date range

3

Respond to Webhook

Replies at once with status 202 and the folder link

4

Edit Fields (Set), named Configure

Holds settings: match phrase, token limit, accountant's address

5

Gmail: Get many messages

Fetches emails with PDF attachments in the date range

6

Code

Makes each attachment a separate item

7

If: Is attachment a PDF?

Passes PDFs on; the rest stop at No Operation, do nothing

8

Extract From File: Extract From PDF

Reads the text of each PDF

9

If, OpenAI, Merge

Skips very long files, asks "invoice or receipt?", pairs answer and file

10

If: Is matched

Keeps files the model answered true for

11

Google Drive: Upload a file

Uploads each match to the new folder

12

If, Code, Gmail: Send a message

If sendEmail is true, mails all matches at once

How to build the invoice processing workflow in n8n

Import the template and add your Gmail, Google Drive and OpenAI credentials, or build it by hand with these settings.

Trigger and Drive folder

  • Webhook: HTTP Method POST, Authentication Header auth (the credential takes a header Name and a Value), Respond set to Using 'Respond to Webhook' Node.

  • Google Drive, named Create folder: Resource Folder, Operation Create, Folder Name {{ 'invoices_' + $json.body.startDate.split('T')[0] + '_' + $json.body.endDate.split('T')[0] }}, plus a Parent Drive and Parent Folder.

  • Respond to Webhook: Respond With JSON, Response Code option 202, and the folder link in the body:

{
  "status": "Accepted",
  "driveFolderUrl": "https://drive.google.com/drive/folders/{{ $json.id }}"
}

The workflow answers early because a month of email can take minutes, and on n8n Cloud a webhook that doesn't respond within 100 seconds fails with a 524 error. n8n runs parallel branches from the top of the canvas down, so keep Respond to Webhook above the Configure branch or the reply waits for the whole run.

The Configure node

An Edit Fields (Set) node named Configure holds the settings the other nodes read: Match on (for example "receipt or invoice"), maxTokenSize (8000), replyTokenSize (50), sendInvoicesTo and sendEmail. The original template compared sendEmail with the string "true", so a real JSON true from a chat tool never sent the email. This expression accepts both:

{{ String($('Webhook').item.json.body.sendEmail) === 'true' }}

Gmail attachments

  • Gmail: Resource Message, Operation Get Many, Return All on, Simplify off (Options only appear with Simplify off). Under Filters, set Search to has:attachment filename:pdf and map Received After and Received Before to the webhook's startDate and endDate. Under Options, turn on Download Attachments. Skip the Read Status filter; its default is unread emails only.

  • Code, in the default Run Once for All Items mode, gives each attachment its own item with the file in data:

const results = [];
for (const item of $input.all()) {
  for (const key of Object.keys(item.binary ?? {})) {
    results.push({ json: {}, binary: { data: item.binary[key] } });
  }
}
return results;

PDF text and AI classification

  • If: {{ $binary.data.fileExtension }} is equal to pdf, with the Ignore Case option on so INVOICE.PDF passes too. The false output goes to No Operation, do nothing.

  • Extract From File: Operation Extract From PDF, Input Binary Field data, and the Keep Source option set to Both. Without it the node drops the PDF, and the upload has nothing to send. This replaces the template's deprecated Read PDF node.

  • If, named "Is text within token limit?": one token is roughly four characters of English, so the condition divides the text length by 4 and compares it with maxTokenSize minus replyTokenSize.

  • OpenAI: Resource Text, Operation Message a Model, with this prompt:

Does this PDF file look like a {{ $('Configure').first().json['Match on'] }}?
Return "true" if it is and "false" if not. Reply only with lowercase true or false.
Filename: {{ $binary.data.fileName }}
Text: {{ $json.text }}
  • Merge: Mode Combine, Combine By Position, with OpenAI on input 1 and the token-limit If on input 2, so each answer lands next to its PDF.

  • If, named "Is matched": {{ $json.output[0].content[0].text }} is equal to true. That is where the current OpenAI node puts the reply with Simplify Output on; the template's older node version reads $json.message.content.

Upload and email

  • Google Drive: Resource File, Operation Upload, Input Data Field Name data, File Name {{ $binary.data.fileName }}, Parent Drive My Drive, Parent Folder By ID {{ $('Create folder').first().json.id }}.

  • An If checks sendEmail from Configure. On true, a Code node packs every matched file into one item so they go out in a single email. The template keyed the files by name, so two attachments called invoice.pdf became one. Numbered keys keep them all, and Gmail still names each attachment after its file:

const merged = { json: {}, binary: {} };
$input.all().forEach((item, i) => {
  merged.binary[`file_${i}`] = item.binary.data;
});
return [merged];
  • Gmail: Resource Message, Operation Send, To {{ $('Configure').first().json.sendInvoicesTo }}. The template shipped with a placeholder address here instead. Under Options, add Attachments with {{ Object.keys($binary).join(',') }} as the field names, and turn off Append n8n attribution if you don't want the footer.

Then publish the workflow (Publish button or Shift+P). The production URL only runs the published version; the test URL listens for 120 seconds after you select Listen for test event.

Trigger the n8n workflow from BrowseWiz in natural language

BrowseWiz is an AI side panel for Chrome and Edge. Its custom tools send a JSON body that the model fills in from your message to an endpoint you register, and an n8n Webhook accepts that body as it is.

  1. Copy the Production URL from the Webhook node.

  2. Open the side panel (Alt+B on Windows, Option+B on Mac), select the wrench icon, then Manage tools. On the Tools page, go to the custom tools section, labelled API/Webhook Tools.

  3. Fill in Tool Name (the label you'll see in the wrench menu), Endpoint URL (the webhook URL), HTTP Method (POST), Headers (the name and value from your Header auth credential, marked secret so BrowseWiz stores it securely) and this JSON Schema:

{
  "name": "getInvoicesAndReceiptsFromEmails",
  "description": "Finds receipts and invoices in Gmail within a date range, uploads them to a new Google Drive folder and optionally emails them to the accountant.",
  "parameters": {
    "type": "object",
    "properties": {
      "startDate": {
        "type": "string",
        "format": "date-time",
        "description": "Start of the email date range, ISO 8601."
      },
      "endDate": {
        "type": "string",
        "format": "date-time",
        "description": "End of the email date range, ISO 8601."
      },
      "sendEmail": {
        "type": "boolean",
        "description": "true to email the collected files to the accountant."
      }
    },
    "required": ["startDate", "endDate"]
  }
}

Check that the tool is switched on under Custom Tools in the wrench menu, then ask something like "Collect my invoices and receipts from August 2026 and email them to my accountant." The model turns that into a request body:

{
  "startDate": "2026-08-01T00:00:00Z",
  "endDate": "2026-09-01T00:00:00Z",
  "sendEmail": true
}

The webhook's reply goes back to the model, so the chat answer can include the Drive folder link. The rest of the run shows up in n8n's Executions tab.

The model decides by itself when to call a tool, and BrowseWiz's docs advise caution with endpoints that change data, so switch this one off when you don't need it. If it picks the wrong range for "last month", give it the dates.

Trigger an n8n workflow from an NFC tag, phone shortcut or curl

n8n has no NFC trigger node, so the tag has to make your phone call a webhook. A phone can't easily add the auth header the main webhook expects, so I give it a small workflow of its own:

  1. A Webhook node, HTTP Method GET, with the Only Run If option set to {{ $json.query.token === 'long-random-string' }}. Only URLs ending in ?token=long-random-string start a run.

  2. An HTTP Request node that POSTs to the main webhook's Production URL with your Header Auth credential (Authentication: Generic Credential Type) and a JSON body covering last month: startDate set to {{ $today.minus({ months: 1 }).startOf('month').toISO() }}, endDate to {{ $today.startOf('month').toISO() }}.

Then point the phone at the helper webhook's URL:

  • iPhone: in the Shortcuts app, build a shortcut with a Get Contents of URL action pointing at that URL, add an NFC automation trigger and scan the tag. Shortcuts ignores the tag's contents apart from its unique identifier, and Apple lists NFC among the automations that can run without asking.

  • Android: write the URL to the tag. On Android 16, scanning a tag that stores an http or https link opens it like any other link, which sends a GET request; from Android 17 you first tap an "open link" notification. Anyone who scans the tag can read the URL, token included.

  • curl, for testing the main webhook:

curl -X POST "https://your-n8n.example.com/webhook/<path>" \
  -H "X-Invoice-Key: <your-secret>" \
  -H "Content-Type: application/json" \
  -d '{"startDate":"2026-08-01T00:00:00Z","endDate":"2026-09-01T00:00:00Z","sendEmail":false}'

Variations: scanned receipts, separate folders, long files

  • Scanned receipts. A photographed receipt saved as a PDF has no text layer, so Extract From PDF returns little or nothing. Send the PDF itself as a File message (File Data from the binary field) in the OpenAI node's Message a Model; vision-capable models (gpt-4o and later) also get an image of each page. The Mistral AI node's Extract Text operation is an OCR alternative.

  • Separate invoices from receipts. A Text Classifier node with the categories invoice and receipt, and When No Clear Match set to Output on Extra, 'Other' Branch, can replace OpenAI, Merge and Is matched and send each category to its own folder. It needs a chat model sub-node such as OpenAI Chat Model or OpenRouter Chat Model (how OpenRouter and other OpenAI-compatible APIs work).

  • Long files. The false output of "Is text within token limit?" is unconnected in the template, so long PDFs vanish. Connect it to an upload into a "check manually" folder.

  • Model choice. A true or false answer doesn't need a frontier model; a small, cheap one keeps the cost per PDF low. For harder jobs such as reading scanned receipts, our comparison of frontier AI models helps you pick.

  • First runs. Classification makes mistakes, so run with sendEmail set to false and check the folder before anything reaches your accountant.

FAQ

How do I see an n8n workflow as a flowchart?

The n8n editor canvas already works as one. Nodes are steps, connections are arrows, and If nodes split into true and false outputs. Press 1 to zoom to fit, Shift+S to add a sticky note, and on n8n 2.31.0 or later Ctrl/Cmd+G to put related nodes in a named Canvas Group. For a diagram outside n8n, download the workflow JSON from the editor menu and write the node names as a Mermaid flowchart LR, which renders in editors such as mermaid.live.

Can n8n process invoices automatically?

Yes. This workflow runs on demand through its webhook, but the same chain can start from a Schedule Trigger on the first of each month (with the $today date expressions above) or from a Gmail Trigger for each new email. Spot-check what the AI picks.

How do I trigger an n8n workflow from an NFC tag?

n8n has no NFC trigger node, so the tag has to make your phone call a Webhook URL. On iPhone, a Shortcuts automation with the NFC trigger runs a Get Contents of URL action. On Android, a tag holding the URL opens it as a GET request, so the Webhook node must accept GET.

What replaced the Read PDF node in n8n?

Extract From File with the Extract From PDF operation. Read PDF is on n8n's deprecated list, so it still runs but gets no fixes. When you swap it, set Keep Source to Both or the PDF is dropped from the item.

Tom's avatar

About Tom

I'm a software engineer and solutions architect specializing in AI-driven tools and productivity software, passionate about helping users reclaim their valuable time through intelligent automation.