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.
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
idfield 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 as409duplicate — 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
.icsattachment 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.