Available for automation projects

AP Autopilot

Turns scanned and handwritten supplier statements into clean spreadsheet rows automatically, with a nightly Telegram digest of exactly who you owe.

n8n OpenRouter Gmail Google Sheets
Supplier statements and paperwork laid out with a calculator

The problem

A multi-branch hardware business gets supplier statements by email: PDFs showing what each branch owes, aged into current/30/60/90/120-plus day buckets. Half of them are scanned images with no selectable text, layouts differ supplier to supplier, and someone has to read every one by hand to know what's actually owed and to whom. Miss one, and a payment slips through, or gets paid twice.

The approach

Two coordinated n8n workflows, one event-driven and one scheduled.

  1. Ingest and extract watches Gmail, classifies every incoming email as statement, order, debit, inquiry, or other.
  2. For statements, it runs vision AI to read the PDF, even scanned or handwritten ones, into structured, validated rows in Google Sheets.
  3. Every night, the digest workflow aggregates the sheet and sends a short Telegram message: total owed per branch, top creditors, and how many statements couldn't be parsed automatically.

Architecture

flowchart LR
    G[Gmail: New Email] --> CL{Classify}
    CL -- Statement --> SP[Split Attachments]
    CL -- Order / Debit / Inquiry --> LB[Apply Label Only]
    CL -- Other --> DROP[No Action]
    SP --> ZIP{ZIP File?}
    ZIP -- yes --> UNZIP[Decompress]
    ZIP -- no --> B64[Prepare for Vision AI]
    UNZIP --> B64
    B64 --> V1[Vision Extract - Fast Model]
    V1 --> CONF{Confidence Acceptable?}
    CONF -- yes --> SHEET[(Upsert Google Sheet)]
    CONF -- no --> V2[Vision Extract - Stronger Model]
    V2 --> SHEET
    SHEET --> AGG[Nightly Aggregate]
    AGG --> TG[Telegram: Owed by Branch]
                    

Engineering decisions

Cost-aware two-tier extraction

A cheap, fast vision model reads every statement first. Only the ones it's genuinely unsure about, low confidence or an unmatched branch, escalate to a stronger, pricier model.

Idempotent by design

Every row upserts on branch, supplier, and statement date. A re-sent statement updates the existing row instead of creating a duplicate, so totals are never inflated by resend noise.

Reconciliation, not blind trust

If the aging buckets don't sum to the stated total, the row is flagged with a confidence penalty and a note, not silently accepted.

Nothing vanishes

A failed extraction, a ZIP with no usable files, an unmatched branch: every one of these still writes a row marked Needs Manual, a task on a list, never a silently lost statement.

Orchestration

n8n

AI / Vision

OpenRouter

Gemini 2.5 Flash + Pro, Claude Haiku

Email

Gmail API

Data & alerts

Google Sheets Telegram Bot API

Want something like this built for your business?