← Tutoring and music

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

Parents and adult learners, on a phone mostly

A trial lesson for £15, then a term enrolled in four steps — no account and no password at either end.

  • Sixteen subjects in eight kinds, each with its length and the price of one lesson; the page says what a whole term comes to and what comes off for a second child.
  • Only the tutors who give what was chosen are offered, and a time is offered only when the tutor and a teaching room are both free.
  • Enrolling asks for the weekly times that are free on every week of the term that is left, so the hour is real before it is chosen.
  • Before anything is committed the page lists every date, with half-term said where it falls, and what the term comes to: seven weeks at £24, £168.
  • One press books every week of the term, all of them or none, and one email carries every date.
  • A group that is full for the term offers its waiting list: a place that comes free is offered to the first in line and kept for 48 hours, and nothing is owed for waiting.
  • A trial lesson is paid for in full, £15, on the payment provider's own page; a place for the term asks nothing, because its money is the term's statement.

02

The family's own page

A family with a place, by a link that is theirs alone

What is waiting, the next seven days, the practice to tick off, the fees, and a lesson they cannot come to.

  • Waiting for you holds what wants a decision: a part of the fees that is due, a make-up to book, the term after to settle.
  • Each place is drawn as a row of leaves — filled for a lesson done, gold for the next, outlined for the ones still to come.
  • The newest note for each student, each thing to practise with seven days under it; a press on a day ticks it, and the count is what the tutor sees next week.
  • Can't come takes two presses, and what the rule gives is said before the second: in time, a make-up to book and by when; too late, that the lesson is charged.
  • Straight after, the same tutor's free times until the make-up's last day, with nothing to pay.
  • Fees and receipts is the term's statement: a line for each place, what comes off for a second student, the payments made and the parts still to come.
  • Each place says the term after is kept unless the family says otherwise, and by when — one press gives it up, and one takes that back.

03

Today, the term and the fees

The front desk, the tutors and the owner

Where the dashboard opens, and the three pages a school that teaches by the term runs on.

  • Today's three quiet cards are there only while they have something to say: notes to write, make-ups to book, statements overdue.
  • Enrolments is every place of the term in four figures — held, joined part-way, kept for the term after, leaving — with each place's leaves and what stands for the next term.
  • Carry over to the next term takes every kept place into it, on the same day, at the same hour, with the same person.
  • Absences and make-ups is the school's rule and every absence under it, with how the school heard and what each is owed.
  • Somebody is away shows what calling a tutor's day off would do — how many lessons, how many families — before anything is done.
  • Fees is one statement for each family and term, worked out from their places each time it is read: nobody types a statement.
  • A note is written from a lesson, by the tutor who taught it or by the owner; the front desk may read and write any, and a tutor only their own.

04

The staff phone app

Tutors, in the teaching room

One codebase for Android and iPhone, carrying the school's own name and mark, reading the same server as the dashboard.

  • The day and the week, each lesson with the student's first name, the tutor, the subject, and which lesson of the term it is with its room.
  • The term at a glance: the week of the term, then every place weekday by weekday — a tutor sees their own and nothing else.
  • A lesson: check in, mark completed, mark no-show, with a way to call or email the parent.
  • What was set last time, with the number of days each thing was ticked at home; a thing at nought is worth a word.
  • The note is written in the room: what was done, up to eight things to practise, and whether the parent is told.
  • A lesson a family told of reads as an absence: what the rule gave, when it was told and how.
  • It writes none of the term's own business — no absence, no place, no fee. That is the dashboard's.

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.

01

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.

02

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.

03

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.

04

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'.

05

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.

06

Notice

Nothing sooner than the lead time. Anywhere from no notice at all to a week.

07

How far ahead

Nothing later than the booking window — one day to a full year.

08

The slot grid

Start times step by the slot interval, measured from the beginning of each stretch of hours rather than from midnight.

09

Anyone available

Everybody's free times merged, each start shown once. On booking, whoever has the fewest bookings that day gets it first.

10

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 tutoring and music.

Its own words

Staff member
Tutor
Service
Subject
Customer
Family
Location
Room
Appointment
Lesson

Its starting rules

Notice before an online booking
A day
How far ahead
120 days
Slot step
30 minutes
Free change until
24 hours before
Deposit
Each subject sets its own — only the trial lesson, its whole £15
Notice that earns a make-up
24 hours
Make-ups a term, a student
2
A make-up is good until
14 days after the term ends
When the school calls one off
Always a make-up, counting against nobody's two
A term is paid in
3 parts
Off for a second student
A tenth
Reminders
Once, a day before
What is kept about a child
A first name, and the year — nothing else
Memberships, walk-in line, vouchers, recalls
Off — a school teaches by the term

The school

Ash Grove Music School — Bath, open after school on weekdays and Saturday morning.

The team

Six tutors — Daniel Okafor on piano and the theory group, Ingrid Halvorsen on violin and the string group, Mateo Ruiz on guitar, Aoife Brennan on singing, Tomasz Zielinski on cello, Priscilla Adeyemi on maths and English.

The rooms

Four teaching rooms and an ensemble room that holds eight together; a teaching room turns around in five minutes.

The term

The autumn term, 14 September to 12 December, twelve weeks, with half-term from 26 October to 1 November; the spring term drawn up and still to come.

The places

45 held by 28 families — 3 joined part-way, 43 kept for the term after, 2 leaving.

The groups

The theory group full at six of six, with a waiting list; the string group at six of eight.

The fees

One statement a family: two piano places at twelve lessons of £24, the theory group at twelve of £12, less £28.80 for the second child — £691.20 for the term.

The money

Pounds sterling, and British dates — “Thu 15 Oct”, “16:30”, on London's clock.

Past the engine

What the pack adds that this trade needs.

Terms and places

A place is one hour a week, for a whole term

A school that teaches by the term does not sell appointments. It sells an hour that belongs to one student until the term ends, and most booking tools have no word for that.

  • Every weekly time offered is free on every week of the term that is left, for the tutor and for a room.
  • The page lists every date, with half-term said where it falls, before one press books all of them or none.
  • Half-terms come from the term itself, and the diary closes for them by itself.
  • Carry over takes every kept place into the next term at the same hour; one whose hour has gone is left for the desk to settle.

The practice

The note goes home, and comes back ticked

What happens between two lessons is the whole point of a music school, and it is the part no booking tool touches.

  • A tutor writes what was done and up to eight things to practise, and chooses whether the parent is told.
  • On the family's page each thing has seven days under it, beginning on the day of the lesson; a press ticks one, and a day still to come cannot be ticked.
  • Before the next lesson the tutor sees how many days each thing was played. A thing at nought is in red.
  • A note is shown to that child's own family and to nobody else's, and what it says is never written to a log.

Absences and make-ups

The rule is said before anything is called off

Every school has a missed-lesson rule and nobody can remember it. The pack says what this one gives, in words, at the moment a parent is about to use it.

  • Told a day ahead or more: a make-up with the same tutor, up to two a term, good until fourteen days after the term ends.
  • Told later: the lesson is charged — it stays on the term's statement, and nothing more is asked for it.
  • A lesson the school calls off always gives a make-up, and it counts against nobody's two.
  • The four numbers are the school's own, and the words the site promises families say the same ones.

What is kept about a child

A first name, and the year — nothing else

Most of the people taught are children, so the pack asks the least it can and writes to the parent at every turn.

  • A first name is enough, and the year they were born is optional: a year, never a date.
  • No photograph, no address or phone number of their own, and no account in their name.
  • Everything goes to the parent's address and nothing to a child; an email names the student, a text message never does.
  • A parent can ask for a copy, a correction, or to be forgotten — and the children's names, their notes and what they practised go with the rest.

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.

FIG. 07Who can do whatRoutes ask for a permission, never for a role — which is why a third role is one file and no route changes.
ActionStaffOwner
See, make, change and cancel bookingsYesYes
Check in, complete, record a no-showYesYes
See, add and edit customersYesYes
Download a customer's data, or erase themNoYes
Services, categories, rooms and equipmentSeeYes
Staff, their hours, time off and closuresSeeYes
Booking settingsSeeYes
Text messages and their wordingNoYes
ReportsNoYes
Refund a depositNoYes
Invite members and change rolesNoYes
The business name, logo, and deleting itNoYes

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.

FIG. 04What a customer actually receivesAnd the longer list of what never arrives.

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.

FIG. 05The deposit holdWhere booking software usually loses money.
  1. 0:00Booking written

    The row exists before the charge, so a slot someone else takes never strands a payment

  2. 0:00Time held

    Pending. Nobody else can book it

  3. 15:00Hold expires

    Unless the customer asked for more time — three holds at most

  4. —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.

FIG. 06The booking state machineA released time goes straight back on sale.
  1. Pending

    Holding its time while a deposit is paid

  2. Confirmed

    Booked. The customer has the email and the link

  3. Arrived

    Checked in. Opens 12 hours before

  4. 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.

FIG. 02The whole deploymentFour callers, one app, one database. Nothing else to keep alive.

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  ↓

booking rulesniche packauthemaili18nlinksstoragejobsrate limitsui

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

Back to the tutoring and music pack