How we built atomic label purchases
A deep dive into the SQL function that guarantees you’re never charged for a label that didn’t print.
A label purchase touches three things: an external API (EasyPost), a wallet ledger, and a label record. Get the order wrong and you have impossible state — money deducted but no label, label exists but no charge.
The order matters
We always call EasyPost FIRST. If EasyPost returns success, then we run a single Postgres function that does the wallet deduction and label insertion in one transaction.
The function
purchase_label_atomic locks the wallet row with SELECT FOR UPDATE, validates balance, decrements, inserts the wallet_transactions row, inserts the label row with the void window expiry, and returns the label id. If any step fails the entire transaction rolls back — and the caller voids the EasyPost label.
Why a SQL function and not application code
Three reasons. (1) Single source of truth — the wallet/label invariant lives in the database, not scattered across services. (2) Lock granularity — a SECURITY DEFINER function holds the row lock for milliseconds, not the round-trip duration of an API call. (3) Auditability — RLS still applies, every change is logged.