Home / Projects / Booking Platform

Replacing Typeform, Calendly & Zapier with One Booking Platform

One owned system replaced three recurring SaaS subscriptions — Typeform, Calendly and Zapier — moving the entire booking workflow inside Google Workspace, customizable at every step and dependent on no outside vendor.

Status In production · v3.2
Scale~4,000 bookings / month
Core TechnologiesGoogle Calendar API · Google Sheets API · Google Apps Script · TypeScript
01

Overview

A scheduling system that lets a distributed services team take bookings across many practitioners without ever double-booking, mis-handling a timezone, or duplicating an appointment on a retry — with Google Calendar as the single source of truth.

What was built

A booking platform layered directly on Google Workspace. Clients pick a slot from computed availability; the platform reserves, verifies and confirms the appointment as a single idempotent transaction, writing the event to the practitioner's Google Calendar and notifying both sides. There is no separate database of record — the calendar the team already lives in stays authoritative.

Who it's for

A services business running several practitioners, each with their own working hours, buffers and time off, taking bookings from a web form, email, and the occasional phone call entered after the fact.

Business context

The manual process it replaced was losing roughly a slot a week to double-books and timezone slips — small in count, expensive in trust. The platform's job was to make the calendar authoritative and booking safe, without adding a second system that would drift out of sync.

02

Architecture

A thin, stateless coordinator in front of Google Calendar. A booking is a short, idempotent transaction — reserve, verify, confirm — routed through a per-practitioner queue so the race that causes double-booking cannot occur.

Clientweb form / email intake
EdgeCloud Function — validate + normalize TZ
QueueCloud Tasks — per-practitioner FIFO
↓ one booking at a time, per practitioner ↓
Coordinatorreserve → verify → confirm
LedgerFirestore — idempotency keys (TTL)
Source of truthGoogle Calendar — freebusy + events

Why a queue per practitioner

Double-booking is a race between concurrent writers for one resource. Rather than reach for distributed locks, every request for a given practitioner runs through a single-consumer FIFO queue — a boring, debuggable primitive that makes the race impossible instead of merely recoverable. Concurrency across practitioners stays high; within one practitioner it drops to one.

03

Components

The platform is built from nine components. Each is documented as its own engineering section — why it exists, its architecture, the constraints and edge cases it handles, the tradeoffs taken, and the final implementation. Expand any to read it.

C1Multi-step booking flowclient-facing
Why it exists
Turns "find a time" into a guided, resumable sequence — service → practitioner → date → slot → details → confirm — that never presents a slot it can't honor.
Architecture
A stateless client driven by the availability engine; each step re-validates against live free/busy so a slot that fills mid-flow is caught before submission.
Constraints
Must work from a web form, an email link, and a manually-keyed phone booking; no step may rely on the client's device clock.
Edge cases
Slot taken between display and submit; back-button re-submission; a practitioner going on leave mid-session.
Tradeoffs
Re-validating each step costs an extra free/busy read but eliminates the "the slot you picked is gone" failure at confirm.
C2Availability engineread model
Why it exists
Computes bookable windows per practitioner from working hours, buffers, existing events and time off — the read model the whole flow renders from.
Architecture
Precomputes windows on a schedule and on calendar-change webhooks, caching them in Firestore; the flow reads the cache, not the Calendar API, on the hot path.
Constraints
Must stay correct as practitioners edit their own calendars directly; must not exceed Calendar read quota under load.
Edge cases
Overlapping events, all-day blocks, recurring holds, and DST boundaries where a "9am" window shifts.
Tradeoffs
A short cache TTL trades a few seconds of staleness for a large drop in API calls; the confirm step is the authority, so brief staleness is safe.
C3Booking API (reserve · verify · confirm)core
Why it exists
The transactional core: turns a request into exactly one calendar event, safely, even under concurrency and retries.
Architecture
Every request for a practitioner runs through that practitioner's single-consumer FIFO queue; confirm() checks free/busy inside that window and writes with a content-addressed idempotency key.
API contract
POST /booking  → 201 {eventId}  | 409 SlotTaken
// idempotent: same intent ⇒ same eventId
key = sha256(practitionerId, startUTC, clientId)
Edge cases
Concurrent requests for one slot (serialized away); form/network retries (collapse to one event via the ledger); partial failure after insert (ledger commit makes it replay-safe).
Tradeoffs
Per-practitioner serialization adds a few ms of latency and removes the race by construction — chosen over distributed locks.
C4Google Calendar integrationWorkspace
Why it exists
Keeps Google Calendar as the source of truth — reading free/busy and writing events — so the team never leaves the tool they already use.
Architecture
Uses the aggregate freebusy query for availability (no event detail) and events.insert for writes, tagging each event with its booking key in private extended properties.
Constraints
Per-minute read quotas; least-privilege scopes; must survive practitioners editing events by hand.
Edge cases
Manually-deleted events, external calendar shares, and reconciliation when the calendar and ledger disagree.
C5Time-zone handlingcorrectness
Why it exists
Guarantees a slot means the same instant to client, practitioner and server across daylight-saving changes.
Architecture
Stores the instant in UTC and the user's intended IANA zone as a separate field; the two recombine only at render.
Edge cases
Spring-forward/fall-back gaps, zones that observe DST on different dates, and clients changing their device zone after booking.
Tradeoffs
Carrying an explicit zone is more to store than a bare timestamp, but it is the only way to preserve the promise "9am in Lisbon."
C6Authentication & permissionssecurity
Why it exists
Lets the platform act on the domain's calendars with the least authority a security team will sign off on.
Architecture
A dedicated service account with narrowly-scoped domain-wide delegation; every account traces to an owner and a decision record.
Constraints
No broad scopes; keys rotated on a schedule; access reviewed on a calendar, not on memory.
Tradeoffs
Least-privilege means more configuration up front in exchange for a model that passes review without exceptions.
C7Notificationsdelivery
Why it exists
Confirms, reminds and cancels for both client and practitioner without ever sending twice.
Architecture
At-least-once delivery via Cloud Tasks with idempotency keys and a dead-letter queue, so a retry never double-sends.
Edge cases
Provider throttling, bounced addresses, and a booking cancelled before its reminder fires.
C8Google Meet generationWorkspace
Why it exists
Attaches a working video link to every remote booking at the moment the event is created.
Architecture
Requests conference data on events.insert so the Meet link is part of the same atomic write as the booking.
Edge cases
Conference-creation failures (the booking must still succeed) and rescheduled events keeping their link.
C9Google Sheets reportingops
Why it exists
Gives the operator a familiar, live view of bookings and load without a bespoke dashboard.
Architecture
Append-only sync from the ledger into a Sheet, treated as a read replica — never a source of truth.
Tradeoffs
A Sheet is limited and slightly delayed, but it is instantly legible to a non-engineer, which is the point.
04

Visual Output

What the system actually produces for the customer and the operator. These are exhibits from the running system — each captioned and cross-linked to the component that creates it. Drop the real screenshots into the frames.

The booking flow

The three-step customer journey: service & timing → contact details → date and time → confirmation. Each step re-validates against live availability.

The booking flow

Real screenshot from the running systemComponent: Multi-step booking flow

The lead record

Where the contact step's answers land: a row appended to the Google Sheet — name, company, work email, source and submission timestamp. Use anonymized or sample data.

The lead record

Real screenshot · sample / anonymized dataComponent: Google Sheets reporting

Calendar event created

The booking API writes directly to the practitioner's Google Calendar — title, time, timezone, both attendees, a Google Meet link, and the intake context in the description. Proof the system creates a native calendar artifact, not a private database row.

Calendar event created

Component: Booking API · Calendar integration

Customer invitation & confirmation

What the customer receives — the Google Calendar invitation and confirmation, with meeting details and the Meet link.

Customer invitation & confirmation

Real screenshot · redact recipient detailsComponent: Notifications · Google Meet generation

Internal notification

What the team sees when a booking lands — the internal notification with the captured qualification context and source workflow.

Real screenshot — internal booking notification

Real screenshot · anonymizedComponent: Notifications

Failure & recovery

The states that prove production thinking: a slot taken between display and submit, and the successful retry that follows. Left: conflict. Right: recovery.

Real screenshot — slot-taken / conflict state
Real screenshot — successful retry / recovery

Component: Booking API (availability & concurrency)

05

Impact

Objective outcomes that resulted from putting the platform into production — evidence, not marketing.

Business
  • Replaced a Calendly-plus-Zapier scheduling stack with one owned system.
  • Removed the recurring third-party subscription cost and the glue connecting the tools.
Customer
  • Immediate, reliable confirmation at the moment of booking.
  • Fewer scheduling failures — no offered slot that can't be honored.
Operations
  • Duplicate bookings eliminated.
  • Timezone handling automated end-to-end.
  • Manual calendar entry removed from the team's day.
Engineering
  • A single source of truth — no parallel booking database to drift.
  • Retry-safe, idempotent writes; fewer moving parts to operate.
Scale
  • ~4,000 bookings / month.
  • Zero double-books since the v2 serialization landed.
06

Technical Documents

Chronological engineering records written while building the platform — the decisions, dead ends and incidents as they happened. Newest first.

07

Case Study

A retrospective on the platform from the business perspective — why it exists, the approach taken, and how it turned out.

Problem
A services team ran its calendar by hand and lost about one slot a week to double-books and timezone errors — cheap to count, expensive in client trust.
Goal
Make the calendar authoritative and booking safe, operable by one person, without a second system of record.
Constraints
Google Calendar stays the source of truth; least-privilege security; correct under concurrency and retries; within Calendar API quotas.
Approach
A thin, stateless coordinator in front of the Calendar API; per-practitioner serialization to remove the race; content-addressed idempotency to remove duplicates; UTC-plus-intent for time.
Architecture
Intake → edge validation → per-practitioner FIFO queue → reserve/verify/confirm coordinator → Google Calendar, with a small idempotency ledger. No parallel booking database.
Tradeoffs
Chose a boring, provably-correct queue over distributed locks; accepted seconds of availability staleness for a large quota saving; kept state in the platform that already owns it.
Outcome
In production since Feb 2024, ~4,000 bookings/month, zero double-books since the v2 serialization landed; "did my booking go through?" became a non-event because retries are safe.
Lessons
  • Serialize the contended resource; don't lock the world.
  • Idempotency is a feature, not a safeguard.
  • Let the platform keep the state it already owns.
Future
A self-serve reschedule flow, and moving the ledger's TTL cleanup off cron onto document expiry.
← All projects