Skip to content
DWKD
Journal 7 min read Beginner Digital products

Why this site runs on its own booking system

Why DWKD keeps booking records inside its own site, what the current system does, and the operating responsibilities that come with building it.

A laptop and a paper calendar on a dark desk under a warm lamp

This site keeps its own booking records. Pick a slot at /book/, and the server saves the booking before attempting invitation emails with an ICS attachment and video-room link. A saved booking does not prove that an email reached the inbox or was added to a calendar. Optional Google Analytics runs only after consent and excludes names, email addresses, project notes and room links; native Jetpack telemetry is a separate integration.

The useful reason to build here is keeping the booking record directly inside the site that serves members and manages contacts. An external scheduler can also synchronize records through APIs or webhooks, so data ownership alone does not settle the buy-versus-build decision. Compare that integration work with the cost of owning availability, validation, delivery, recovery and maintenance yourself.

The map, applied to a booking

The first process map had five visible parts: availability rules, a slot, two identities, an invite and a room. The implementation adds editable weekly rules, a public slot feed, a short form, an ICS file and a room link. That is a useful boundary for a small system, but it is not a time estimate: conflict handling, email delivery and recovery add work beyond the five boxes.

That drawing is the decision, and the same one I run on client processes. The bottleneck map makes the boxes and the waits visible before anybody opens a pricing page, because an undrawn process always looks bigger than it is — or, more expensively, smaller.

What the booker actually does, in order

Availability is six rules in one setting: which weekdays, start and end time, slot length, notice required, how far ahead to offer. The defaults are weekdays, nine to four, thirty-minute slots, twelve hours of notice, fourteen days out. One screen edits them, and nothing else in the system has an opinion about Tuesdays.

From those rules a public /slots feed builds the open list, dropping anything inside the notice window and anything already taken. The browser converts to the visitor’s timezone, so nobody does arithmetic. The form asks for a name, an email, and one optional line of context — plus a hidden field no human ever sees.

Submitting sends a request to the booking endpoint. After saving and verifying the booking fields, the server derives a video-room name from the slot, email and site salt, writes an ICS attachment, and attempts two invitation emails: one to the guest and one to Kenny. Room naming does not by itself establish meeting access control, and email attempts do not establish delivery.

The slot that is gone by the time you click it

Here is the second-order problem, and why a five-box drawing is not a finished job. The list of open slots in your browser was true when the page rendered. Two people can be looking at the same Thursday. Somebody can leave a tab open for an hour.

The booking endpoint regenerates the offered slots before saving and rejects one that is already taken or has expired. This handles a stale browser list, but the currently deployed read-then-write check is not an atomic reservation: two requests could both pass before either writes. Stronger concurrency protection needs a separate tested release; this case study does not claim double bookings are impossible.

That stays cheap because one function owns the rules and both the feed and the endpoint call it. Most of what looks like scheduling complexity is two copies of the same truth disagreeing.

The token that expired inside the cache

None of the above is the interesting part. This is.

Every public form here shipped a security token baked into its HTML, and that HTML is served by an edge cache. The tokens expire after about a day, and a stale one produces nothing a visitor can see — the request comes back as a silent 403. So anyone served an older cached page could press Book and watch nothing happen, on the one form whose job is to make money. No log said a booking had failed, because as far as the system was concerned none was attempted.

The public booking route supports anonymous requests without a cached signed-in nonce, with server-side validation, rate limits and a bot field. Signed-in requests still use the appropriate nonce checks. These controls serve different purposes: rate limiting reduces abuse; it does not replace authorization or CSRF protection for private actions.

That defect is the honest cost of building and also the argument for it: the failure was mine, so the fix was mine the same afternoon. Either way somebody has to care that the button did nothing — the difference is whether you can do anything about it. Shipping so it fails safely is most of the distance between a demo and something strangers use.

What keeping the record local makes possible

The subscription was never the point. The record was.

An external scheduler owns its primary booking record unless you synchronize it elsewhere. DWKD stores its record in WordPress, where other site features can use it directly. Both designs need deliberate integration; an email alone is not a durable customer history.

The Contacts administration screen synchronizes booking contacts by email when it loads. The plugin also has a helper for retrieving a member’s bookings, but the currently deployed account template does not display them. A customer-facing booking history still needs a tested interface and access checks. Local storage makes integration convenient; it does not create a CRM automatically.

Every failure path ends at a human

Three bookings an hour from one connection is plenty, so the fourth is refused — and the refusal hands out my email address rather than a code. If the save fails, the message says to email me and it gets sorted by hand. The hidden field that catches bots returns a cheerful success message, because an error tells the bot what to change.

The shared booking-link helper retains a configured third-party destination as a fallback. Links using that helper prefer the published /book/ page while DWKD Engine is available, then use the configured external URL otherwise. Direct links to /book/ do not automatically change destination. Recovery needs checking across the actual entry points, not just the helper.

What it does not do, said plainly

No payment collection. No self-serve reschedule or cancel — you reply to the email and I move it. No live read of my own calendar, so anything I agree to elsewhere stays out of the rules by hand. No reminder the day before. No round-robin, no team routing, no buffer between calls, no daily cap. The open-slot lookup reads at most two hundred upcoming bookings, sized for one person.

For one person taking a handful of calls a week, those absences cost close to nothing. For a business with a shared inbox, a no-show problem and five people to route between, they are the entire product — buy the product. The cost of building is not the afternoon. It is that every line above is now mine to add later, if it ever matters.

What this settles about buy versus build

That decision is part of product strategy and transformation: understand the process and choose what is worth building. Improving the path from a visitor’s first click to a working booking is also a practical example of digital experience consulting.

The honest lesson is not “always build.” Calendly is excellent, and for most businesses the buy decision is correct — my own rule is buy before build, build before wish. The lesson is that the decision should be made from the map, not from the pricing page. When the drawn process is five boxes and you already own the surrounding system, building is often smaller than integrating. When it is fifty boxes with edge cases you have never met, buy the fifty boxes.

Which makes it an exercise rather than an opinion. Managed or self-run, decided properly is the same short set of questions I ran here; the first asks whether operating the thing exceptionally well is a genuine differentiator. For a booking form, obviously not — so the build had to justify itself on the record it leaves. When it is a vendor you are weighing, a five-day evaluation that can actually say no beats a three-month one that cannot.

How I know it worked

Live is not the test. Three signals are.

The operational checks are specific: a visitor can choose a time without clarification; the expected booking and contact records persist; and an actual invitation reaches the intended inbox and can be opened in the calendar. Check those separately. The saved receipt proves persistence, not delivery or calendar acceptance.

The negative test matters more, and gets skipped: try the cold path. Not the second click, the first, with nothing warmed up and nothing cached. Test cold, then check both the screen and the record — that habit has caught more here than any amount of rereading the code.

The same reasoning runs through every engagement here: sometimes it ends in a vendor contract, sometimes in an afternoon of code, and the map decides — not the demo. If your stack has a subscription that a drawn process would shrink to five boxes, the booker works, appropriately enough, for exactly that conversation.

Try this

Map the subscription workflow you are considering replacing, including failures, retries, conflicting requests and support. Compare a supported integration with the ongoing cost of building and operating it. A short map helps define the decision; the number of boxes is not enough to make it.