# Before you send your booking link to a client

From the night I built my own booking app (crmmeister.com/blog/build-your-own-calendly, Ahmed Essam, October 2026). Every item below is something a check caught, a doc warned about, a step I had to do by hand, or a mistake I made. It works for a bought tool too: most items are questions to ask your vendor.

## Calendar truth

- [ ] The page never shows a time it could not check. If the calendar read fails, show no times, not stale ones.
- [ ] Free/busy is re-checked at the moment of booking, not trusted from the list the visitor loaded.
- [ ] Two bookings for the same slot at the same second: one wins, one gets a clear "taken" message. Enforce it in the database, not only in code.
- [ ] Buffers count: a booking cannot start inside the buffer of another.
- [ ] You move a booked event in your calendar: the new time is busy for everyone else.
- [ ] A guest declines in their calendar: the booking is marked declined and the slot frees up.
- [ ] Clock changes: test a week that crosses a daylight saving change, in your zone and the visitor's.
- [ ] A visitor whose browser reports no time zone sees a sensible default, and can change it.

## Google

- [ ] OAuth app type checked: an external app in "Testing" gets refresh tokens that expire after 7 days. Internal (Workspace) or "In production" avoids it.
- [ ] Tokens die after a password change when they carry Gmail scopes: the admin shows it and something alerts you.
- [ ] Push notification channels expire and do not renew themselves: a scheduled job renews them.
- [ ] Meet link: the first API response may not hold the finished conference data yet. Read it again before you mail the link.

## Mail

- [ ] One invitation per booking: if the calendar already sends one, your app does not send a second with its own .ics file.
- [ ] Your domain has SPF, DKIM and a DMARC policy beyond "none" once reports are clean.
- [ ] A mail that fails is retried, and a mail that may have gone out is never sent twice.

## Abuse

- [ ] Bot check (for example Turnstile) runs before any calendar or database call.
- [ ] Limits per e-mail address, per IP and per hour, including guest addresses.
- [ ] Notes and guest lists cannot turn your invitation into a spam relay from your own address.

## Hosting

- [ ] Scheduled jobs (reminders, sync, cleanup) run on a platform that has them. Cloudflare Pages has no cron triggers; Workers do.
- [ ] Measure CPU time per booking request against your plan's limit (mine: 22 ms against a free limit of 10 ms).
- [ ] A free database that pauses after a week of no traffic gets a keep-alive, or a paid plan.
- [ ] Backups decided: manual export, scheduled export, or a plan that includes them.
- [ ] An uptime monitor on a health URL that does not depend on your own mail account.

## Integrations

- [ ] The webhook to your CRM is queued before anything else after a booking, so a mail or Meet hiccup cannot drop the lead.
- [ ] A webhook that keeps failing alerts you before it is switched off.
- [ ] Lead source captured: UTM, click ids and the referrer, without query strings.

## Shipping

- [ ] The code is in git and every change is committed before a deploy.
- [ ] Before a deploy: read the open items, compare what will ship with what is live, and check that every database change the code needs is applied.
- [ ] After every phase: a separate reviewer gets the code and the spec, not the builder's explanation, and tries to break it.

## Legal

- [ ] The privacy notice names the booking data, the processors (calendar, database, hosting) and how long logs are kept.
- [ ] Footer links to the privacy notice and the legal notice on the booking page itself.
