Invoice approval workflow routing a PDF through approvers before posting to QuickBooks

Multi-Step Invoice Approval Workflow from Email to QuickBooks

Stanislav Kapustin May 1, 2026 case study · automation · n8n · quickbooks · invoice approval · accounts payable

Case summary

Quick scan before the full breakdown.

Goal

Automate the path from incoming supplier email to approved QuickBooks bill across three company entities.

Stack

n8n, Claude API, Gotenberg, QuickBooks Online, Gmail, Slack, Google Drive

Result

91% of invoices routed and approved without manual data entry, with zero duplicate payments in 10 weeks.

Time saved

Saved the finance manager approximately 12-15 hours per month.

A complete accounts payable automation for a Canadian real estate company: supplier invoices arrive by email, get extracted and routed to the correct approver based on amount and project, and land in QuickBooks Online as a verified bill — with reminders, escalation logic, and a full audit trail, entirely without manual re-entry.


The Problem

A mid-sized Canadian real estate operation managed three companies under one umbrella, each with its own QuickBooks Online account. Supplier invoices arrived by email — contractors, property managers, utilities, maintenance vendors — at a rate of roughly 150 per month across the three entities.

The process had five manual steps. Someone received the email, downloaded the attachment, typed the invoice details into a tracking spreadsheet, forwarded it to the relevant project manager for approval, and waited. Once approval came back (by reply email), someone else entered the invoice into QuickBooks and filed the PDF. Twice a month, a payment schedule was prepared manually from the spreadsheet.

Two things broke regularly: approvers forgot to reply, so invoices sat in limbo for weeks. And data entry errors between the spreadsheet and QuickBooks caused duplicate payments and incorrect expense coding.

The brief: automate everything between “email arrives” and “approved bill in QuickBooks.”


What I Built

Four n8n workflows connected by internal webhooks.


Workflow 1 — Ingestion and Extraction

Triggered by a Gmail label. A filter rule on the shared AP inbox labels any email with a PDF or image attachment as invoice-inbox. n8n polls this label every 5 minutes.

For each new email:

  1. Download the attachment
  2. Convert PDF to image via Gotenberg
  3. Send to Claude API for extraction — same structured prompt as my other invoice projects, extended with two additional fields: project_code (from the invoice body if present) and entity (best guess at which of the three companies this invoice belongs to, based on the recipient email address)
  4. Look up the vendor in a Google Sheet master list — maps vendor name to default expense account, QuickBooks vendor ID, and typical approver
  5. If confidence < 0.85, send to a Slack #ap-review channel for human verification before routing
  6. If confidence ≥ 0.85, pass the extracted data to Workflow 2

Workflow 2 — Approval Routing

Determines who needs to approve the invoice and sends the request.

Routing rules (configured in a Google Sheet, editable by the finance manager without touching n8n):

AmountApprover
Under $500Auto-approved, goes straight to Workflow 4
$500–$5,000Project manager for the relevant property
Over $5,000Project manager + CFO (both must approve)
Any contractor invoiceProject manager regardless of amount

The approval request is sent as a Slack message to the approver with:

  • Vendor name, invoice number, date, amount
  • Expense category (pre-filled from the vendor lookup)
  • Project code
  • A link to the original PDF in Google Drive
  • Two buttons: Approve and Reject / Needs Changes

If no response within 48 hours, the workflow sends a reminder. If no response after 72 hours, it escalates to the CFO and posts in #ap-review.


Workflow 3 — Rejection Handling

If the approver clicks Reject:

  • A Slack thread opens asking for a reason
  • The reason is logged against the invoice in the tracking sheet
  • The original sender is notified by email with the rejection reason
  • The invoice is moved to a rejected folder in Google Drive

Workflow 4 — QuickBooks Entry

Triggered when an approval is confirmed (either auto-approved or button click from Workflow 2).

Steps:

  1. Look up or create the vendor in QuickBooks Online via the Vendor API. Matching is done on vendor name + email to avoid duplicates
  2. POST a new Bill to /v3/company/{realmId}/bill with:
    • VendorRef (QBO vendor ID)
    • TxnDate (invoice date from extraction)
    • DueDate (calculated from invoice payment terms, defaulting to 30 days)
    • Line array with amount, account code, and description
    • DocNumber (invoice number)
  3. Upload the PDF as an attachment to the bill via QBO’s /v3/company/{realmId}/upload endpoint
  4. Write the QBO bill ID back to the tracking sheet
  5. Send a confirmation Slack message to #ap-review: “Invoice [number] from [vendor] — $[amount] — added to QuickBooks ✓“

The QBO OAuth2 Complexity

QuickBooks Online uses OAuth2 with a 1-hour access token and a 100-day refresh token. Three separate QBO companies meant three separate OAuth2 connections in n8n, each with its own realmId.

The workflow determines which company to use from the entity field extracted in Workflow 1 (mapped to a QBO credential name via a lookup). This part required careful testing — routing an invoice to the wrong QBO company is exactly the kind of error this system was meant to eliminate.

To handle token expiry, the same reminder approach as my other projects: a daily check on token expiry dates, Slack alert 14 days before.


The Audit Trail

Every invoice gets a row in a Google Sheet with:

  • Received timestamp
  • Vendor, amount, invoice number, invoice date
  • Extraction confidence score
  • Assigned approver
  • Approval timestamp (or rejection reason)
  • QBO bill ID
  • Google Drive PDF link

This sheet is the finance manager’s source of truth. It also feeds the twice-monthly payment schedule — a separate n8n workflow filters for bills due within the next 15 days and generates a formatted summary email to the banking team.


Results

After 10 weeks across all three companies (approx. 150 invoices/month):

  • 91% of invoices routed and approved without manual data entry
  • Average approval time: 6.2 hours (previously 3–5 days due to email chains getting buried)
  • Zero duplicate payments in 10 weeks — previously averaging 2–3 per month
  • Finance manager time saved: approximately 12–15 hours per month
  • Escalation fired 11 times across 10 weeks — in each case, an invoice that would previously have sat in limbo indefinitely was resolved within 24 hours

The twice-monthly payment schedule is now generated automatically and reviewed, not built from scratch.


What I’d Do Differently

The approval Slack buttons have a 3-second delay before the workflow responds — Slack shows a “loading” spinner during this time. For the multi-approval path (project manager + CFO), I’d move to a lightweight internal web form instead of Slack buttons, which would allow both approvers to see each other’s status in real time.

The entity detection from the recipient email works but would break if the client ever changes their email routing. A more robust approach is a sender domain lookup — mapping vendor email domains to the correct company entity.


Stack

  • n8n (self-hosted)
  • Claude API — invoice data extraction
  • Gotenberg — PDF to image conversion
  • QuickBooks Online REST API — bill creation, vendor management, PDF attachment
  • Gmail API — invoice ingestion via label polling
  • Slack — approval routing, reminders, escalation
  • Google Drive — PDF storage and archive
  • Google Sheets — vendor master list, routing rules, audit log, payment schedule

Running a multi-entity accounts payable process that still relies on email chains and manual QuickBooks entry? Get in touch.

More cases

Three nearby case studies worth reading next.

Need a similar system in your business?

If you have a manual workflow between tools, I can help map the logic, design the system, and automate it in a way your team can actually use.

svg