How a bill gets approved for payment
A bill lands in Dan’s inbox at 8:03am. Packaging Co, $4,200, flagged price variance. There are three buttons: Approve, Query, Reject. What happens when he taps one? The honest answer is “it depends which one.” This post walks through the three things an approver can do — approve, query, reject — and how the bill, the supplier, and the audit trail all stay in sync. The one thing none of them does is pay the bill: that’s always your normal payment run, after a human has said yes.
Key takeaways
- Three actions per bill: approve (clear for the payment run), query (ask the supplier), reject (decline).
- Approving a flagged bill requires a reason, captured in the audit trail.
- Query sends a templated note to the supplier; a reissued bill re-enters the matcher fresh.
- Approve only marks the bill ready — payment is still your normal run, never the matcher.
- The Approve button is an email action backed by a Function URL.
Three actions on a bill
Action 1: approve (the most common)
Most bills are clean, so most of the time Approve is a single tap. On a matched bill, the approver glances at the supplier and total, taps Approve, and they’re done. On a flagged bill, Approve is slightly heavier on purpose: a small form opens asking for a reason for overriding the flag — “agreed the higher price with the supplier by phone” or “short delivery, but we’ll accept and they’ll credit next month.” The reason is required, because an override with no explanation is exactly the thing an auditor (or a future you) will want to understand later.
The button submits to the ack-handler Function URL. Three things happen, in order. First, the bill is marked ready_for_payment in the bm-bills table and the matched purchase-order line is closed so it can’t be billed against twice. Second, if the bill needed two sign-offs (Part 4), the bill stays in a half-approved state until the second approver also taps Approve. Third, an action: approved row is written to bm-audit with the user, timestamp, outcome, and any override reason.
Marking a bill ready_for_payment does not pay it. The matcher hands a clean list of approved bills to your normal payment process — an export to your accounting tool, or a file for the bank run — and a person runs that process. The matcher never touches money. That boundary is the whole point: the system removes the tedious checking, not the human judgment about whether to actually pay.
Action 2: query (the “ask the supplier”)
When a bill is flagged and the approver doesn’t want to either approve or reject it — they want the supplier to explain or fix it — they tap Query. A small form opens with a templated message tailored to the outcome: for a price variance, “Your bill charges $4.20/unit; our PO agreed $3.80/unit. Please confirm the price or reissue.” For a quantity variance, “Your bill is for 100 units; our dock received 84. Please reissue for the received quantity or advise on the balance.”
On send, the ack-handler emails the supplier via SES outbound and parks the bill in a waiting_on_supplier state. The bill stops chasing the approver — the ball is in the supplier’s court. If the supplier reissues, the corrected bill comes back in through the normal intake lanes, gets read fresh, and is matched again from scratch. A corrected bill that now matches clean clears for a one-tap approval, and the loop closes. The original queried bill is linked to the reissue in the audit trail so the history is one connected story, not two unrelated bills.
Action 3: reject (the decline)
Sometimes the right answer is no. A duplicate of a bill already paid. A bill for an order that was cancelled. A no-PO bill for goods nobody ordered. The approver taps Reject, gives a reason, and the ack-handler marks the bill rejected, notifies the supplier with the reason, and closes it out. Nothing is queued for payment, and the rejected bill stays in the record so the same bill arriving again can be spotted as a re-send rather than treated as new.
Reject is deliberately final but not destructive. The bill isn’t deleted — it’s kept, marked rejected, with the reason and the person attached. If a reject was a mistake (the duplicate turned out to be a legitimate second order), an admin can reopen it through a small command that reads the audit snapshot and puts the bill back in the queue. The reopen is itself an audit row.
Every action is logged, every action is reversible
The bm-audit table records every approve, query, and reject with the user who acted, the timestamp, the outcome the matcher had assigned, any override reason, and a snapshot of the bill’s state before and after. If a bill was approved in error — wrong supplier, wrong amount — an admin can run an “undo last action” that restores the previous state from the snapshot, as long as the bill hasn’t already gone out in a payment run. The undo is itself an audit row, so the trail of edits stays clean.
This kind of reversibility matters most for the decisions made quickly on a busy morning. Six months later, when an auditor or the owner asks “why did we pay this one over the PO price?”, the answer is right there: who approved it, when, and the reason they typed. The audit trail is the system’s memory.
Next post: the cost breakdown. The whole pipeline above runs in coffee-money territory at SMB volume; Part 6 explains exactly where the dollars go and why the match itself is essentially free.
All posts