Small Biz Workflows
FORM SBW-005 · REV B · 5-FILE KITBack Office

One-Tap Mobile Approvals

Replace 'approve this when you're back at a computer' with a magic link: the approver taps once on their phone and the workflow moves — single-use, expiring, fully audited, with an LLM writing the decision-ready summary.

TIME TO VALUE
1 weekend
RUNNING COST
$0–5 (hosting you already have + pennies of LLM)
STACK
Any web stack with one serverless function · HMAC-signed JWTs · SMS or email delivery · A consumed_tokens table · An LLM (Claude) for the approval summary
Get the kit free ↓READ THE FULL BUILD BELOW · KIT FREE WITH EMAIL

PROVENANCE: Production magic-link approval flow for purchase orders, single-use tokens with TTL. Generalized for any small business — no vendor lock-in, no secrets, adapt freely. The kit's INSTALL.md is a one-shot Claude Code build: unzip, paste one prompt, answer ~5 questions.

TL;DR

The work is done; the decision is what's late. A magic link moves the decision to the decider's pocket: one text, one tap to review, one tap to approve. Tokens are single-use and expire; the database enforces it; an audit row remembers it. An LLM writes the three-line summary so the approver decides in ten seconds.

The problem this solves

Every small business has one: the approval bottleneck. Quotes, purchase orders, time-off, change orders — all queued behind a busy owner who is on a roof, in a truck, or at dinner. The usual fix (give everyone portal logins) dies because nobody remembers a password for a thing they use twice a month.

A magic-link approval removes every step except the decision itself. No login, no app, no password — and no security hand-waving either, because the token discipline below is real.

The full picture

                          THE END-TO-END FLOW
═══════════════════════════════════════════════════════════════════════

 ┌──────────────────┐         ┌──────────────────────────────┐
 │ SOMETHING NEEDS  │         │  LLM SUMMARIZER (optional    │
 │ APPROVAL         │ ──────▶ │  but worth it)               │
 │ (PO, quote,      │         │  turns the full record into  │
 │  change order)   │         │  3 decision-ready lines      │
 └──────────────────┘         └──────────────┬───────────────┘
                                             ▼
                              ┌──────────────────────────────┐
                              │  TOKEN MINTED                │
                              │  signed JWT encoding:        │
                              │  • what (order id)           │
                              │  • who may approve           │
                              │  • action scope (approve PO) │
                              │  • expiry (72h)              │
                              └──────────────┬───────────────┘
                                             ▼
                              ┌──────────────────────────────┐
                              │  SMS TO THE APPROVER         │
                              │ "PO-118 — 24 pr cut gloves,  │
                              │  $312, Norwood Supply.       │
                              │  [Review & Approve]"         │
                              └──────────────┬───────────────┘
                                             │ tap
                                             ▼
                              ┌──────────────────────────────┐
                              │  REVIEW PAGE                 │
                              │  shows EVERYTHING:           │
                              │  every line, every dollar    │
                              │  [ APPROVE ]  [ REJECT ]     │
                              └──────────────┬───────────────┘
                                             │ tap
                       ┌─────────────────────┼─────────────────────┐
                       ▼                     ▼                     ▼
            ┌──────────────────┐  ┌──────────────────┐  ┌──────────────────┐
            │ STATE CHANGES    │  │ TOKEN CONSUMED   │  │ AUDIT ROW        │
            │ in the SAME      │  │ (unique constraint│ │ who, when, what, │
            │ transaction —    │  │  = used exactly  │  │ from which device│
            │ next step fires  │  │  once, ever)     │  │                  │
            └──────────────────┘  └──────────────────┘  └──────────────────┘
═══════════════════════════════════════════════════════════════════════

What you need

Piece What it does Pick
One serverless function + one page Mints tokens, renders review, handles the POST Whatever you already host (Vercel/Cloudflare/Supabase Edge)
JWT library Signs and verifies tokens correctly Any standard lib in your language — never hand-roll
Delivery Gets the link to the approver SMS beats email for field owners
consumed_tokens table Enforces single-use at the DB level One table, one unique constraint
LLM (optional) Writes the 3-line decision summary Claude Haiku — pennies

Phase 1 — Mint the token

The token is a signed claim: this person may take this one action on this one thing, until this time.

  TOKEN PAYLOAD (inside a signed JWT)
  ┌──────────────────────────────────────────┐
  │ jti:     "tok_8f3a…"   ← unique token id │
  │ action:  "approve_po"  ← ONE action only │
  │ subject: "PO-118"      ← ONE record only │
  │ approver:"+1403…"      ← who may use it  │
  │ exp:     now + 72h     ← short life      │
  └──────────────────────────────────────────┘
            signed with a SERVER-ONLY secret
            link = yourapp.com/a/<token>

Rules that are not optional:

Phase 2 — The LLM decision summary

The approver shouldn't decode a database row on their phone. Give the record to an LLM and send three lines a human can act on:

The summarizer prompt:

You write SMS approval summaries for {{business_name}}. Below is the full record needing approval. Write EXACTLY three short lines:

  1. What it is and who requested it.
  2. The total cost and the vendor/recipient.
  3. Anything unusual a careful owner would want flagged (price above the usual range, new vendor, rush request) — or "Nothing unusual." if clean. Under 280 characters total. No emoji, no sales tone.

RECORD: {{record_json}} HISTORY (last 5 similar approvals, for the unusual-check): {{history_json}}

  EXAMPLE OUTPUT ON THE APPROVER'S PHONE
  ┌────────────────────────────────────────────┐
  │ PO-118 from Dave — 24 pr cut-resistant     │
  │ gloves. $312 to Norwood Supply.            │
  │ Nothing unusual.                           │
  │ Review & approve: yourapp.com/a/tok_8f3a…  │
  └────────────────────────────────────────────┘

That third line is the quiet killer feature: the LLM compares against recent history and flags the weird one — "Price is 40% above the last three glove orders." The owner's attention goes exactly where it should.

Phase 3 — The review page and the binding tap

  GET /a/<token>                         POST /a/<token>/decide
  ┌─────────────────────────┐            ┌─────────────────────────────┐
  │ verify signature        │            │ verify signature again      │
  │ check expiry            │            │ BEGIN TRANSACTION           │
  │ check not consumed      │            │   insert jti into           │
  │ load the record         │            │   consumed_tokens  ← unique │
  │ render EVERYTHING:      │            │   constraint = the lock     │
  │  every line item        │            │   update record state       │
  │  every dollar           │            │   write audit row           │
  │ [APPROVE] [REJECT]      │            │ COMMIT                      │
  └─────────────────────────┘            │ fire the next step          │
                                         └─────────────────────────────┘

Phase 4 — Close the loop

  APPROVED ──▶ parent workflow continues (PO emails the vendor,
       │       quote goes to the customer, time-off hits the calendar)
       │
       └─────▶ requester notified: "PO-118 approved by Mike, 2:14 pm"

  REJECTED ──▶ back to requester with the note, state returns to draft

The approver's tap should visibly cause something. That's what builds the habit.

Data model

 consumed_tokens                      approval_events
 ┌──────────────┬─────────────┐      ┌──────────────┬──────────────────────┐
 │ jti          │ UNIQUE ←lock│      │ id           │ uuid                 │
 │ consumed_at  │ timestamp   │      │ subject      │ PO-118               │
 │ decision     │ approved/   │      │ action       │ approve_po           │
 │              │ rejected    │      │ decided_by   │ +1403…               │
 └──────────────┴─────────────┘      │ decision     │ approved | rejected  │
                                     │ note         │ "wrong vendor" / null│
                                     │ decided_at   │ timestamp            │
                                     │ device_meta  │ user-agent, ip       │
                                     └──────────────┴──────────────────────┘

Folder structure

 one-tap-approvals/
 ├── CLAUDE.md            ← the AI-builder brief (ships in this kit)
 ├── api/
 │   ├── mint.ts          ← create token + LLM summary + send SMS
 │   └── decide.ts        ← verify, consume, transition, audit — one transaction
 ├── pages/
 │   └── a/[token].tsx    ← the review page (shows everything)
 ├── lib/
 │   ├── token.ts         ← sign/verify helpers (JWT lib, never hand-rolled)
 │   └── summarize.ts     ← the LLM summary + history comparison
 └── prompts/
     └── summarize.md

Security rules (the whole list, again, because it matters)

Numbers to watch

Metric Healthy Where it comes from
Median time-to-decision < 10 minutes (was: hours–days) mint → decide timestamps
Expired-token rate < 10% — higher means TTL too short or asks too noisy consumed vs expired
Rejection rate with notes rejections are good — silence is the enemy approval_events
"Unusual" flags caught every true catch = the LLM summary earning rent flag vs owner action

Week-one checklist

Troubleshooting

MEMBERS · FREE · THE KIT

Get the One-Tap Mobile Approvals kit — free

Drop your email and the full kit opens right here. No spam; new-workflow announcements only.