The specification
Everything the engine does, written out.
The sales page has the short version. This one has every screen in the dashboard, every message the engine will and will not send, how money is handled, who can see what, and what it is built on. Nothing here is a plan — it is a description of the build you download.
Where it appears
The four surfaces, all views of one database.
A customer’s phone, a dashboard, a screen on the wall and an app in a pocket. A change on one is on the others at once.
01
The booking site
Clients, on a phone mostly
Three steps with no account and no password: the pet, then the reason, then a time.
- Nobody books for themselves. A new client adds the pet — its name, its kind, a breed from the list or typed, and a date of birth or an age.
- Every visit the pet can have, grouped by kind, each with its length and its price for that pet; visits that do not fit the pet's kind or age are not offered.
- A client the site knows sees their own pets first, each with what is due, and can tick a second or third to be seen together, by one person, at one time — never dearer than booking them one by one.
- An operation, a dental or travel papers says “Ask us” instead of offering a time, and opens a card with the request already filled in.
- Anything urgent stops the booking: the card gives the practice's number, today's hours and the out-of-hours partner, and lists the signs to call at once for.
- A time is offered only when the person and a room are both free; the times held for the urgent are not offered until two hours before.
- The price list is public, as the regulator expects: every kind of visit with its minutes and its price, VAT included, and neutering priced by weight in bands.
02
The client's own page
A client, by a link that is theirs alone
What is booked, a card for each pet, the requests, and the quiet link under every one.
- Each pet's card: its photo on a ring, its breed, age and last weight, what is due, the next visit and its plan — and the client can change the photo or take it down.
- A health plan priced by the pet's kind, covering some visits and taking ten percent off the rest.
- Ask us something is a request that is not a time: a repeat prescription typed as it is on the box, an operation, travel papers, or a word about the end of a life.
- Requests and receipts shows each one as received, then ready — and a request about the end of a pet's life shows only as “Your request”, with no kind named.
- Ask for an earlier time puts the pet on the cancellation list; when a time comes back it is offered by email to the first whose wish it fits, and taken in one press.
- “Your day with us” repeats when to bring the pet and when to collect, and says that a day is changed by phone.
- Under each pet's card, a quiet link: the sheet asks whether the pet has died or has a new home, says what it will call off, and changes nothing until it is pressed.
03
Today, the diary and the board
The front desk, the vets and nurses, and the owner
The held times, the day, the operation board, the recalls and every pet's record.
- Today opens on the held times, with whose time each is and when it opens online — and “Open online now” gives one to the booking page early.
- The diary reads by day, by week or by room, with held times hatched, a visit for two pets showing both names, and the team's warnings in coral.
- A visit's note is written in the room: the weight and the temperature against the last reading and what is usual for the kind, then what the client told us, what we found, what we did and the plan.
- Drop-off days is the operation board — expected, in, in theatre, ready to collect, handed back — moved one press at a time, with who did it and when kept.
- Recalls lists every pet due back with its three reminders and where it stands; a booster missed by more than three months starts the course again.
- A pet's record carries its chip, its insurer and policy, the team's warnings that the client never sees, and its weight drawn as a chart.
- A death or a new home stops everything about the pet at once — no reminder, recall, offer or survey — calls off every visit still to come quietly, and stops its plan with nothing more taken.
04
The staff phone app
Vets and nurses, in the room
Built for Android from the same code — the day, a visit's screen with the pet's warnings, and the note and weight written in the room.
- It is built, and it is in the repository with the rest.
- It has not been walked: the machine the guide was made on could not start the phone emulator.
- So this page claims no screens for it, and the guide prints none. Its build and its state are written down in the handbook.
- Everything the practice runs on in this build is the dashboard, in a browser, and that is what every capture here shows.
The ten rules
What the engine refuses to break.
These are the decisions a booking engine has to make and almost never writes down. Each one is enforced in the database, not in a screen.
How long it takes
The chosen services added together, with the largest prep-before and the largest clean-up-after of them wrapped around the whole booking.
Who can do what
A staff member must do every service in the booking. Pick two and only the people who do both are offered.
The working day
Each person's own weekly hours. Two stretches on one day make a split shift, and the gap between them is not bookable.
Time already taken
Blocked, with its buffers — unless the booking was released by a cancellation, a no-show or an expired hold. A new booking's buffers stack on its neighbours'.
Closures and extra openings
A holiday, a week off, a late Thursday. Set for one person or the whole location, and they add time as readily as they remove it.
Notice
Nothing sooner than the lead time. Anywhere from no notice at all to a week.
How far ahead
Nothing later than the booking window — one day to a full year.
The slot grid
Start times step by the slot interval, measured from the beginning of each stretch of hours rather than from midnight.
Anyone available
Everybody's free times merged, each start shown once. On booking, whoever has the fewest bookings that day gets it first.
A person's own length and price
A senior stylist's cut can be longer and dearer than the shop's. Their number replaces the service's, for them only.
And beyond the ten
The settings an owner can move, and how far
- Only people who still take bookings are offered.
- Busy times read in from a staff member's own calendar block the diary.
- A service that needs a room or a piece of equipment needs a free unit of it.
- A service marked in-house only is never offered to a customer, though staff can book it.
- Notice before a booking
- 0 minutes to 7 days
- How far ahead
- 1 to 365 days
- Slot step
- 5, 10, 15, 20, 30 or 60 minutes
- Cancellation window
- 0 to 168 hours
- Reminders
- Up to five, each 1 to 168 hours before
- Deposit
- None, a fixed amount, a percentage, or per service
- Hold while a deposit is paid
- 5 to 30 minutes
- Prep and clean-up per service
- 0 to 240 minutes each
- Currency
- Any ISO 4217 code, until the first booking
- Automatic data clean-up
- Off, or 1 to 120 months
Not a generic booking tool
The parts that are the shape of a veterinary clinic.
Its own words
- Staff member
- Vet or nurse
- Service
- Visit
- Customer
- Client
- Location
- Room
- Appointment
- Consultation
Its starting rules
- Notice before an online booking
- 1 hour
- How far ahead
- 90 days
- Slot step
- 10 minutes
- Free change until
- 24 hours before
- Held times, each vet
- Two short times a day
- A held time opens online
- 2 hours before
- A consultation
- 20 minutes — 30 for a rabbit, a small pet, a giant breed or a pet of ten or more
- Recalls
- Three — four weeks before, on the day, four weeks after
- A booster missed by
- More than three months starts the course again
- A missed visit
- The first in twelve months is on us; the practice sets the rest
- Never a missed-visit fee
- A quiet visit, one the practice called off, or a pet that has died
- Memberships, walk-in line, vouchers
- Off — health plans do that work here
The practice
Fernbank Veterinary — 8 Sample Lane, Harrogate, open Monday to Friday and Saturday morning.
The team
Four vets and three nurses — Hannah Reid, Tomás Ferreira, Priya Nair and Owen Gallagher; Bethany Cole, Sam Whitlock and Lucy Marsh.
The rooms
Three consulting rooms, the nurse room, the theatre and the kennels, each saying when it is next in use.
The day
Fifty-four consultations booked, fifty-three still to come, and £3,143.20 taken.
The patients
Thirty clients with forty-six pets — dogs, cats, rabbits and a guinea pig; Rolo may bite, Rex needs two people, and Bracken died last month.
The prices
A £52 consultation up to 40 kg, £60 for a giant breed, £78 for two pets on the same reason and £120 for three in one visit — VAT included.
The plans
£19.50 a month for a dog, £15.50 for a cat, £12 for a rabbit — some visits covered, ten percent off the rest.
The money
Pounds sterling, and British dates — “Fri 16 Oct”, “11:30”, on London's clock.
Past the engine
What the pack adds that this trade needs.
The patient
The owner books; the animal is the patient
Every other booking tool models one person. A practice needs two — the one who pays and the one who is treated — and almost everything about the visit comes from the second.
- Every booking names a pet, and the pet is chosen before the reason, because its kind, weight and age decide the length and the price.
- A consultation is twenty minutes for a dog or a cat and thirty for a rabbit, a small pet, a giant breed or a pet of ten or more.
- Two or three pets can be seen together, by one person, in one room, at the practice's own price for them together.
- The team's warnings — “may bite”, “needs two people” — live on the pet and show in coral on the day, and are never shown to the client.
Held times
A practice cannot sell out of today
A fully booked diary is a disaster for a vet: the pet that needs seeing this afternoon does not care that the page is full.
- Each vet keeps two short times a day for pets who need to be seen today.
- They are drawn hatched in the diary, and the booking page is not offered them until two hours before.
- “Open online now” gives one to the booking page early, in one press; the desk can book into one at any moment.
- A client who ticks an urgent sign is stopped before they book, and given the practice's number and the out-of-hours partner instead.
Operations
An operation is a day, not an appointment
The pet comes in at eight, goes to theatre at eleven and leaves at half past one. That is a board, not a diary slot.
- Expected, in, in theatre, ready to collect, handed back — one press moves a card on, and Back a step undoes it.
- Each card names the pet, the client, the operation, the weight and the kennel, with the times to admit, operate and collect.
- Who moved it, and when, is kept.
- The client is told the evening before when to bring the pet and, by its kind, what to do about food — and told again when it is ready.
The quiet parts
What happens when a pet dies
This is the thing clients remember about a practice, and the thing an automated reminder can ruin. The pack treats it as a first-class case rather than an afterthought.
- A client can tell the practice from their own page: the sheet asks whether the pet has died or has a new home, and changes nothing until it is pressed.
- Everything stops at once — no reminder, no recall, no offer, no survey — every visit still to come is called off quietly, and the plan stops with nothing more taken.
- The pet is filed apart under Remembered, with its date, and has no colour, no motion and no button. The records stay.
- A quiet visit — a talk about quality of life, or the end of a pet's life — is never named in any email, calendar file or reminder: it says “your consultation”.
The owner’s dashboard
Menu by menu, and what it does.
Seven screens. The menu reads in the trade’s own nouns, so an owner is never translating from software words into their own.
01
Today
The morning screen.
Bookings made today, what is still to come, takings so far, and a list of anything waiting on a person — a deposit to refund, a late change to approve.
02
Appointments
Day or week, one column per person.
Open a booking to check a customer in, mark it done, or record a no-show. Drag it to another staff member. Add a walk-in without leaving the screen.
03
Services
What can be booked.
Grouped into categories, each with a length, a price, and padding before or after for clean-up.
04
Staff
People and their hours.
Which services each person does, their weekly hours, time off, extra openings, and a login of their own.
05
Clients
Everyone who has booked.
Visit history, total spend, private notes, and the privacy actions — download their data, stop emails, erase them.
06
Reports
What actually happened.
Bookings, value, utilisation, no-shows and cancellations for any period, by person and by service, with where the booking came from. CSV download.
07
Settings
The rules of the business.
Booking link, profile, locations, time zone, notice period, how far ahead people can book, cancellation window, time step, currency, deposits, reminder times and automatic data clean-up.
Plus the platform admin console, on the Full and All-packs licences.
Every account and every business on the deployment: roles, sessions, bans, and signing in as somebody to help them, with a banner that says you are.
Who can do what
Routes ask for a permission, not a role.
Which is why a client who needs a third role — a receptionist who may not change prices — defines it in one file and changes no route at all.
| Action | Staff | Owner |
|---|---|---|
| See, make, change and cancel bookings | Yes | Yes |
| Check in, complete, record a no-show | Yes | Yes |
| See, add and edit customers | Yes | Yes |
| Download a customer's data, or erase them | No | Yes |
| Services, categories, rooms and equipment | See | Yes |
| Staff, their hours, time off and closures | See | Yes |
| Booking settings | See | Yes |
| Text messages and their wording | No | Yes |
| Reports | No | Yes |
| Refund a deposit | No | Yes |
| Invite members and change roles | No | Yes |
| The business name, logo, and deleting it | No | Yes |
Four kinds of person
Customer
No account needed
Books in a few taps. Every email carries a private link to manage that booking — no password to forget, no sign-up to abandon.
Staff
Works the calendar
Check-ins, walk-ins, moves and no-shows. Sees their own clients. Cannot change settings, see reports, or touch money.
Owner
Runs the business
The full dashboard, every report, all settings, staff logins and the money.
Platform admin
Runs the platform
Every account and business, impersonation to help someone, role changes, bans. Full and All-packs licences only.
Messages
Every message that goes out — and what never is.
The second list matters more than the first. Booking software that quietly markets to a client’s customers is a problem the client discovers after launch, in public.
Booked
the moment it is made
Confirmed
mail goes out
2 h before
reminder
The visit
and then silence
to the customer
Confirmation
Calendar file, private link, stamp card
to the staff
New booking
To the staff member, unless they made it
to the customer
Reminder
At each time the owner set
And what never leaves the building
- A follow-up after the visit
- A review request
- Any marketing email, ever
- Anything about a no-show
- Anything while a deposit is pending
- A reminder whose visit has already begun
Booking software that quietly markets to a client’s customers is a problem they find out about in public. This one has no sender for it.
How they leave the building
- Every change to a booking writes a history row, and in the same transaction the messages that change calls for. One function decides who is told what.
- A reminder is simply an outbox row with a later time. Moving the booking drops and re-queues them; cancelling drops them.
- Each message is rendered from the booking as it stands when it is sent, so a confirmation delayed by a provider outage still shows the right time.
- A failure that might pass is retried after 1 minute, 5, 15, an hour, then every few hours — eight attempts over about a day. A refusal of the address is given up at once.
- Text messages go through the business's own provider account: Twilio over SMS or WhatsApp, Meta's WhatsApp Cloud API, or any gateway that accepts a JSON POST.
- Nothing is texted without consent, STOP and START replies are honoured for every booking that number has, and a monthly ceiling stops a runaway bill.
Every message, and exactly when it goes
- Confirmation
- Once the booking is confirmed. Carries a calendar file, the private link to manage it, and the loyalty card where the pack has one.
- Moved
- When the time changes. Not when only the staff member changes at the same time.
- Cancelled
- With a refund note where one applies and a link to book again. Its calendar file removes the event.
- Reminder
- At each reminder time the owner set. Dropped if the booking moved, was cancelled, or the visit has already begun.
- New, moved, cancelled
- On the same changes, unless they made the change themselves.
- Account mail
- Confirm your email, reset your password, a magic link, a six-digit code, a change of email, an invitation to a business.
Money
Stripe built in, any gateway in one file.
Payments go through one interface. A provider says whether it is configured, starts a charge, says how to pay it, calls it off, and reads its own webhook. Nothing downstream knows which provider took the money — which is what makes a country Stripe does not serve a day of work instead of a rewrite.
- 0:00Booking written
The row exists before the charge, so a slot someone else takes never strands a payment
- 0:00Time held
Pending. Nobody else can book it
- 15:00Hold expires
Unless the customer asked for more time — three holds at most
- —Webhook decides
Confirmed only on the provider's signed message. The page never takes the browser's word
Refunds and disputes
- A deposit for a time that is no longer theirs, or a cancellation inside the window, puts the booking in “needs your attention”. The owner presses refund, and pressing twice is still one refund.
- The engine never refunds by itself. An owner can also refund at the provider and mark it handled.
- A chargeback is flagged above everything else, because the business has a deadline for its evidence, and the refund button is refused so nobody pays twice.
Any country
- Stripe is built in, with its own card form on the page and 3-D Secure handled there. Card numbers never reach the server, which keeps the business in PCI's shortest self-assessment.
- Where Stripe does not serve a country, a local gateway is one file against one interface. Nothing downstream knows which provider took the money.
- A stand-in gateway in the test suite lets that file be tested without any keys at all.
- Money is stored as whole units of the smallest denomination, one currency per business, and a booking keeps its price as booked — so a price rise never moves an old booking.
A booking’s life
Seven states,and the rules between.
Pending
Holding its time while a deposit is paid
Confirmed
Booked. The customer has the email and the link
Arrived
Checked in. Opens 12 hours before
Completed
Done, and a loyalty stamp earned
Any state above can leave the flow
Cancelled
Time released, reminders dropped, refund flagged if one is owed
No-show
Time released, counted on the customer. No fee, no message, undoable the same day
Expired
The hold ran out. Time goes back on sale and the charge is called off
Completed and no-show are only reachable after the visit has started. A released time goes straight back on sale, and the database — not the code — is what refuses the overlap if someone takes it first.
Calendars
Read both ways, never obeyed.
A feed to subscribe to
Every staff member has a private address, 30 days back and 400 days ahead, that Apple Calendar, Google Calendar or Outlook can subscribe to. Renewing it cuts off every old copy at once. It needs no keys at all.
Their own calendar, read in
Paste the secret address of a personal calendar and the engine keeps only the busy times — start and end, never the title and never who is attending. Those times block the booking page.
Google and Microsoft, both ways
Busy times come in; bookings go out, 180 days ahead, written once and not rewritten when nothing changed. A failing connection backs off for up to a day, then says on the staff page that it needs looking at.
Clashes are flagged, not obeyed
An outside event landing on a booking already made never moves or cancels it. The booking gets a clash flag, and the flag clears itself if the outside event goes away.
Accounts and security
The part nobody asks about until after launch.
Six ways to sign in, limits that are on in every environment, and a double-booking guard that lives in the database rather than in code somebody might refactor.
Six ways to sign in
- Email and password
- Web and phone
- Magic link by email
- Web and phone
- Six-digit code by email
- Web and phone
- Passkey
- Web
- Two-factor with an authenticator app, with backup codes
- Web and phone
- Apple, Google and GitHub
- Web and phone, with each provider's keys
Rate limits, per caller
- Signing in with a password
- 5 a minute
- Trying a six-digit code
- 3 a minute
- Asking for a link, a code or a password reset
- 3 in 5 minutes
- Reading the booking page's free times
- 120 a minute
- Making bookings
- 10 in 10 minutes, 5 an hour per email address, 200 in 10 minutes per business
- Cancelling or moving from a booking link
- 20 in 10 minutes
A refusal answers with a retry time, in the visitor’s own language. There is no CAPTCHA anywhere in the product.
Signed links, not guessable ones
A booking's own page carries an HMAC signature. Nothing about the link is stored, a booking always has the same link, and it stops working a week after the visit. An altered link gets one flat answer that never says which part was wrong.
Every row belongs to one business
The database itself enforces it: a booking cannot name another business's staff member, customer, location or service. An integration test proves one business cannot read or write another's rows.
The double-booking guard is in the database
Not in the code. A Postgres exclusion constraint refuses two overlapping bookings for the same person, buffers included, and the API turns that refusal into a message the page can translate.
Secrets are sealed
A staff calendar token, a business's text credentials — encrypted with AES-256-GCM under a key that lives in the host's settings, never in the repository, and never sent back to a browser.
Cross-site writes are refused
Forms by a CSRF check, JSON by an origin check. Machine routes carry no cookie and prove themselves with a secret or a signature instead.
Headers that are actually set
An enforced content policy, framing denied outright, two years of HSTS on production builds, no sniffing, a strict referrer policy, and camera, microphone and location switched off.
Privacy
A compliance kit, not a promise to add it.
Export, erase and retention are built, and six documents ship with the code — including the answers to Apple’s and Google’s privacy forms, which is the step that delays a launch by a fortnight.
Export
Everything the business holds about one customer as JSON — who they are, every booking with its services and prices, payments, what happened to each booking, the messages sent, and their places in any queue. The whole customer list downloads as CSV. Owners only, and typed text is written so a spreadsheet will not run it as a formula.
Erase
Name, email, phone and notes gone for good, along with what was typed on their bookings. The bookings themselves stay without a name, so the figures still add up. Refused while they have a booking ahead. It cannot be undone.
Retention
Erase customers who have been away a number of months you choose, 1 to 120, off until an owner turns it on. A customer with a booking ahead, or money still waiting on a person, is left alone until it is settled.
Stops, per customer
“Don't email this customer” stops every email from that business to them. Texts have their own switch on the record, the customer's own switch on their booking page, and STOP replies. A bounce or a complaint protects the person from every business on the deployment.
Cookies
Six, every one of them essential or functional, each listed with its purpose and how long it lasts. Booking needs no cookie at all. No advertising cookie, no tracking cookie, and no analytics unless the owner switches one on.
The compliance kit
Six documents: who is controller and who processor, what to choose at each provider to keep data in the EU, the card-payment note for Stripe's shortest self-assessment, every cookie and storage entry, the data inventory that answers Apple's and Google's privacy forms, and a one-page handout for the owner.
One command prints the outside providers your own settings actually select, by name and purpose, and the privacy page shows the same list. The engine itself sends nothing home: no telemetry, no licence check, no call to its maker.
The kit is a set of templates, not legal advice, and the demo business’s legal facts are sample facts. Your client’s lawyer fills in theirs.
How it is built
One deployment, no worker to babysit.
No queue, no cache server, no second service. The API is mounted inside the web app by one catch-all route, and one HTTP call a minute runs every background job — which is the whole reason it fits on a $5 host.
Customers
booking page, queue, screen
Owners & staff
dashboard, admin console
Staff phone app
iPhone and Android
Scheduler
one call a minute
One deployment · one address
Pages & dashboard
Next.js 16, React 19, rendered on the server
API at /api
Hono 4, mounted in the same process
↓ they share one set of packages ↓
One Postgres
Every row belongs to one business, enforced by the database
Outside providers
Mail, payments, texts, calendars, picture bucket
No queue. No cache server. No worker to keep alive. That is the whole reason it runs on a $5 host, and the reason a handover is one repository and one database rather than a diagram like this one with six boxes missing.
And what each piece is
- Web app
- Next.js 16 and React 19. The marketing pages, the booking flow, the queue, the screen view, a booking's own page, the dashboard, the admin console and the legal pages.
- API
- Hono 4 under /api, mounted inside the same app by one catch-all route. Same program, same address: there is no second server to deploy.
- Staff phone app
- Expo SDK 57 and React Native 0.86, one codebase for iPhone and Android, signing in against the same server and calling the same routes.
- Database
- One Postgres, 17 or 18. The schema is written with Drizzle and changed only by numbered SQL migrations.
- Sign-in
- Better Auth 1.6 with its plugins for businesses, the platform admin, two-factor, passkeys, magic links, email codes and the phone app.
What is actually tested
100%
of the rules engine
Every line and every branch of the booking rules, held at full coverage by the build. The booking tests run a second time under another time zone to prove nothing reads the server's own.
20
booking the same second
Twenty simultaneous bookings for one slot, against Postgres 17 and 18. Exactly one wins. The rest are offered another time.
< 300ms
to find free times
A benchmark on the availability query that the build refuses to go past.
5
browser set-ups
End-to-end tests across five, with an accessibility gate that fails on a single axe violation on the dashboard or the booking page.
Hosting
Three routes, one of them recommended
Cloudflare Workers
The recommended host, and the one the build is tuned for. The whole app and its API run as one Worker, Postgres through Hyperdrive, pictures in R2, and a cron trigger calling the tick every minute. From $5 a month.
Vercel
Project root is the web app, and the cron entry that calls the tick every minute is already written. A bucket is required, because pictures do not survive a deploy.
Any Node server
A production build behind one reverse proxy, with the jobs worker kept alive beside it. Every setting is spelled out in full rather than assumed.
Running it
It refuses to half-work
- Start-up checks refuse to serve customers on a half-configured deployment: a missing link secret, an unknown payment provider, a bucket with no public address, a console mail sender. Every route answers 500 and the log names each problem on its own line.
- A doctor command runs the same checks from your machine against the live settings, and also checks the database, the extension the booking guard needs, your sending domain's DNS records, and the phone app's identity.
- A health route answers ok, degraded or down, and tells a monitor how long ago the tick last finished — the one alarm worth setting.
- Migrate first, then deploy. A status command shows what is pending without changing anything, and the release notes say what each one does.
- One verify command is the gate: the repository's own checks, formatting, lint, types, migrations, the doctor and every test, in order, stopping at the first failure.
White label
A check fails the build if our name is anywhere a client could see it
- Nothing a customer or a staff member sees names the engine. The product name, the logo, the installed-app icon, the link-preview picture and the cookie prefix are all the client's.
- A strict check fails the build if a page, a language file, an email, store text or a public file still carries the engine's name or the template's.
- Colours, corner radius and the font pair reach every screen as variables, so a change lands everywhere without editing a component. The phone app takes the same four values.
- The client folder is the only place a project writes: the brand, the wording overlay, the legal facts, its own routes, its own event subscribers and its own tables.
- Any engine file a project does change is recorded in one list, and a check fails if that list is out of date — so the next engine update merges cleanly.
That is the whole of it