# How we process vendor invoices the moment they arrive

Every 15 minutes our payables agent pulls new invoices from Gmail, reads them, checks them against POs and the ledger in Google Sheets, and sends them to Slack for approval. It never pays anything.

Canonical page: https://themachine.myapps.ai/use-cases/ap-invoice-processing

Invoices show up in the inbox whenever vendors feel like sending them, and every
one asks for the same routine. Open the PDF. Note who sent it, how much, and what
the lines are. Find the purchase order. Make sure it isn't a copy of something
you've already logged. Confirm the price matches the deal. That's about five
minutes of close reading per invoice, and those five minutes are exactly where
double bills and inflated prices sneak past. Not because anyone is sloppy, but
because no one can hold every past invoice and every open PO in their head on a
busy afternoon.

So an agent on Machine does it for us. Every 15 minutes it looks in the inbox,
reads each new invoice, checks it against our POs and the payables ledger, and
posts it in Slack for approval, with a flag if it's a duplicate, an overcharge or
has no PO. It keeps the books. It doesn't pay.

- **Team:** Machine
- **Runs on:** A timer that fires every 15 minutes
- **Connected systems:** Gmail · Google Sheets · Slack
- **Mode:** Logs and sends for sign-off, never schedules or makes a payment

## The problem

On its own, an invoice tells you nothing beyond a total. You only know if it's
*correct* by holding it up against records kept somewhere else: the PO that
approved the spend, the agreed price, and every earlier invoice from the same
vendor in the ledger. Someone working through a stack by hand covers the obvious
questions (does a PO exist, does the total look sane). The checks that really
catch trouble, such as a vendor sending last month's bill again or nudging a
line item up a few percent, are the first to go when the inbox is full and the
day is nearly over.

Popular shortcuts don't solve it. Paying whatever comes in catches nothing at
all. Reviewing in a monthly batch does find duplicates and overcharges, but only
later, and sometimes after the money has left. Neither tests each invoice against
all the POs and all the past invoices the minute it lands.

## What we built

A timer on Machine fires every 15 minutes, and each time it opens a fresh
**session**: a new cloud computer (a sandbox) with no memory of the previous run.
That's on purpose, because the payables ledger in Google Sheets *is* the record.
The agent checks a Gmail label for new invoice emails and pulls the vendor,
amount and line items out of each attachment. It compares them with the known POs
and earlier invoices tracked in the sheet, flags any duplicate, overcharge or
missing PO, and writes every invoice into the ledger. Then the batch goes to
Slack, where a person approves it. Scheduling a payment or marking anything as
paid is never part of the agent's job.

## How it works

### Start clean every 15 minutes

A **cron trigger** (a timer) wakes the agent every 15 minutes, and each wake-up
creates a brand-new **session** in its own sandbox. The agent keeps nothing in
its own head between runs. The Google Sheets ledger provides the continuity
instead: each run reads it first, so duplicates and overcharges are always judged
against one shared record.

### Hand it your payables rulebook

The way we read, match and flag invoices is written down as a **skill**, a
playbook loaded into each session. It defines what a duplicate is, how far a line
item may stray from the agreed price before it's an overcharge, and what to do
when there's no PO to match. So every invoice is held to one standard, rather
than whatever seems sensible that day.

### Connect the inbox, the ledger and Slack

The agent uses **connectors**: scoped connections to your tools, with keys held
on the server so the model never handles a raw token. Through them it can:

- **Read Gmail**: fresh invoice emails, attachments included, in the label it
watches.
- **Read the Google Sheet and add rows to it**: the POs and ledger it checks
against, plus a new row for each invoice it logs.
- **Post in Slack**: the processed batch, clean and flagged alike, ready for
sign-off.

### Keep money movement with people

The agent writes exactly two things: new rows in the payables ledger and a Slack
message. It can't reach any payment system, and nothing in its instructions lets
it schedule an invoice or mark one paid. A person always does that, even when the
match is perfect. Credentials are stored encrypted in the **Secrets Manager** and
only handed to the agents you grant them to.

### Approve the batch in Slack

The result: every 15 minutes, new invoices are already read, matched and logged
before anyone opens Slack. Each is labelled clean, duplicate, overcharge or
missing PO, with links to the PO and any earlier invoice it relates to. Someone
reviews the batch and approves payment through your normal payables process. The
agent's work stops at the Slack message.

> **The pattern**
> A **cron** every 15 minutes opens a fresh session, and the Google Sheet ledger
> acts as its memory. Through **connectors** the agent reads Gmail, matches each
> invoice against POs and earlier invoices, logs everything in the sheet and
> posts the batch to Slack. A person, never the agent, schedules a payment or
> marks one paid.

## Guardrails

Payables is where a slip costs actual money, so the agent's reach is kept small:

- **Isolation.** Each run gets its own sandbox. The session can reach only Gmail,
  the payables sheet and Slack, and nothing else leaves it.
- **Scoped secrets.** The Gmail, Sheets and Slack credentials are stored
  encrypted in the Secrets Manager, delivered into the sandbox when the run
  starts, and only handed to the agents you grant them to.
- **No payment action, ever.** There's no connector, tool or instruction that
  lets the agent schedule a payment or mark an invoice paid. Clean or flagged,
  every invoice goes to a person for that call.
- **Flag, don't discard.** Duplicates, overcharges and invoices without a PO get
  logged and flagged in the ledger. None are quietly dropped, and none are
  approved automatically.
- **Everything is code.** The matching rules, the skill and the permissions for
  each system are files in your repo. They're versioned, and you change them
  through a reviewed **change request**, not a dashboard toggle.

## The outcome

- **Every 15 min:** Fresh invoices read, matched and logged
- **0 payments:** Ever scheduled or marked paid by the agent
- **3 checks:** Duplicate, overcharge and missing PO, on each invoice

The five minutes of careful reading each invoice used to wait for now happen on
their own, four times an hour, against the full ledger history instead of
someone's memory. Invoices that once reached the payment step without a proper
check now arrive flagged, with the clashing PO or earlier invoice already linked.
And the decision to pay hasn't moved an inch: a person still makes it.
