Scheduling

Adding scheduling to your product without building a calendar engine

Aug 25, 2026 · 3 min read

The ticket says add a booking page. It reads like a week of work: a form, a list of open slots, a confirmation email. Then someone asks what happens when the host drags the meeting to Thursday from their own calendar, and you realize the ticket has been hiding a calendar engine.

The booking page is the smallest part

A booking page is a form over a slot list. The form is easy. The slot list is a query against a system you do not control: one or more external calendars, owned by a provider, mutated constantly by people who have never heard of your product. Everything hard about scheduling lives in that query and in keeping its answer true after you have shown it to someone.

Scope it before you commit. The page is maybe a tenth of the work. The rest splits into availability computation, provider sync, reminder delivery, and the lifecycle of a booking after it exists. Each of those is a system with its own failure modes.

Availability is arithmetic with hostile inputs

Computing open slots means intersecting working hours with free/busy data, then subtracting buffers, minimum notice, daily caps, and the duration of the meeting itself. Each rule is simple. The combination is not, because every input arrives in a different time zone and at least one calendar is stale at any given moment.

Then there are the edges, and they are not rare. They are Tuesday.

  • A host in one zone, an invitee in another, and a DST transition between now and the proposed slot.
  • An all-day event that should block bookings on one calendar but not on another.
  • A recurring event with a single instance moved to a different day.
  • A canceled meeting that still appears on one of two synced copies of the same calendar.

Sync never stops

Reading a calendar once is an API call. Keeping a copy correct is infrastructure. Google and Microsoft both push change notifications, but the channels expire and the payload often tells you that something changed, not what. Some deliveries never arrive at all. You end up building renewal jobs and reconciliation sweeps, plus a merge strategy for the edit that happened on both sides at once.

Tokens are their own tax. Refresh tokens die when users change passwords or an admin tightens policy, and your booking flow has to degrade sensibly when a connected calendar goes dark. The alternative is worse: confidently offering slots that were taken days ago.

Bookings live on after the confirmation

The confirmation email is the start, not the end. Reminders reduce no-shows, which means scheduled sends, which means a job queue and per-invitee time-zone rendering. Reschedules need a link that survives the original event being moved or deleted. Cancellations have to propagate to both calendars and notify both sides. Every one of these paths can fail halfway, so each needs its own retry and reconciliation story.

This is also where trust is won or lost. A reminder that arrives at the wrong hour, or a reschedule link that returns a 404, reads as your product being careless with someone's day.

When embedding is the right call

Build the engine yourself when scheduling is the product, when your availability rules are genuinely unusual, or when you already operate calendar sync for another feature. Those are real cases. Most products are not in them.

For everyone else, embedding wins the math. A white-label scheduler gives you the page, the availability engine, the sync layer, and the reminder lifecycle behind one integration, and your team keeps building the thing your users actually chose you for. Horato packages exactly that split: white-label booking pages on top of a normalized calendar API for Google and Microsoft accounts, so the flow runs under your brand while someone else worries about change-notification renewals. Whichever way you go, decide with the real scope on the table. The page was never the project.