Purchase Order Automation: 5 n8n lessons from a real client build [Workflow Included]
▲ 22 r/documentAutomation+6 crossposts

Purchase Order Automation: 5 n8n lessons from a real client build [Workflow Included]

👋 Hey n8n community,

A few of you asked for more detail after my Purchase Order extractor post yesterday. The build looked simple on the surface, but a handful of things bit me along the way. Here are the five that cost me the most time, in case they save you some.

1. Only numbered rows are real articles. The POs had little note lines wedged between items ("2 Box", a location note under a lock, etc). My extraction kept swallowing those into the neighbouring product name, so one row would come out as two items mashed together. The fix wasn't in the workflow at all, it was in the document description I gave the extractor: spell out that only rows with a number in the No. column are real articles, and that unnumbered lines are notes belonging to the row above. That one paragraph cleaned up every merge. Lesson: when a document extraction misbehaves, describe the document better before you touch the nodes.

2. Keep numbers as text until you've checked for empties. Tempting to pull quantity and price straight as numbers. Don't, at least not yet. A missing field comes back as the string "null", and if the field is typed as a number that signal turns into a 0 or a real null and you lose the ability to spot the miss. I keep everything as text, run my checks, then cast to a number afterwards. Clean signal in, clean data out.

3. One helper to catch every flavour of empty. "Empty" is never just one thing. Across the docs I saw real null, the string "null", empty strings, and whitespace. I stopped writing one-off checks and made a single isMissing() helper that catches all of them, then used it everywhere. If a field is missing, the workflow flags it on the form's completion screen with the document name and which field failed, so my friend knows exactly which PO to eyeball instead of trusting the sheet blindly.

4. The Google Sheets checkbox that quietly broke everything. This one nearly broke me. New rows kept landing at the very bottom of the sheet instead of the top empty row. Turns out I'd added a checkbox column with Insert > Tick box, which silently writes a FALSE value into every one of the 1000 cells in the column. The Append node counts those as data, so it always appended below them. If you want checkboxes, add them through Data → Data validation instead, which draws the box without writing a value. Hours lost to a column that looked empty but wasn't.

5. Batch size 1 is not optional here. The extractor node bundles every input item into a single call, so if you hand it two PDFs at once you get one jumbled result. Wrapping it in a Loop Over Items with batch size 1 means each document gets its own clean pass. Small setting, big difference, and easy to miss until your two-file test comes back merged.

The whole thing runs on the easybits extractor node for the actual PDF reading, the rest is stock n8n. If you want to try the Purchase Order extractor yourself, feel free to grab it here:
https://github.com/felix-sattler-easybits/n8n-workflows/blob/c38749a68fd6ea4ae6ebff41789d35cceaacdef1/easybits-purchase-order-extractor-workflow/easybits_purchase_order_extractor_workflow.json

What's the dumbest time-sink you've hit on a build that looked easy? Always happy to hear how other builders tackle this stuff.

Best,
Felix

u/easybits_ai — 3 days ago
▲ 40 r/pdf+7 crossposts

Purchase Order Automation in n8n – extract PO data straight into a Google Sheet [Workflow Included]

👋 Hey n8n community,

Last week I posted a workflow I built for a friend who runs an online shop (find it here). He called me again a few days later with a new headache: he's drowning in Purchase Orders. Every single one gets opened by hand, the data typed into a Google Sheet, and that sheet uploaded into his ERP to update his numbers. Hours a week, pure copy-paste.

So I built him something to kill that step. He uploads the PO PDFs through a simple n8n form, and a structured Google Sheet comes out the other end. He just downloads it and pushes it to his ERP.

How it's set up:

The form accepts multiple PDFs at once, so he can batch a whole stack instead of doing them one by one. Each PO loops through on its own so nothing gets jumbled.

The extraction runs on the easybits Extractor node (@easybits/n8n-nodes-extractor). I set the field structure up in two parts: the header fields that appear once per PO (PO number, PO date, delivery date, mark for, PR number, reference no), plus an articles array for the line items, each holding article name, unit and quantity. That array is the key bit, it gives you one entry per row of the PO table, and I flatten it into one sheet row per article with the header details repeated on each.

Two things I added because real documents are messy:

Error flagging. If any field comes back empty, the completion screen lists which document and which field didn't extract cleanly, so he knows exactly which PO to double-check instead of trusting it blindly.

Document name column. The original filename lands in the sheet next to every row, so if a number looks off he can jump straight back to the source PDF.

Workflow JSON is on GitHub: https://github.com/felix-sattler-easybits/n8n-workflows/blob/c38749a68fd6ea4ae6ebff41789d35cceaacdef1/easybits-purchase-order-extractor-workflow/easybits_purchase_order_extractor_workflow.json

I also made a short video showing how the workflow works.

Anyone else automating document-to-sheet data entry? Curious how you're handling the messy multi-line rows – that was the trickiest part to get right.

Best,
Felix

u/easybits_ai — 4 days ago
▲ 12 r/n8nforbeginners+5 crossposts

Built an extraction tool for my own projects, then realised it describes product images too

👋 Hey n8n Community,

Bit of background first. I originally built this extraction setup to serve my own automation projects, mostly document classification and pulling structured data out of PDFs and invoices. It worked so well for me internally that I decided to open it up so anyone can use it.

The new use case

In my last project I needed product photos described so I could feed that into content generation, and I almost reached for a separate vision model. Turns out the Extractor handles images really well, which is the moment it clicked that this is more than a classic data-extraction tool.

How it works

Instead of pointing a pipeline at a document, you point it at the image and define the fields as questions about what's visible: dominant colour, visible features, material, the type of shot. It comes back as structured fields, exactly like a document extraction, so the result drops straight into the rest of the workflow with no paragraph to parse and clean up.

The workflow

I recorded a short video walking through it, attached to this post. You can also see the actual workflow I built around this case here: https://github.com/felix-sattler-easybits/n8n-workflows/tree/e3103344d9b3358402dc38a3a862d510bb4e7c5e/easybits-product-content-creation-workflow

Setup

  • n8n Cloud: it's a verified node, just search easybits Extractor in the node panel. Nothing to install.
  • Self-hosted: Settings → Community Nodes → Install → '@easybits/n8n-nodes-extractor'.

Then create a pipeline, define your fields, and point it at an image instead of a doc. There's a free plan with 50 API requests a month included, which is plenty to test the image use case end to end.

Anyone else using extraction tools for image understanding rather than a dedicated vision model? Curious what you're pointing them at.

Best,
Felix

u/easybits_ai — 10 days ago

[Workflow Included] I built an n8n pipeline that turns messy supplier docs into publish-ready store content

Frontend page of the shipped solution

👋 Hey VibeCoders Community,

A friend of mine runs an online store, and for every new product they get supplier inputs in whatever format the supplier feels like: spec PDFs, Excel sheets, a few photos, some loose notes. Someone then hand-writes the title, descriptions, specs and SEO fields. I built them a pipeline that does it end to end, and I'm sharing all four workflows.

What it does: intake form → extract specs → analyse photos → generate content → poll status. Drop in the files and notes, get back review-ready content (title, descriptions, meta fields, features, tags, attributes).

The four workflows

  • WF1 – Intake & spec extraction. Saves files to Drive, routes each by type (PDFs/images → easybits Extractor, Excel → Code node), merges into one spec object, resolves brand, hands off to WF2.
  • WF2 – Image analysis. Runs each photo through an Extractor pipeline to capture what's visible (colour, features, angle), then passes it to WF3.
  • WF3 – Content generation. Builds context from spec + image data + notes and has Gemini write the full content set. Hard rule: only features that are in the spec or visible in the images, no inventing.
  • WF4 – Status polling. A small webhook the frontend polls for progress and the finished draft.

Extractor setup

  • n8n Cloud: verified node, just search easybits Extractor in the node panel. No install.
  • Self-hosted: Settings → Community Nodes → Install → '@easybits/n8n-nodes-extractor'.

Then create a pipeline at easybits, define your fields, and paste the Pipeline ID + API key into the node. It reads the binary straight from the previous node.

Workflows (all four, sanitized): https://github.com/felix-sattler-easybits/n8n-workflows/tree/e3103344d9b3358402dc38a3a862d510bb4e7c5e/easybits-product-content-creation-workflow

Cross-workflow calls use placeholder IDs you re-point after import, plus your own Google + Extractor credentials.

How do you handle brand-voice consistency in generated content? I went with a per-brand profile the model reads from, curious if others template it harder.

Best,
Felix

reddit.com
u/easybits_ai — 10 days ago
▲ 5 r/automation+1 crossposts

[Workflow Included] I built an n8n pipeline that turns messy supplier docs into publish-ready store content

Frontend page of the shipped solution

👋 Hey AIStartupAutomation Community,

A friend of mine runs an online store, and for every new product they get supplier inputs in whatever format the supplier feels like: spec PDFs, Excel sheets, a few photos, some loose notes. Someone then hand-writes the title, descriptions, specs and SEO fields. I built them a pipeline that does it end to end, and I'm sharing all four workflows.

What it does: intake form → extract specs → analyse photos → generate content → poll status. Drop in the files and notes, get back review-ready content (title, descriptions, meta fields, features, tags, attributes).

The four workflows

  • WF1 – Intake & spec extraction. Saves files to Drive, routes each by type (PDFs/images → easybits Extractor, Excel → Code node), merges into one spec object, resolves brand, hands off to WF2.
  • WF2 – Image analysis. Runs each photo through an Extractor pipeline to capture what's visible (colour, features, angle), then passes it to WF3.
  • WF3 – Content generation. Builds context from spec + image data + notes and has Gemini write the full content set. Hard rule: only features that are in the spec or visible in the images, no inventing.
  • WF4 – Status polling. A small webhook the frontend polls for progress and the finished draft.

Extractor setup

  • n8n Cloud: verified node, just search easybits Extractor in the node panel. No install.
  • Self-hosted: Settings → Community Nodes → Install → '@easybits/n8n-nodes-extractor'.

Then create a pipeline at easybits, define your fields, and paste the Pipeline ID + API key into the node. It reads the binary straight from the previous node.

Workflows (all four, sanitized): https://github.com/felix-sattler-easybits/n8n-workflows/tree/e3103344d9b3358402dc38a3a862d510bb4e7c5e/easybits-product-content-creation-workflow

Cross-workflow calls use placeholder IDs you re-point after import, plus your own Google + Extractor credentials.

How do you handle brand-voice consistency in generated content? I went with a per-brand profile the model reads from, curious if others template it harder.

Best,
Felix

reddit.com
u/easybits_ai — 11 days ago

[Workflow Included] I built an n8n pipeline that turns messy supplier docs into publish-ready store content

Frontend page of the shipped solution

👋 Hey n8n for Beginners Community,

A friend of mine runs an online store, and for every new product they get supplier inputs in whatever format the supplier feels like: spec PDFs, Excel sheets, a few photos, some loose notes. Someone then hand-writes the title, descriptions, specs and SEO fields. I built them a pipeline that does it end to end, and I'm sharing all four workflows.

What it does: intake form → extract specs → analyse photos → generate content → poll status. Drop in the files and notes, get back review-ready content (title, descriptions, meta fields, features, tags, attributes).

The four workflows

  • WF1 – Intake & spec extraction. Saves files to Drive, routes each by type (PDFs/images → easybits Extractor, Excel → Code node), merges into one spec object, resolves brand, hands off to WF2.
  • WF2 – Image analysis. Runs each photo through an Extractor pipeline to capture what's visible (colour, features, angle), then passes it to WF3.
  • WF3 – Content generation. Builds context from spec + image data + notes and has Gemini write the full content set. Hard rule: only features that are in the spec or visible in the images, no inventing.
  • WF4 – Status polling. A small webhook the frontend polls for progress and the finished draft.

Extractor setup

  • n8n Cloud: verified node, just search easybits Extractor in the node panel. No install.
  • Self-hosted: Settings → Community Nodes → Install → '@easybits/n8n-nodes-extractor'.

Then create a pipeline at easybits, define your fields, and paste the Pipeline ID + API key into the node. It reads the binary straight from the previous node.

Workflows (all four, sanitized): https://github.com/felix-sattler-easybits/n8n-workflows/tree/e3103344d9b3358402dc38a3a862d510bb4e7c5e/easybits-product-content-creation-workflow

Cross-workflow calls use placeholder IDs you re-point after import, plus your own Google + Extractor credentials.

How do you handle brand-voice consistency in generated content? I went with a per-brand profile the model reads from, curious if others template it harder.

Best,
Felix

reddit.com
u/easybits_ai — 11 days ago
▲ 8 r/lowcode+2 crossposts

[Workflow Included] I built an n8n pipeline that turns messy supplier docs into publish-ready store content

Frontend of the shipped solution

👋 Hey n8n Community,

A friend of mine runs an online store, and for every new product they get supplier inputs in whatever format the supplier feels like: spec PDFs, Excel sheets, a few photos, some loose notes. Someone then hand-writes the title, descriptions, specs and SEO fields. I built them a pipeline that does it end to end, and I'm sharing all four workflows.

What it does: intake form → extract specs → analyse photos → generate content → poll status. Drop in the files and notes, get back review-ready content (title, descriptions, meta fields, features, tags, attributes).

The four workflows

  • WF1 – Intake & spec extraction. Saves files to Drive, routes each by type (PDFs/images → easybits Extractor, Excel → Code node), merges into one spec object, resolves brand, hands off to WF2.
  • WF2 – Image analysis. Runs each photo through an Extractor pipeline to capture what's visible (colour, features, angle), then passes it to WF3.
  • WF3 – Content generation. Builds context from spec + image data + notes and has Gemini write the full content set. Hard rule: only features that are in the spec or visible in the images, no inventing.
  • WF4 – Status polling. A small webhook the frontend polls for progress and the finished draft.

Extractor setup

  • n8n Cloud: verified node, just search easybits Extractor in the node panel. No install.
  • Self-hosted: Settings → Community Nodes → Install → '@easybits/n8n-nodes-extractor'.

Then create a pipeline at easybits, define your fields, and paste the Pipeline ID + API key into the node. It reads the binary straight from the previous node.

Workflows (all four, sanitized): https://github.com/felix-sattler-easybits/n8n-workflows/tree/e3103344d9b3358402dc38a3a862d510bb4e7c5e/easybits-product-content-creation-workflow

Cross-workflow calls use placeholder IDs you re-point after import, plus your own Google + Extractor credentials.

How do you handle brand-voice consistency in generated content? I went with a per-brand profile the model reads from, curious if others template it harder.

Best,
Felix

reddit.com
u/easybits_ai — 11 days ago

Batch invoice processing in n8n: upload multiple invoices via a form, extract the data in one go [Workflow included]

https://preview.redd.it/gzee1qkyk78h1.png?width=2724&format=png&auto=webp&s=9ea9d81b450f36f80e11fe981668cc3fdfba65c0

👋 Hey n8n business automation Community,

I've built a lot of finance workflows over the last few weeks and months for a couple of friends who run small businesses. Going back through my n8n library the other day, I realised I'd never actually shared the most basic one people keep asking me for: a simple batch invoice processing flow.

So I put one together. You upload multiple invoices through a single form (JPG, PNG, or PDF), and it extracts the data from all of them in one go, instead of dragging every invoice in one by one.

Here's what it does:

The form takes one or many files. Each invoice gets read and the key fields come out structured: invoice number, date, vendor, customer, total, currency, payment status, and so on. Every result lands as a row in a Google Sheet, so your invoice data extraction ends up somewhere you can actually filter and sort.

The part I find most useful day to day is the error log. When the upload finishes, the form shows a summary listing every file with a ✅ or ❌. If an invoice was blurry, cropped, or just missing a core field like the invoice number or total, it gets flagged so you know exactly which one to re-check. No silently dropped invoices.

A couple of build notes for anyone who wants to take it apart:

It uses the easybits Extractor node for the actual extraction. The flow loops one file at a time on purpose, since the extractor bundles everything you hand it into a single request, so looping is what gives you one clean result per invoice.

Workflow JSON is here: https://github.com/felix-sattler-easybits/n8n-workflows/tree/c6cba24c56bfe20dbe1f7852b02696b4a4140ccd/easybits-batch-invoice-extractor-workflow

And the rest of my finance and invoice processing workflows are on my n8n creator page: https://n8n.io/creators/easybits/

Curious how others here handle the failures. Do you flag bad invoices for manual review like this, or route them somewhere else entirely?

Best,
Felix

reddit.com
u/easybits_ai — 17 days ago
▲ 4 r/n8nforbeginners+1 crossposts

Batch invoice processing in n8n: upload multiple invoices via a form, extract the data in one go [Workflow included]

https://preview.redd.it/aensjozlk78h1.png?width=2724&format=png&auto=webp&s=a318b20cbe3e4730f78ff053af63eb91ee66e290

👋 Hey n8n for beginners Community,

I've built a lot of finance workflows over the last few weeks and months for a couple of friends who run small businesses. Going back through my n8n library the other day, I realised I'd never actually shared the most basic one people keep asking me for: a simple batch invoice processing flow.

So I put one together. You upload multiple invoices through a single form (JPG, PNG, or PDF), and it extracts the data from all of them in one go, instead of dragging every invoice in one by one.

Here's what it does:

The form takes one or many files. Each invoice gets read and the key fields come out structured: invoice number, date, vendor, customer, total, currency, payment status, and so on. Every result lands as a row in a Google Sheet, so your invoice data extraction ends up somewhere you can actually filter and sort.

The part I find most useful day to day is the error log. When the upload finishes, the form shows a summary listing every file with a ✅ or ❌. If an invoice was blurry, cropped, or just missing a core field like the invoice number or total, it gets flagged so you know exactly which one to re-check. No silently dropped invoices.

A couple of build notes for anyone who wants to take it apart:

It uses the easybits Extractor node for the actual extraction. The flow loops one file at a time on purpose, since the extractor bundles everything you hand it into a single request, so looping is what gives you one clean result per invoice.

Workflow JSON is here: https://github.com/felix-sattler-easybits/n8n-workflows/tree/c6cba24c56bfe20dbe1f7852b02696b4a4140ccd/easybits-batch-invoice-extractor-workflow

And the rest of my finance and invoice processing workflows are on my n8n creator page: https://n8n.io/creators/easybits/

Curious how others here handle the failures. Do you flag bad invoices for manual review like this, or route them somewhere else entirely?

Best,
Felix

reddit.com
u/easybits_ai — 17 days ago
▲ 11 r/lowcode+4 crossposts

Batch invoice processing in n8n: upload multiple invoices via a form, extract the data in one go [Workflow included]

https://preview.redd.it/ehbe656gk78h1.png?width=2724&format=png&auto=webp&s=ae09d5670cca1f4228321ee13282967bb6a8b3b9

👋 Hey n8n Community,

I've built a lot of finance workflows over the last few weeks and months for a couple of friends who run small businesses. Going back through my n8n library the other day, I realised I'd never actually shared the most basic one people keep asking me for: a simple batch invoice processing flow.

So I put one together. You upload multiple invoices through a single form (JPG, PNG, or PDF), and it extracts the data from all of them in one go, instead of dragging every invoice in one by one.

Here's what it does:

The form takes one or many files. Each invoice gets read and the key fields come out structured: invoice number, date, vendor, customer, total, currency, payment status, and so on. Every result lands as a row in a Google Sheet, so your invoice data extraction ends up somewhere you can actually filter and sort.

The part I find most useful day to day is the error log. When the upload finishes, the form shows a summary listing every file with a ✅ or ❌. If an invoice was blurry, cropped, or just missing a core field like the invoice number or total, it gets flagged so you know exactly which one to re-check. No silently dropped invoices.

A couple of build notes for anyone who wants to take it apart:

It uses the easybits Extractor node for the actual extraction. The flow loops one file at a time on purpose, since the extractor bundles everything you hand it into a single request, so looping is what gives you one clean result per invoice.

Workflow JSON is here: https://github.com/felix-sattler-easybits/n8n-workflows/tree/c6cba24c56bfe20dbe1f7852b02696b4a4140ccd/easybits-batch-invoice-extractor-workflow

And the rest of my finance and invoice processing workflows are on my n8n creator page: https://n8n.io/creators/easybits/

Curious how others here handle the failures. Do you flag bad invoices for manual review like this, or route them somewhere else entirely?

Best,
Felix

reddit.com
u/easybits_ai — 17 days ago
▲ 2 r/lowcode+1 crossposts

[Challenge] Why LLMs hallucinate on grid extraction and how we parsed a handwritten scorecard in n8n

👋 Hey Vibe Coders Community,

Document extraction pipelines handle invoices and CVs beautifully. But during a game night, we fed our AI something that looked simple but completely broke it: a handwritten Kniffel (Yahtzee) scorecard.

🪤 The grid trap

The goal: Take a photo, find players with a handwritten asterisk (*) above their name, extract their scores, calculate the 35-point bonus (if top $\ge$ 63), total it up, and push to Sheets and Telegram.

Look at the attached photo:

https://preview.redd.it/v9dxgp94w18h1.jpg?width=3024&format=pjpg&auto=webp&s=7dea1a9e95edba29d7398625c1991de84085800e

It’s obvious to humans. But the LLM hallucinated names ("Ivan" became "ban"), merged columns, and botched the math.

Here is why spatial data breaks AI, and the prompt architecture we used to fix it:

👀 Breaking the "Left-to-Right" bias

LLMs read like a book (left-to-right, row-by-row). A scorecard demands the opposite. To stop "row bleeding" across columns, we forced an Array of Objects output ([{player_name, sum_top, sum_bottom}]). This forces the AI to finish reading one vertical column before moving to the next.

🧮 Deterministic math, zero LLM judgment

We already know the Extractor we are using is incredibly reliable at classifying documents and pulling structured data, so we decided to push its limits and test if it could handle calculations, too. Big mistake. LLMs are text-prediction engines, not calculators. The fix: We instructed it to only extract raw numbers, and used a pure JavaScript Code node downstream to handle the math and conditional bonus.

🛑 Explicit empty states stop hallucinations

Players wrote dashes (-) for empty boxes. Because the LLM couldn't fit a string dash into a Number schema, it panicked and outputted -1, breaking our JavaScript. Adding a strict constraint—"CRITICAL: If a cell is a dash or empty, you MUST output 0", fixed the pipeline instantly.

⚓ Anchoring the eyes with labels

To stop the AI from drifting across columns, we gave it visual coordinates. We instructed it to look at the printed labels on the far left (e.g., "Dreierpasch"), trace an invisible horizontal line to the starred column, and extract only that cell.

🐢 One honest caveat

Offloading math to JS guarantees perfect logic, but handwriting recognition still depends on photo quality. If you need flawless data from messy writing, human-in-the-loop validation is still the ceiling.

🔧 How to run it

I used the easybits Extractor because it handles JSON-schema enforcement natively without wrestling with HTTP node prompts. Both cloud and self-hosted fit in the free tier.

  • n8n Cloud: Search easybits in the nodes panel.
  • Self-hosted: Install '@easybits/n8n-nodes-extractor' from Community Nodes.

💬 Feedback welcome

I've attached the raw image. I'm highly curious: what happens when you run this exact image through your current OCR or LLM setup? Have you found a cleaner way to extract vertical columns from dense horizontal grids?

Best,
Felix

reddit.com
u/easybits_ai — 18 days ago

[Challenge] Why LLMs hallucinate on grid extraction and how we parsed a handwritten scorecard in n8n

👋 Hey n8n for beginners Community,

Document extraction pipelines handle invoices and CVs beautifully. But during a game night, we fed our AI something that looked simple but completely broke it: a handwritten Kniffel (Yahtzee) scorecard.

🪤 The grid trap

The goal: Take a photo, find players with a handwritten asterisk (*) above their name, extract their scores, calculate the 35-point bonus (if top $\ge$ 63), total it up, and push to Sheets and Telegram.

Look at the attached photo:

https://preview.redd.it/hp5a8x9dv18h1.jpg?width=3024&format=pjpg&auto=webp&s=7eff32fc35cec955c59082294b72c08c47aea86a

It’s obvious to humans. But the LLM hallucinated names ("Ivan" became "ban"), merged columns, and botched the math.

Here is why spatial data breaks AI, and the prompt architecture we used to fix it:

👀 Breaking the "Left-to-Right" bias

LLMs read like a book (left-to-right, row-by-row). A scorecard demands the opposite. To stop "row bleeding" across columns, we forced an Array of Objects output ([{player_name, sum_top, sum_bottom}]). This forces the AI to finish reading one vertical column before moving to the next.

🧮 Deterministic math, zero LLM judgment

We already know the Extractor we are using is incredibly reliable at classifying documents and pulling structured data, so we decided to push its limits and test if it could handle calculations, too. Big mistake. LLMs are text-prediction engines, not calculators. The fix: We instructed it to only extract raw numbers, and used a pure JavaScript Code node downstream to handle the math and conditional bonus.

🛑 Explicit empty states stop hallucinations

Players wrote dashes (-) for empty boxes. Because the LLM couldn't fit a string dash into a Number schema, it panicked and outputted -1, breaking our JavaScript. Adding a strict constraint—"CRITICAL: If a cell is a dash or empty, you MUST output 0", fixed the pipeline instantly.

⚓ Anchoring the eyes with labels

To stop the AI from drifting across columns, we gave it visual coordinates. We instructed it to look at the printed labels on the far left (e.g., "Dreierpasch"), trace an invisible horizontal line to the starred column, and extract only that cell.

🐢 One honest caveat

Offloading math to JS guarantees perfect logic, but handwriting recognition still depends on photo quality. If you need flawless data from messy writing, human-in-the-loop validation is still the ceiling.

🔧 How to run it

I used the easybits Extractor because it handles JSON-schema enforcement natively without wrestling with HTTP node prompts. Both cloud and self-hosted fit in the free tier.

  • n8n Cloud: Search easybits in the nodes panel.
  • Self-hosted: Install '@easybits/n8n-nodes-extractor' from Community Nodes.

💬 Feedback welcome

I've attached the raw image. I'm highly curious: what happens when you run this exact image through your current OCR or LLM setup? Have you found a cleaner way to extract vertical columns from dense horizontal grids?

Best,
Felix

reddit.com
u/easybits_ai — 18 days ago
▲ 4 r/NoCodeSaaS+3 crossposts

[Challenge] Why LLMs hallucinate on grid extraction and how we parsed a handwritten scorecard in n8n

👋 Hey r/n8n Community,

Document extraction pipelines handle invoices and CVs beautifully. But during a game night, we fed our AI something that looked simple but completely broke it: a handwritten Kniffel (Yahtzee) scorecard.

🪤 The grid trap

The goal: Take a photo, find players with a handwritten asterisk (*) above their name, extract their scores, calculate the 35-point bonus (if top $\ge$ 63), total it up, and push to Sheets and Telegram.

Look at the attached photo:

https://preview.redd.it/nrubrxehu18h1.jpg?width=3024&format=pjpg&auto=webp&s=f411e368b74d4f140630382429eae23c443c422d

It’s obvious to humans. But the LLM hallucinated names ("Ivan" became "ban"), merged columns, and botched the math.

Here is why spatial data breaks AI, and the prompt architecture we used to fix it:

👀 Breaking the "Left-to-Right" bias

LLMs read like a book (left-to-right, row-by-row). A scorecard demands the opposite. To stop "row bleeding" across columns, we forced an Array of Objects output ([{player_name, sum_top, sum_bottom}]). This forces the AI to finish reading one vertical column before moving to the next.

🧮 Deterministic math, zero LLM judgment

We already know the Extractor we are using is incredibly reliable at classifying documents and pulling structured data, so we decided to push its limits and test if it could handle calculations, too. Big mistake. LLMs are text-prediction engines, not calculators. The fix: We instructed it to only extract raw numbers, and used a pure JavaScript Code node downstream to handle the math and conditional bonus.

🛑 Explicit empty states stop hallucinations

Players wrote dashes (-) for empty boxes. Because the LLM couldn't fit a string dash into a Number schema, it panicked and outputted -1, breaking our JavaScript. Adding a strict constraint—"CRITICAL: If a cell is a dash or empty, you MUST output 0", fixed the pipeline instantly.

Anchoring the eyes with labels

To stop the AI from drifting across columns, we gave it visual coordinates. We instructed it to look at the printed labels on the far left (e.g., "Dreierpasch"), trace an invisible horizontal line to the starred column, and extract only that cell.

🐢 One honest caveat

Offloading math to JS guarantees perfect logic, but handwriting recognition still depends on photo quality. If you need flawless data from messy writing, human-in-the-loop validation is still the ceiling.

🔧 How to run it

I used the easybits Extractor because it handles JSON-schema enforcement natively without wrestling with HTTP node prompts. Both cloud and self-hosted fit in the free tier.

  • n8n Cloud: Search easybits in the nodes panel.
  • Self-hosted: Install '@easybits/n8n-nodes-extractor' from Community Nodes.

💬 Feedback welcome

I've attached the raw image. I'm highly curious: what happens when you run this exact image through your current OCR or LLM setup? Have you found a cleaner way to extract vertical columns from dense horizontal grids?

Best,
Felix

reddit.com
u/easybits_ai — 18 days ago

5 lessons from building a multi-signer document workflow in n8n

👋 Hey VibeCoders Community,

Just wrapped a build that takes incoming documents from one team, bundles them with assets from a second team, and routes the package to two sequential signers, all triggered by emails. Some learnings that might save you a few hours.

1. Order your state machine for the human, not the system

Originally I wired: collect input from team A → send for signature → ask team B for supporting docs → loop back for re-sign. Total mess. Swapped to: collect from team A → wait for team B → bundle → single signature round. Rule I'm keeping: signers should never have to context-switch back to a document they thought was done. Re-route the state machine if you have to.

2. Filesystem-mode binary breaks $binary.data.toString('base64')

If your n8n runs in filesystem storage mode (the default for self-hosted), binary.data is just a marker string, not the actual file bytes. Calling .toString('base64') returns base64 of the marker, which is ~14 bytes of garbage that downstream APIs happily accept then mangle. Use this.helpers.getBinaryDataBuffer(0, 'data') inside a Code node to load real bytes.

3. The Merge trick for cross-node binary access

After a Sheets Lookup or Update, your binary is gone from the current item. Instead of $('NodeName').item.binary.data references everywhere, branch your binary-source node into two paths: one through your normal chain, one to a Merge node (Combine by Position) downstream. The Merge stitches the binary back exactly where you need it. Way cleaner than cross-node refs.

4. Parser nodes should be lenient on attachment types, strict on validation

I started by requiring a PDF attachment. Then real-world users sent two phone photos and zero PDFs. Then a PDF plus inline-embedded images from email signatures. Then nothing at all by accident. Build your parser to classify by mime type AND filename, accept what's plausible, and only throw when there's literally nothing to process. The downstream "should I run extraction?" decision belongs in an IF node, not in the parser.

5. n8n's JSON body validator runs before expressions resolve

If your HTTP node body has a JSON expression like "documents": {{ JSON.stringify($json.documents) }}, n8n's parser flags it as invalid JSON before the workflow runs, because at validation time it sees {{ ... }} as literal text, not parsed JSON. Fix: wrap the entire body in one big expression, prefixed with =:

={{ JSON.stringify({ template_id: ..., documents: $json.documents }) }}

Single expression, evaluates at runtime, validator stays happy.

Bonus: extraction from variable-quality input

For pulling structured data out of incoming attachments (could be a clean scan, could be a handwritten form photographed on a phone), I used my own Extractor. Way less brittle than OCR + regex.

  • n8n Cloud: search easybits in the nodes panel, it's verified by n8n, so it shows up natively, no install step
  • Self-hosted: install the community node '@easybits/n8n-nodes-extractor' from Settings → Community Nodes

What's the messiest real-world input your workflow has had to handle? Curious whether others are dealing with the same "user sends whatever, parser figures it out" situation, or if you've found a clean way to enforce structure upstream.

Best,
Felix

reddit.com
u/easybits_ai — 26 days ago

5 things I wish I'd known earlier building n8n workflows

👋 Hey n8n for Beginners Community,

I've been building automations in n8n for a while now, and recently wrapped a more complex workflow involving email triggers, document handling, and signature routing. Looking back, there are a handful of lessons that would have saved me hours when I started. Sharing them here in case any of you are running into the same things.

1. Design the flow around the human, not the system

When I first sketched the workflow, I built it in the order the system needed: trigger fires, send for signature, then collect supporting data, then loop back. It worked, but the people using it were getting confused, "wait, why am I signing this again?"

I rebuilt it so everything is collected upfront and only THEN sent for signature. One round, no context-switching. Rule I keep in mind now: if your workflow makes a human revisit a step they thought was done, the order is probably wrong. Worth pausing before you wire things up and asking "what does this look like from the user's seat?"

2. Binary data isn't always where you think it is

If you're working with file attachments and you're self-hosting n8n, there's a gotcha worth knowing. In "filesystem" storage mode (the default for self-hosted), binary.data doesn't actually contain the file bytes, it's just a reference. So if you try binary.data.toString('base64') in a Code node expecting base64, you get garbage.

The fix is to use this.helpers.getBinaryDataBuffer(0, 'data') inside a Code node, which loads the real file. I lost half a day to this before figuring out why my API uploads were sending broken PDFs.

3. The Merge node is your friend for keeping binary data alive

After certain nodes (like Sheets Lookup), the binary attached to your item disappears. The instinct is to reference it from the original node by name everywhere, but that gets messy fast.

Cleaner approach: branch your original "has the file" node into two paths. One path goes through your normal chain. The other goes straight to a Merge node (Combine by Position) downstream. The Merge stitches the binary back onto the item exactly where you need it. Felt like a "why didn't I do this from the start" moment.

4. Build parsers that don't trust the input

I built a parser expecting one PDF attachment per email. Real users sent two phone photos with no PDF. Then a PDF plus inline images from their email signature. Then nothing at all because they forgot to attach the file.

Now I default to: classify every attachment by both mime type AND filename, accept anything plausible, and only throw an error when there's literally nothing to process. The "is this the right shape?" decision belongs in a downstream IF node, not in the parser. Way less brittle.

5. n8n's JSON body validator runs before expressions resolve

This one's a recent bite. If you build an HTTP node with a JSON body containing expressions, like:

"documents": {{ JSON.stringify($json.myArray) }}

n8n flags it as invalid JSON before the workflow even runs, because at validation time it sees {{ ... }} as literal text, not parsed JSON.

Fix: wrap the entire body in one big expression, with = at the start:

={{ JSON.stringify({ template_id: ..., documents: $json.myArray }) }}

Single expression, evaluates at runtime, validator stops complaining. Took me longer than I'd like to admit to figure out.

Bonus: extraction from messy attachments

For the part where I had to pull structured data out of attachments (clean scans, handwritten forms, phone photos), I used the easybits Extractor instead of writing OCR + regex myself.

  • n8n Cloud: search easybits in the nodes panel, it's verified by n8n, no install needed
  • Self-hosted: install the community node '@easybits/n8n-nodes-extractor' from Settings → Community Nodes

Curious to hear from others: what's the lesson YOU wish you'd known earlier when you started with n8n? Always looking to learn from people who've stumbled into the same traps.

Best,
Felix

reddit.com
u/easybits_ai — 26 days ago
▲ 4 r/NoCodeSaaS+2 crossposts

5 lessons from building a multi-signer document workflow in n8n

👋 Hey n8n Community,

Just wrapped a build that takes incoming documents from one team, bundles them with assets from a second team, and routes the package to two sequential signers, all triggered by emails. Some learnings that might save you a few hours.

1. Order your state machine for the human, not the system

Originally I wired: collect input from team A → send for signature → ask team B for supporting docs → loop back for re-sign. Total mess. Swapped to: collect from team A → wait for team B → bundle → single signature round. Rule I'm keeping: signers should never have to context-switch back to a document they thought was done. Re-route the state machine if you have to.

2. Filesystem-mode binary breaks $binary.data.toString('base64')

If your n8n runs in filesystem storage mode (the default for self-hosted), binary.data is just a marker string, not the actual file bytes. Calling .toString('base64') returns base64 of the marker, which is ~14 bytes of garbage that downstream APIs happily accept then mangle. Use this.helpers.getBinaryDataBuffer(0, 'data') inside a Code node to load real bytes.

3. The Merge trick for cross-node binary access

After a Sheets Lookup or Update, your binary is gone from the current item. Instead of $('NodeName').item.binary.data references everywhere, branch your binary-source node into two paths: one through your normal chain, one to a Merge node (Combine by Position) downstream. The Merge stitches the binary back exactly where you need it. Way cleaner than cross-node refs.

4. Parser nodes should be lenient on attachment types, strict on validation

I started by requiring a PDF attachment. Then real-world users sent two phone photos and zero PDFs. Then a PDF plus inline-embedded images from email signatures. Then nothing at all by accident. Build your parser to classify by mime type AND filename, accept what's plausible, and only throw when there's literally nothing to process. The downstream "should I run extraction?" decision belongs in an IF node, not in the parser.

5. n8n's JSON body validator runs before expressions resolve

If your HTTP node body has a JSON expression like "documents": {{ JSON.stringify($json.documents) }}, n8n's parser flags it as invalid JSON before the workflow runs, because at validation time it sees {{ ... }} as literal text, not parsed JSON. Fix: wrap the entire body in one big expression, prefixed with =:

={{ JSON.stringify({ template_id: ..., documents: $json.documents }) }}

Single expression, evaluates at runtime, validator stays happy.

Bonus: extraction from variable-quality input

For pulling structured data out of incoming attachments (could be a clean scan, could be a handwritten form photographed on a phone), I used my own Extractor. Way less brittle than OCR + regex.

  • n8n Cloud: search easybits in the nodes panel, it's verified by n8n, so it shows up natively, no install step
  • Self-hosted: install the community node '@easybits/n8n-nodes-extractor' from Settings → Community Nodes

What's the messiest real-world input your workflow has had to handle? Curious whether others are dealing with the same "user sends whatever, parser figures it out" situation, or if you've found a clean way to enforce structure upstream.

Best,
Felix

reddit.com
u/easybits_ai — 26 days ago

[Workflow Included] Get an email alert when any of your AI subscriptions silently raises its price – runs on Gmail + Google Sheets, free tier friendly

https://preview.redd.it/3v4syautz95h1.png?width=6016&format=png&auto=webp&s=8833737e13cf9b8882df11b4342a626b6814c35d

👋 Hey r/VibeCodersNest Community,

A while back I built a workflow for my friend Mike so he'd never pay the same invoice twice. After that one made the rounds, a colleague of mine, let's call him Tom, reached out. He started learning automations around the same time I did, so we trade notes a lot. This time he wasn't asking how to build something. He was asking if I could just build it for him.

The problem

Tom runs a content shop, so he's subscribed to maybe 10–15 AI and design tools at any time. The kind of stack a lot of us are running in 2026.

Looking at his card statement, he realized his monthly subscription costs had crept up significantly over six months. Some of it was tier upgrades he made on purpose. Most of it was providers nudging prices up a few percent at a time, small increases that hit silently, with maybe a "we're updating our pricing" email he skimmed and forgot.

His ask: "I want an email the moment a new invoice comes in that's higher than what this vendor charged me over the last months. Not three months later. On the first increase, so I can cancel before it stacks."

So I built it.

How it works

The system is two workflows that share one Google Sheet (the "ledger"):

  1. Subscription Baseline Seeder – Tom labels his last 2–3 receipts per vendor with historical invoice in Gmail. The workflow extracts vendor, amount, plan, billing period, and date from each receipt (whether the info is in the email body or in a PDF attachment) and saves them to the ledger. This builds the baseline. One-time setup per vendor.
  2. Subscription Price Drift Monitor – Going forward, Tom labels each new receipt with new invoice as it arrives. The workflow extracts the same fields, looks up the last 3 receipts for that vendor in the ledger, averages them as a rolling baseline, and compares. If the new amount is higher → email alert. If the price stayed the same or dropped → silent log. Either way, the receipt gets added to the ledger so the rolling baseline naturally shifts forward over time.

A few technical bits I think are worth flagging:

📄 Email body OR attachment, with priority logic. Most AI tool receipts come as HTML emails (Stripe-hosted, Paddle, vendor-direct). Some include a PDF attachment, some don't. The workflow renders the email body to a PDF with a small Code node (pure JS, no external service), runs extraction on it, and if a PDF attachment is also present, runs a second extraction on that too. Then merges the two with body priority, falling back to attachment values only when the body returned null for a given field. This makes it robust across very different vendor email templates.

🧮 Deterministic comparison, no LLM judgment. The Extractor extracts. JavaScript decides what's flagged. The alert email just narrates what JS already detected. The LLM never decides whether the price went up, that decision lives in deterministic math against the rolling baseline. Way more reliable than letting an LLM eyeball two amounts.

📊 Rolling baseline that self-heals. If a vendor has 1 prior receipt → compare against that. 2 → average of 2. 3+ → average of the last 3. So Tom gets useful signal from receipt #2 onwards, and the baseline shifts forward naturally as new receipts come in. No manual baseline updates.

🚨 Alerts only on increases. Decreases are logged silently. The whole point is catching creeping cost, Tom doesn't need an email every month from every vendor.

Both workflow JSONs + the setup guide are here:

https://github.com/felix-sattler-easybits/n8n-workflows/tree/2e2dd67e6799ce8886beac8c386104f954f0a1d8/easybits-subscription-monitor-workflow

Import them into n8n, follow the sticky notes for setup (you'll need a Gmail label per workflow, one Google Sheet, and an easybits pipeline, all spelled out step by step on the canvas).

I used the easybits Extractor for the document parsing here. Both workflows fit comfortably in the free plan.

If anyone else is running a heavy AI tool stack and quietly bleeding money to price creep, give it a spin and let me know how it lands. Curious if there's a vendor whose receipts are weird enough to break the extraction, would love to harden it.

Best,
Felix

reddit.com
u/easybits_ai — 1 month ago
▲ 2 r/lowcode+1 crossposts

[Workflow Included] Get an email alert when any of your AI subscriptions silently raises its price – runs on Gmail + Google Sheets, free tier friendly

https://preview.redd.it/18pz90jez95h1.png?width=6016&format=png&auto=webp&s=8e162b752e1c10def3f7dd9d13cf8c1934da1569

👋 Hey r/NoCodeSaaS Community,

A while back I built a workflow for my friend Mike so he'd never pay the same invoice twice. After that one made the rounds, a colleague of mine, let's call him Tom, reached out. He started learning automations around the same time I did, so we trade notes a lot. This time he wasn't asking how to build something. He was asking if I could just build it for him.

The problem

Tom runs a content shop, so he's subscribed to maybe 10–15 AI and design tools at any time. The kind of stack a lot of us are running in 2026.

Looking at his card statement, he realized his monthly subscription costs had crept up significantly over six months. Some of it was tier upgrades he made on purpose. Most of it was providers nudging prices up a few percent at a time, small increases that hit silently, with maybe a "we're updating our pricing" email he skimmed and forgot.

His ask: "I want an email the moment a new invoice comes in that's higher than what this vendor charged me over the last months. Not three months later. On the first increase, so I can cancel before it stacks."

So I built it.

How it works

The system is two workflows that share one Google Sheet (the "ledger"):

  1. Subscription Baseline Seeder – Tom labels his last 2–3 receipts per vendor with historical invoice in Gmail. The workflow extracts vendor, amount, plan, billing period, and date from each receipt (whether the info is in the email body or in a PDF attachment) and saves them to the ledger. This builds the baseline. One-time setup per vendor.
  2. Subscription Price Drift Monitor – Going forward, Tom labels each new receipt with new invoice as it arrives. The workflow extracts the same fields, looks up the last 3 receipts for that vendor in the ledger, averages them as a rolling baseline, and compares. If the new amount is higher → email alert. If the price stayed the same or dropped → silent log. Either way, the receipt gets added to the ledger so the rolling baseline naturally shifts forward over time.

A few technical bits I think are worth flagging:

📄 Email body OR attachment, with priority logic. Most AI tool receipts come as HTML emails (Stripe-hosted, Paddle, vendor-direct). Some include a PDF attachment, some don't. The workflow renders the email body to a PDF with a small Code node (pure JS, no external service), runs extraction on it, and if a PDF attachment is also present, runs a second extraction on that too. Then merges the two with body priority, falling back to attachment values only when the body returned null for a given field. This makes it robust across very different vendor email templates.

🧮 Deterministic comparison, no LLM judgment. The Extractor extracts. JavaScript decides what's flagged. The alert email just narrates what JS already detected. The LLM never decides whether the price went up, that decision lives in deterministic math against the rolling baseline. Way more reliable than letting an LLM eyeball two amounts.

📊 Rolling baseline that self-heals. If a vendor has 1 prior receipt → compare against that. 2 → average of 2. 3+ → average of the last 3. So Tom gets useful signal from receipt #2 onwards, and the baseline shifts forward naturally as new receipts come in. No manual baseline updates.

🚨 Alerts only on increases. Decreases are logged silently. The whole point is catching creeping cost, Tom doesn't need an email every month from every vendor.

Both workflow JSONs + the setup guide are here:

https://github.com/felix-sattler-easybits/n8n-workflows/tree/2e2dd67e6799ce8886beac8c386104f954f0a1d8/easybits-subscription-monitor-workflow

Import them into n8n, follow the sticky notes for setup (you'll need a Gmail label per workflow, one Google Sheet, and an easybits pipeline, all spelled out step by step on the canvas).

I used the easybits Extractor for the document parsing here. Both workflows fit comfortably in the free plan.

If anyone else is running a heavy AI tool stack and quietly bleeding money to price creep, give it a spin and let me know how it lands. Curious if there's a vendor whose receipts are weird enough to break the extraction, would love to harden it.

Best,
Felix

reddit.com
u/easybits_ai — 1 month ago

[Workflow Included] Get an email alert when any of your AI subscriptions silently raises its price – runs on Gmail + Google Sheets, free tier friendly

https://preview.redd.it/x60knvx4z95h1.png?width=6016&format=png&auto=webp&s=daeeb901f6bebdbc10b1375b85921556e0d39e41

👋 Hey r/n8nforbeginners Community,

A while back I built a workflow for my friend Mike so he'd never pay the same invoice twice. After that one made the rounds, a colleague of mine, let's call him Tom, reached out. He started learning automations around the same time I did, so we trade notes a lot. This time he wasn't asking how to build something. He was asking if I could just build it for him.

The problem

Tom runs a content shop, so he's subscribed to maybe 10–15 AI and design tools at any time. The kind of stack a lot of us are running in 2026.

Looking at his card statement, he realized his monthly subscription costs had crept up significantly over six months. Some of it was tier upgrades he made on purpose. Most of it was providers nudging prices up a few percent at a time, small increases that hit silently, with maybe a "we're updating our pricing" email he skimmed and forgot.

His ask: "I want an email the moment a new invoice comes in that's higher than what this vendor charged me over the last months. Not three months later. On the first increase, so I can cancel before it stacks."

So I built it.

How it works

The system is two workflows that share one Google Sheet (the "ledger"):

  1. Subscription Baseline Seeder – Tom labels his last 2–3 receipts per vendor with historical invoice in Gmail. The workflow extracts vendor, amount, plan, billing period, and date from each receipt (whether the info is in the email body or in a PDF attachment) and saves them to the ledger. This builds the baseline. One-time setup per vendor.
  2. Subscription Price Drift Monitor – Going forward, Tom labels each new receipt with new invoice as it arrives. The workflow extracts the same fields, looks up the last 3 receipts for that vendor in the ledger, averages them as a rolling baseline, and compares. If the new amount is higher → email alert. If the price stayed the same or dropped → silent log. Either way, the receipt gets added to the ledger so the rolling baseline naturally shifts forward over time.

A few technical bits I think are worth flagging:

📄 Email body OR attachment, with priority logic. Most AI tool receipts come as HTML emails (Stripe-hosted, Paddle, vendor-direct). Some include a PDF attachment, some don't. The workflow renders the email body to a PDF with a small Code node (pure JS, no external service), runs extraction on it, and if a PDF attachment is also present, runs a second extraction on that too. Then merges the two with body priority, falling back to attachment values only when the body returned null for a given field. This makes it robust across very different vendor email templates.

🧮 Deterministic comparison, no LLM judgment. The Extractor extracts. JavaScript decides what's flagged. The alert email just narrates what JS already detected. The LLM never decides whether the price went up, that decision lives in deterministic math against the rolling baseline. Way more reliable than letting an LLM eyeball two amounts.

📊 Rolling baseline that self-heals. If a vendor has 1 prior receipt → compare against that. 2 → average of 2. 3+ → average of the last 3. So Tom gets useful signal from receipt #2 onwards, and the baseline shifts forward naturally as new receipts come in. No manual baseline updates.

🚨 Alerts only on increases. Decreases are logged silently. The whole point is catching creeping cost, Tom doesn't need an email every month from every vendor.

Both workflow JSONs + the setup guide are here:

https://github.com/felix-sattler-easybits/n8n-workflows/tree/2e2dd67e6799ce8886beac8c386104f954f0a1d8/easybits-subscription-monitor-workflow

Import them into n8n, follow the sticky notes for setup (you'll need a Gmail label per workflow, one Google Sheet, and an easybits pipeline, all spelled out step by step on the canvas).

I used the easybits Extractor for the document parsing here. Both workflows fit comfortably in the free plan.

If anyone else is running a heavy AI tool stack and quietly bleeding money to price creep, give it a spin and let me know how it lands. Curious if there's a vendor whose receipts are weird enough to break the extraction, would love to harden it.

Best,
Felix

reddit.com
u/easybits_ai — 1 month ago
▲ 4 r/nocode+2 crossposts

[Workflow Included] Get an email alert when any of your AI subscriptions silently raises its price – runs on Gmail + Google Sheets, free tier friendly

https://preview.redd.it/ybwjd3q0y95h1.png?width=6016&format=png&auto=webp&s=177730f88194266c99c4376699ffe3436a78784e

👋 Hey r/n8n Community,

A while back I built a workflow for my friend Mike so he'd never pay the same invoice twice. After that one made the rounds, a colleague of mine, let's call him Tom, reached out. He started learning automations around the same time I did, so we trade notes a lot. This time he wasn't asking how to build something. He was asking if I could just build it for him.

The problem

Tom runs a content shop, so he's subscribed to maybe 10–15 AI and design tools at any time. The kind of stack a lot of us are running in 2026.

Looking at his card statement, he realized his monthly subscription costs had crept up significantly over six months. Some of it was tier upgrades he made on purpose. Most of it was providers nudging prices up a few percent at a time, small increases that hit silently, with maybe a "we're updating our pricing" email he skimmed and forgot.

His ask: "I want an email the moment a new invoice comes in that's higher than what this vendor charged me over the last months. Not three months later. On the first increase, so I can cancel before it stacks."

So I built it.

How it works

The system is two workflows that share one Google Sheet (the "ledger"):

  1. Subscription Baseline Seeder – Tom labels his last 2–3 receipts per vendor with historical invoice in Gmail. The workflow extracts vendor, amount, plan, billing period, and date from each receipt (whether the info is in the email body or in a PDF attachment) and saves them to the ledger. This builds the baseline. One-time setup per vendor.
  2. Subscription Price Drift Monitor – Going forward, Tom labels each new receipt with new invoice as it arrives. The workflow extracts the same fields, looks up the last 3 receipts for that vendor in the ledger, averages them as a rolling baseline, and compares. If the new amount is higher → email alert. If the price stayed the same or dropped → silent log. Either way, the receipt gets added to the ledger so the rolling baseline naturally shifts forward over time.

A few technical bits I think are worth flagging:

📄 Email body OR attachment, with priority logic. Most AI tool receipts come as HTML emails (Stripe-hosted, Paddle, vendor-direct). Some include a PDF attachment, some don't. The workflow renders the email body to a PDF with a small Code node (pure JS, no external service), runs extraction on it, and if a PDF attachment is also present, runs a second extraction on that too. Then merges the two with body priority, falling back to attachment values only when the body returned null for a given field. This makes it robust across very different vendor email templates.

🧮 Deterministic comparison, no LLM judgment. The Extractor extracts. JavaScript decides what's flagged. The alert email just narrates what JS already detected. The LLM never decides whether the price went up, that decision lives in deterministic math against the rolling baseline. Way more reliable than letting an LLM eyeball two amounts.

📊 Rolling baseline that self-heals. If a vendor has 1 prior receipt → compare against that. 2 → average of 2. 3+ → average of the last 3. So Tom gets useful signal from receipt #2 onwards, and the baseline shifts forward naturally as new receipts come in. No manual baseline updates.

🚨 Alerts only on increases. Decreases are logged silently. The whole point is catching creeping cost, Tom doesn't need an email every month from every vendor.

Both workflow JSONs + the setup guide are here:

https://github.com/felix-sattler-easybits/n8n-workflows/tree/2e2dd67e6799ce8886beac8c386104f954f0a1d8/easybits-subscription-monitor-workflow

Import them into n8n, follow the sticky notes for setup (you'll need a Gmail label per workflow, one Google Sheet, and an easybits pipeline, all spelled out step by step on the canvas).

I used the easybits Extractor for the document parsing here. Both workflows fit comfortably in the free plan.

If anyone else is running a heavy AI tool stack and quietly bleeding money to price creep, give it a spin and let me know how it lands. Curious if there's a vendor whose receipts are weird enough to break the extraction, would love to harden it.

Best,
Felix

reddit.com
u/easybits_ai — 1 month ago