Part 5 of 7 · Booking assistant series ~5 min read

How a booking gets confirmed

The customer taps one of the proposed slots. From there, four things happen in a strict order — claim, write, confirm, remind. Each step is idempotent, the calendar is the source of truth, and a slot can only be claimed once even if two customers click at the same second.

Confirmation: claim, write, confirm, remind — in that order A vertical sequence of four numbered steps with arrows between them. At the top, “Customer picks a slot” — a tap on a one-click confirm link from the proposal email. Step 1, “Claim,” runs an atomic conditional write on a small DynamoDB table keyed by (resource, slot start) using a condition-expression that fails if the row already exists. The first request for a given slot wins; any second request for the same slot gets a 409 and is bounced back to the proposal stage with fresh slots. Step 2, “Write,” calls Google Calendar’s events.insert with all the booking details (service, customer, notes, location, video link if needed) and a stable UUID set as the event id, so a retry of the same write returns 409 duplicate instead of creating a second event. Step 3, “Confirm,” sends a confirmation email via SES with the booking details, an .ics attachment for one-click add-to-calendar, and a reschedule/cancel link. Step 4, “Remind,” schedules an EventBridge rule for one day before and one hour before the appointment to send a reminder email or SMS. A reschedule or cancel from the customer flows back to a small “reschedule/cancel handler” box which deletes the calendar event and the claim row, freeing the slot. A bottom note reads: claim before write, write before confirm, confirm before remind — a partial failure at any step leaves the system in a known good state. Customer picks a slot one-click confirm link step 1 Claim atomic conditional write keyed by (resource, slot) step 2 Write to calendar events.insert with a UUID event id step 3 Confirm SES email + .ics + reschedule/cancel link step 4 Remind EventBridge: T−1 day, T−1 hour Reschedule / cancel delete event + claim, slot is free again 409: slot taken re-propose fresh slots, no event written Claim before write, write before confirm, confirm before remind. Partial failures leave a clean state.
Fig 5. Four steps in strict order. Each one is safe to retry; the claim step is the one that prevents double-booking.

Step 1 — Claim the slot before anything else

The double-booking problem isn’t solved by “check the calendar, then write to it.” Two requests checking at the same time both see the slot as free, and both write. The fix is a single atomic step that either succeeds or fails — and the easiest place to do it is a small DynamoDB table.

The table is keyed by (resource_id, slot_start_iso). The claim step does a PutItem with ConditionExpression: attribute_not_exists(resource_id). The first request for a given slot succeeds; any second request gets a ConditionalCheckFailedException and is bounced back to the proposal stage with fresh slots.

The claim row carries a TTL roughly equal to the slot’s start time, so abandoned claims (the customer started but never finished the confirmation) auto-expire and free the slot.

Step 2 — Write to the calendar

Only after the claim succeeds does the confirmer call Google Calendar’s events.insert. The event includes:

  • The right calendar. The qualified resource’s calendar (stylist A’s personal calendar, the “chair 2” resource calendar, etc.) — not a global business calendar.
  • The summary and description in your style: service name, customer name, customer phone, any notes from the request.
  • An attendee entry for the customer with their email, so they get the calendar invite alongside the email confirmation.
  • A stable event id. Google Calendar lets you set the id field on the event yourself instead of letting the server pick one. We use a UUID derived from the claim row’s key, so the same write retried twice always gets the same id. The second try comes back as 409 duplicate — not a second event on the calendar.

If the calendar write fails (Google has a hiccup, the OAuth token rotated mid-flight), the confirmer rolls back the claim row so the slot is free again and the customer sees a clean “something went wrong, here are fresh slots.”

Step 3 — Confirm to the customer

Now the customer hears back. The confirmation is a short SES email with:

  • The service, the time, the staff member, and the location (or the video link, if it’s a remote service).
  • An .ics attachment so “add to my calendar” is one click on any device.
  • A reschedule link and a cancel link, both signed and time-limited so they can’t be replayed by anyone but the customer.
  • The deposit-payment link if your rules file says this service requires one.

The tone is straight from your rules file — warm, terse, formal, whatever you’ve set. The same language model that does the parsing fills in the few variable bits; the rest is a template.

Step 4 — Schedule the reminders

One-time EventBridge schedules trigger one day before and one hour before the appointment. Each reminder is a short message with the same details and the same reschedule/cancel links. If the customer reschedules, the old reminders are cancelled and new ones are scheduled against the new time. If they cancel, both reminders are cancelled.

The hour-before reminder is the one that pays for the system on its own. No-shows drop noticeably when customers get a quiet nudge an hour before they need to leave.

Reschedule and cancel: the same flow, in reverse

Both links lead to a small handler that does three things in order: delete the calendar event, delete the claim row (so the slot is free again), cancel the pending reminders. If it’s a reschedule, the customer is then routed back to the slot-proposal stage for the same service, in a new window, and the loop closes when they pick a new slot.

What’s deliberately not in this step

  • Payment up front, except where rules say so. Most small services don’t want a deposit gating the booking. The rules file decides per-service whether deposits are required.
  • Customer accounts. Confirmation links and reschedule/cancel links are signed; no login needed. Adding accounts later is fine, but it’s not required for the assistant to work.
  • Two-way calendar sync. The assistant writes to the calendar; manual edits in Google Calendar (you drag an event to a new time, or block out an unexpected meeting) are picked up the next time the freebusy API is queried. There’s no separate sync job to drift.

In plain words

Claim the slot atomically. Write the event with a stable UUID as its id. Send a confirmation with an .ics file and a reschedule link. Schedule two reminders. If anything fails, the previous step is the rollback. The customer sees a fast, friendly confirmation; you see a calendar that never lies.

All posts