Eastern Standard Studio · case study

Two people click the same slot. One booking exists.

A small architecture and MEP practice needed a site that turns a visitor into a confirmed appointment in one visit. The calendar was the easy part. The work went into making sure it can only ever be right.

Status
Verified end to end
Built
Sept 2026
Runtime
WordPress 7.1 · PHP 8.4 · Docker
Engagement
Self-initiated

Enquiries stalled between the contact form and the diary.

Someone reads about a project and wants twenty minutes to find out whether their own job is feasible. They meet a contact form that disappears into an inbox. Two days later a partner replies offering three times, and one is already gone. The thread runs four messages before anything reaches a diary, and some prospects give up in the middle.

The practice also cannot show its work properly. Every project has facts a prospective client wants first: where, how big, how long, which disciplines. Those facts were retyped into prose, so they disagreed between pages and could not be filtered.

What finished looks like, stated so it can be checked:

  • A visitor picks a slot and receives a confirmation in one visit, with no email exchange.
  • Two people clicking the same slot at the same moment produce exactly one booking.
  • Each project's facts are entered once and render identically everywhere they appear.
  • The site is fast and structured enough that the work can be found.

Eastern Standard Studio is invented. The practice, its projects and its address are fictional, and this build was self-initiated.

One slot holds one booking, and the database enforces it.

Correctness

Make double booking a database problem

The obvious build reads "is this slot free?" and then writes. Two simultaneous requests both pass the read. That window is milliseconds wide, and a double-booked principal is the exact failure a booking system exists to prevent, so the guarantee sits in a UNIQUE KEY.

wp_ess_slot_locksUNIQUE KEY slot_start409 on conflict

Scope

Build the booking engine in code

A booking plugin carries settings for every case this practice does not have: one appointment type, one calendar, a fixed weekly pattern. The only hard question in it is the one a plugin would hide.

Structure

Keep the data out of the theme

Everything functional sits in a plugin and the theme only renders. A redesign in three years should not take the appointments with it.

Automation

Host the automation on the same network

A cloud automation platform cannot reach a local site without a tunnel whose URL changes on every restart. n8n on the same Docker network has a hostname that never moves, and the workflow ships as a JSON file in the repository that setup.sh imports.

  • WordPress7.1
  • PHP8.4
  • MariaDB11.8
  • Block theme
  • Block Bindings API
  • Interactivity API
  • REST API
  • n8n2.40
  • Redis8
  • Mailpit1.31
  • Docker Compose

What one booking does.

The steps run on every booking request. The table beside them lists one decision per layer and what it buys.

  1. Guard the requestNonce, honeypot, a signed render stamp and a per-address rate limit, all checked before anything is written.
  2. Resolve the slot on the serverA submitted timestamp is never trusted. It is matched against the slots the studio publishes, or a crafted request could book 3 am on a Sunday.
  3. Create the record, then claim the lockIf the claim loses the race, the record is deleted again, so a rejected booking leaves nothing behind.
  4. Hand off to the automationThe payload carries its own callback URL, so the workflow never hardcodes where the site lives.
  5. Confirm backn8n sends both emails and calls a signed REST route that marks the booking confirmed and timestamps it.
  6. Recover when it does notA booking with no callback stays pending, and an hourly job releases the hold after 30 minutes. A broken workflow costs one slot for half an hour.
One decision per layer.
LayerChoiceWhat it buys
ThemeBlock theme, theme.json v3No page-builder payload; 100 performance
DisplayBlock Bindings APIProject fields bound to core blocks, no shortcodes
Booking UIInteractivity API, server-renderedSlots in the HTML before JS; CLS 0
CorrectnessUNIQUE KEY on a lock tableA second booking for a slot cannot be written
Automationn8n on the Docker networkConfirmation and callback with no tunnel
MailMailpitEvery message inspectable; nothing leaves the stack
CacheRedis, phpredis in the shared imageCLI and web behave identically
HomeLCP 0.5 s
Projectssector and location from meta
Spec sheetevery value is a bound field
Contactsame nonce and anti-spam layer
The workflow that confirms a bookingdrawn from n8n/workflows/ess-booking.json

ess-core/includes/class-slot-lock.php

// No SELECT first, on purpose: the INSERT is the test, and it is atomic.
// Correctness comes from UNIQUE KEY slot_start, not from PHP timing.
$inserted = $wpdb->insert( self::table(), [ /* … */ ] );

if ( false === $inserted ) {
    return false;   // duplicate key → the REST layer returns 409
}

ess-core/includes/class-availability.php

// PHP silently rolls a non-existent local time forward on a spring-forward
// day. Comparing the result back catches a slot no clock ever shows.
$built = DateTimeImmutable::createFromFormat( 'Y-m-d H:i', "$date $time", $tz );

if ( ! $built || $built->format( 'Y-m-d H:i' ) !== "$date $time" ) {
    return null;
}
Every run the automation has haddrawn from n8n's database

Twelve requests for one slot. One booking.

Every figure on this page came from a script in the repository, so each one can be re-run.

Twelve simultaneous bookings, one slot: bin/concurrency-test.sh 12

  1. 201
  2. 409
  3. 409
  4. 409
  5. 409
  6. 409
  7. 409
  8. 409
  9. 409
  10. 409
  11. 409
  12. 409
  • 1 created
  • 11 rejected
  • 1 lock row
  • 1 booking post
  • 0 orphans

Lighthouse, desktop preset

The first audit scored 92 for SEO on two pages and 95 to 98 for accessibility on four. The causes: labels marked up as headings, so the outline jumped from h2 to h6; a text colour at about 1.1:1 against its background; and two pages with no meta description. Each was fixed at the cause, and the table is the second audit.

Reports committed to the repository under docs/lighthouse/.
PagePerfA11yBestSEOLCPCLS
Home1001001001000.5 s0
Projects1001001001000.5 s0
Single project1001001001000.7 s0
Services1001001001000.4 s0
Booking1001001001000.5 s0
Contact1001001001000.4 s0
Lighthouse, home pagereport in the repo
At 390 px0 px horizontal overflow, seven pages
  • Booking made in a browser, confirmed by the automation with no human step1,062 ms
  • Daylight-saving assertions across the 1 Nov 2026 transition6 / 6
  • Callback with a missing or wrong signature403
  • Callback with a valid signature and an unknown booking404
  • Horizontal overflow at 390 px, seven public pages0 px
  • PHP warnings after exercising every page and both write routes, WP_DEBUG on0
  • Build step required to run the sitenone

Want to see the lock hold?

I can run the concurrency test live and walk through the plugin, the theme and the workflow on a call. The source is public.