Own product
Booked In
A booking page for solo beauty professionals that takes no commission — and a database that refuses a double booking, or a treatment whose patch test has run out, rather than asking the application to check first.
- Type
- Own product · UK
- Role
- Supabase Postgres with Row Level Security · Stripe · pg_cron · Render · Static front end, no build step · Twenty tracked migrations
- Status
- Live
- Built with
- PostgreSQL
- Supabase
- Stripe
- Render
- JavaScript
What it needed
- Interface
- Software
- Infrastructure
01
Problem
A solo lash, brow or nail technician has two bad options. A marketplace takes a slice of every booking and keeps the client relationship, or a salon system charges monthly for a diary built for a shop with a front door and staff rotas. Neither fits someone working out of other people’s front rooms.
This started as one page for èllash, a mobile technician covering three towns, where the awkward part was not the booking form but the travel: no salon diary understands that Tuesday is a Coventry day and Friday is Birmingham. Having built it once, the interesting question was whether the same thing could be handed to anyone else — which turns a page into a platform, and a platform has a problem a single page never had.
02
Approach
Two people on two phones can take the same 09:00.
Checking “is that slot free?” in application code does not fix it; it only narrows the window between the check and the insert. So the database refuses the second write itself — an EXCLUDE USING gist constraint over the studio and the appointment’s time range, so two overlapping confirmed bookings cannot both exist. The second transaction to commit raises 23P01 and rolls back. Cancelled rows sit outside the constraint, so cancelling frees the slot without any code deciding that it should.
That is the whole design in miniature: put the rule where it cannot be talked round. A studio’s plan is written only by the Stripe webhook, through a SECURITY DEFINER function with no grant to the anonymous or authenticated roles — so nothing running in a browser can promote itself to Pro. Cancelling at period end leaves her on Pro until the paid time actually runs out rather than cutting her off mid-month. And the view the booking page reads tells the client which times are gone without telling them who took them.
Each studio gets its own address and its own page, and the free tier carries a small line in the footer that Pro removes. The commercial promise is the one thing the architecture has to keep true: no commission, ever, however many clients she takes. There is no per-booking figure anywhere in the schema to take a percentage of.
The second rule in the database is the one nobody else enforces.
Tinting and lash extensions need a patch test 24 to 48 hours beforehand; the exact window is the insurer’s, a signed disclaimer does not replace it, and a test done at another salon does not count. Every other booking platform sells the appointment and leaves the compliance around it in a notebook — so the one thing a stranger can do unaided, book herself in, is also the thing that can quietly void the studio’s cover. Here a treatment that needs a test cannot be booked by somebody who has not had one in time, the refusal says what to do instead, the tile says so before anyone picks a time, and booking the patch test is the record, so nothing has to be written down afterwards. The window, the lifetime and which treatments need one are the studio’s own, because guessing at the wording of somebody else’s insurance policy is not a thing to do on their behalf.
03
Outcome
It is live, and it is called Booked In
— the name solo technicians already use about their own week, on its own domain at booked-in.co.uk. It grew out of the single èllash booking page, which is still live and still carries that technician’s real prices and photographs, and which is exactly why the platform needed its own sample studio: those photographs are her clients’ faces and cannot be reused as a sales demo for anyone else.
The schema is twenty tracked migrations and nine test suites. What is written: the exclusion constraint and the patch-test gate, sign-up and onboarding, two trades rather than one — the half that travels to a client and the half that works from a room, a rented chair or a shop, which needed an address in the database where previously there was only an area — photo handling, transactional email, a sender pg_cron runs every minute, rebooking reminders, deposits, and Stripe billing.
Rebooking is the feature that pays for Pro, and it is built to be hard to misuse: off until she turns it on, sent once per visit by primary key rather than by being careful, skipped for anyone already booked or long lapsed or opted out, and every message carries the one-tap opt-out that keeps it a soft opt-in rather than marketing.
Pricing is published: free to start, £10 a month for Pro, £24 for Pro+, a fifth off paid yearly, and fourteen days of Pro+ to begin with. No commission at any tier, and no cut of a deposit — there is no per-booking figure anywhere in the schema to take a percentage of. The build refuses to ship: build.sh will not produce a dist/ containing a service-role key, and it decodes every JWT it finds rather than grepping for the phrase, because the two keys look alike and a comment mentioning one is not a leak.
Nothing here claims a customer count, because a studio signing up is their business and not a number this site gets to publish.
The screens
3 captures



If this looks like your problem
Tell me about it and I'll tell you what it takes.