Vendors send invoices via email. Your accounting / ERP system wants structured data. The bridge between the two is usually a sad combination of a forwarded email, a person, and copy-paste.

This guide shows you how to replace that bridge with a webhook that POSTs the invoice details as JSON directly into your accounting API. The approach uses MailToAPI — sign up and you can follow along in real time.

Upfront: text-body invoices vs PDF invoices

MailToAPI v1 extracts from the email body, not from PDF attachments. If your invoices live entirely inside a PDF — like most enterprise vendor invoices — you need a PDF-parsing tool (Parseur, Klippa, or a custom OCR pipeline). If your invoice details are written in the email body (very common for SaaS subscription receipts, freelancer invoices, marketplace fees, payment processor receipts) — keep reading, this works.

Good fit for this guide:

  • SaaS receipts (Stripe, Notion, Linear, Vercel, Railway, etc. — invoice data is in the email body)
  • Freelancer / agency invoices (short body text with amount + due date)
  • Marketplace fee summaries (Etsy, eBay, App Store) — fee breakdown in the email
  • Payment processor weekly summaries

Bad fit (use a PDF-parsing tool):

  • Vendor invoices that are just "Please find your invoice attached" + a PDF
  • Itemized line-item tables that only exist in the PDF

Step 1 — Define the JSON shape you want

Think about what your accounting API actually consumes. A reasonable minimum for an invoice:

invoice_number    string   required
vendor            string   required
amount            number   required
currency          string   required
issue_date        date     optional
due_date          date     optional
description       string   optional

You can add more (line items, tax, billing period) but start lean. The LLM extracts more reliably when it has fewer, well-defined fields.

Step 2 — Create the inbox

In MailToAPI, create an inbox called e.g. "Vendor invoices" with the fields above. Set the destination URL to your accounting API endpoint (e.g. https://api.youraccounting.com/invoices) and any auth headers (Authorization: Bearer ...).

You'll get a forwarding address like [email protected].

Step 3 — Forward a real invoice

Forward a recent invoice email from one of your vendors to the address above. Within 10–30 seconds, you'll see a run in MailToAPI with:

  • The extracted JSON
  • The POST attempt to your destination URL
  • Status: delivered (your API returned 2xx) or delivery_failed (something else)

If the extracted JSON looks wrong — e.g. the LLM misread a number or invented a date — you can edit the JSON manually and replay. Use this to refine your schema (add a description hint to fields the LLM gets wrong, drop fields that aren't reliably present).

Step 4 — Route real vendor invoices to that address

Two ways to do this:

Option A: Gmail filter (easiest)

In Gmail, create a filter like:

from:(stripe.com OR linear.app OR notion.so OR vercel.com)
subject:(invoice OR receipt)

Action: "Forward it to" → your MailToAPI address. See the Gmail forwarding guide for the verification flow.

Option B: Update the vendor's billing email

Most vendors let you change which email gets the invoice. Update the billing contact in each vendor's dashboard to [email protected] directly (or a forwarding alias on your domain that points there).

This is cleaner long-term — no Gmail filter to maintain — but takes a few minutes per vendor to update.

Step 5 — Receive the JSON on your accounting side

Your accounting API endpoint receives a JSON body like:

{
  "invoice_number": "INV-3041",
  "vendor": "Acme Vendor Co",
  "amount": 1240.00,
  "currency": "USD",
  "issue_date": "2026-05-15",
  "due_date": "2026-06-14",
  "description": "Services rendered May 2026"
}

A typical Hono handler:

import { Hono } from 'hono';

const app = new Hono();

app.post('/invoices', async (c) => {
  const inv = await c.req.json();
  // persist, notify Slack, queue a payment, etc.
  return c.json({ ok: true, id: inv.invoice_number });
});

export default app;

Step 6 — Quality control

For the first few weeks, monitor a few things:

  • Validation failures — runs where the LLM couldn't extract a required field. Usually means you need to soften required on fields not all vendors include, or refine the field description.
  • Numeric extraction — currencies and decimal separators (1.000,50 vs 1,000.50) can trip up extraction. If you see issues, add a description like "amount in decimal, US format (1234.56)".
  • Dates — many vendors write "Due in 30 days" instead of a specific date. The LLM can't always resolve relative dates. Either accept that due_date will sometimes be missing or extract a due_in_days integer field instead.

What to do with the extracted data

  • QuickBooks / Xero / Wave — most expose an Invoices API. Wrap your destination endpoint to translate the JSON shape and POST upstream.
  • Postgres / your own DB — straightforward insert; add a received_at timestamp.
  • Notion / Airtable — both have row-creation APIs. Map JSON keys to columns.
  • Slack / email — flag invoices over a threshold for human review.

That's the loop. The hardest 20% is tuning your schema for your specific mix of vendors — the rest is plumbing.