Approval inbox with an audit log
A service any of your other builds can call when a person has to say yes: it posts the request to Slack with Approve and Reject controls, calls the caller back with the decision, and keeps a row saying who decided what and when.
What you'll have at the end
A repository and a public Vercel address with four endpoints, a Supabase table of requests, a Slack channel where each pending action arrives with two controls, an audit table with one row per decision, a job that expires anything unanswered after 24 hours, and a one-page inbox.
What it proves
Accounts to create
- Instead of Claude Code: Codex
What it costs: Free tiers on Supabase, Vercel, GitHub and Slack. No model usage at all: nothing in this build calls a model.
Have this ready
- A Slack workspace where you are allowed to create an app and a channel
- A terminal with Node 20.6 or newer, git, and Claude Code installed; Codex works the same way, with the same prompts
- A GitHub account where you may create a repository
- One made-up decision a business would want a person to approve, in one line; the copy blocks use a 240-shekel refund, so swap that for your own
How it fits together
- POST /api/requests: another build asks for a decision
- requests table: one row, status pending
- Slack #approvals: Approve button, reject menu
- /api/slack-actions: signature checked, row updated
- approval_audit: who, when, what, why
- callback_url: the caller hears the answer
- /api/expire: 24 hours with no answer
The steps
1. The table and the repository
One table that holds pending decisions, and a repository holding the service that will fill it. After this step nothing is live, but the shape of every row and every endpoint is decided.
2. A public address that stores a request
The endpoint live on an address Slack will be able to reach later. Until the service has a public address, no button anywhere can call it back.
3. The request lands in Slack
The part a person sees. Every pending row arrives in one channel with two controls, so nobody has to open a database to say yes.
4. The decision, checked and recorded
The endpoint Slack calls when somebody presses a control. It proves the call really came from Slack, updates the row, tells the original caller, and replaces the message so nobody decides twice.
5. The audit table
The row the client actually cares about six months later: who decided, when, what they decided and why. The requests table changes; the audit table only ever grows.
6. Anything unanswered expires
The quiet failure this kind of service has: a request nobody ever presses. After this step it closes itself, tells the channel, and answers the caller, instead of waiting forever.
7. The inbox page and the hand-over
One page that answers "what is waiting, and what did we decide?" without a database login, and the two notes that let somebody else run this after you.