How to Build·14 min read

How to Build a Scheduling and Booking App Like Calendly

Scheduling apps look simple on the surface. Under the hood, timezone logic, calendar sync, and conflict resolution make them surprisingly complex.

N

Nate Laquis

Founder & CEO ·

Why Scheduling Apps Are Harder Than They Look

Calendly looks like a simple product. You pick times, someone books one. How hard can it be? Deceptively hard, it turns out. Most teams that try to build scheduling software underestimate the complexity by a factor of three.

Calendar interface showing scheduling and booking app features

The first wall you hit is timezones. A user in New York sets their availability as 9 AM to 5 PM. A client in Tokyo wants to book a slot. What is 3 PM Eastern in JST? What happens when Daylight Saving Time kicks in mid-series for a recurring booking? These questions seem academic until they break a real booking and cost someone a deal.

The second wall is calendar sync. Google Calendar, Outlook, and Apple Calendar each have different APIs, different authentication flows, and different event models. Bidirectional sync means you write to the calendar and read from it, and you need to handle conflicts when both sides change simultaneously.

The third wall is conflict resolution. A user has availability from 10 AM to 2 PM, but they already have a meeting from 11 to 11:30 that got added outside your app. Your system needs to know about that event before it ever shows the booking page. If it doesn't, you get double-bookings, which destroy trust fast.

Edge cases stack up quickly: buffer times between meetings, minimum notice periods, booking cutoffs, rolling availability windows, collective vs. round-robin team scheduling, multi-person bookings. Each one is individually manageable. Together, they form a meaningful engineering surface area.

None of this means you shouldn't build it. Calendly charges $8 to $16 per seat per month, and it still can't be customized to match your exact workflow. If scheduling is core to your product or business, owning that logic pays for itself.

Core Features for an MVP

Before you overengineer this, lock down what your MVP actually needs. A scheduling app has a small, well-defined core that you can ship in 8 to 12 weeks and validate before building the complex stuff.

Availability Windows

Let hosts define their working hours per day of the week. Monday through Friday, 9 AM to 5 PM is the baseline. You also need date overrides: mark specific days unavailable (holidays, vacations) or add custom hours for an event. Store all of this in UTC and convert at display time.

Booking Pages

Each event type gets a public URL. The page shows available slots in the visitor's local timezone, allows them to pick a slot, and collects their name, email, and any custom questions you configure. Keep the UI minimal. Every extra click loses a booking.

Confirmations and Reminders

Send an email confirmation immediately after booking with all the details: date, time, timezone, location or video link. Add a calendar invite (.ics file) as an attachment. Send a reminder 24 hours before and again 1 hour before. Missed reminders are the top complaint in scheduling products.

Cancellation and Rescheduling

Every confirmation email needs a one-click link to cancel or reschedule. When a booking is cancelled, the slot opens up immediately. When it's rescheduled, the old slot frees and the new slot is locked. Notify both parties on every change. This sounds obvious, but it's where a lot of early builds drop the ball on notification logic.

Event Types

Start with one-on-one meetings. Add group events (multiple attendees book the same slot) and one-to-many sessions (a host, many attendees, capped capacity) once the core is stable. Trying to build all three at once slows you down without meaningful benefit at the MVP stage.

Calendar Sync: Google, Outlook, and Apple

Calendar sync is the most technically demanding part of a scheduling app. You need to read existing events to block off busy times, and write new events when a booking is made. You need both directions working reliably, or your availability data is wrong.

Google Calendar API

Google uses OAuth 2.0 for authentication. Request the https://www.googleapis.com/auth/calendar scope for read/write access. Use the freebusy endpoint to check availability across multiple calendars in a single call. This is faster than fetching full event lists. When a booking is created, write the event via the Events API. Store the Google event ID so you can update or delete it later.

Microsoft Graph API

Outlook and Office 365 use Microsoft Graph. Authentication goes through Azure AD with MSAL. The calendar API is similar to Google's in concept but different in structure. Use the calendarView endpoint to fetch events in a time range, and getSchedule for free/busy queries across multiple users. This matters for enterprise team scheduling where you need to see everyone's calendar at once.

Apple CalDAV

Apple uses CalDAV, an open standard, rather than a proprietary REST API. It's more complex to implement correctly. You'll use libraries like tsdav (TypeScript) or ical.js to handle CalDAV requests. Apple iCloud requires app-specific passwords, which creates friction in the authorization flow. Many apps skip Apple sync in their MVP and add it only when users specifically request it.

Webhook vs. Polling

Google supports push notifications via webhooks: register a channel, and Google notifies your server when a calendar changes. This is far better than polling. Microsoft Graph also supports change notifications. Set up webhooks from day one. Polling every few minutes burns API quota and introduces lag that causes double-bookings during busy periods.

Timezone Handling Done Right

Timezone bugs in scheduling apps don't just break functionality. They break meetings, waste time, and lose clients. This is the one area where you cannot cut corners.

Developer building calendar sync and scheduling logic

The Golden Rule: Store UTC, Display Local

Every timestamp in your database should be stored as UTC. Never store local time. When you display a time to a user, convert from UTC to their local timezone at render time. This single rule prevents 90% of timezone bugs.

Use IANA Timezone IDs

Don't store timezone offsets like -05:00. Store IANA timezone IDs like America/New_York. Offsets change with DST. IANA IDs account for DST transitions automatically. Detect the user's IANA timezone with Intl.DateTimeFormat().resolvedOptions().timeZone in the browser, and let them override it in their profile.

Libraries That Get It Right

Use Luxon or date-fns-tz for all timezone-aware date operations. Both handle IANA timezones correctly and are actively maintained. Avoid Moment.js for new projects. For server-side work in Node.js, the same libraries apply. Never use raw JavaScript Date objects for scheduling math without a timezone-aware wrapper.

DST Edge Cases to Test

The clocks-forward transition in March creates a 23-hour day. The clocks-back transition in November creates a 25-hour day. Test these explicitly: book a meeting at 2 AM on a DST transition night, book a recurring series that crosses a DST boundary, and book across two users in different DST-observing regions. These tests will catch bugs before your users do.

Always Show the Timezone

Every time you display a booking time, show the timezone explicitly: "Tuesday, November 10 at 3:00 PM EST (8:00 PM GMT)." Ambiguous times cause missed meetings. Redundancy is a feature here.

Payment Integration for Paid Bookings

If you're building a scheduling app for paid consultations, coaching, or services, payment handling is a first-class feature. Getting it right means handling deposits, cancellation policies, and refunds without manual intervention.

Stripe Checkout for Paid Bookings

Stripe is the right default choice. Use Stripe Checkout sessions to collect payment during the booking flow. Create a Checkout session server-side with the booking details, redirect the user to Stripe's hosted page, and listen for the checkout.session.completed webhook to confirm the booking. Never confirm a booking before the payment webhook fires. Race conditions between your booking logic and payment confirmation are a real issue if you sequence this wrong.

Deposits vs. Full Payment

Some services take a deposit at booking and collect the remainder after the appointment. Stripe handles this with payment intents: authorize the full amount at booking, capture only the deposit portion, and capture the rest manually after the service is delivered. This requires more careful state management on your end but gives you full flexibility over billing timing.

Cancellation Policies

Define your policy in terms of hours before the appointment: full refund if cancelled 48+ hours before, 50% refund within 24 to 48 hours, no refund within 24 hours. Enforce this automatically in your cancellation handler. When a cancellation request comes in, calculate the hours remaining, apply the policy, and trigger a Stripe refund via the Refunds API for the appropriate amount. Log every refund decision with the policy version that was in effect at the time.

Stripe Connect for Marketplace Models

If you're building a marketplace where multiple service providers accept bookings through your platform, use Stripe Connect. Each provider has their own connected Stripe account. You collect payment, take your platform fee, and pass the remainder to the provider automatically via transfer. This handles tax reporting and payouts cleanly without you touching provider funds directly.

Team Scheduling and Round-Robin

Single-host scheduling is straightforward. Team scheduling, where multiple people share a booking page and assignments rotate, is where the logic gets genuinely complex.

Team collaborating on scheduling app product design and features

Availability Aggregation

For collective meetings (all team members must be available), take the intersection of each member's free slots. For round-robin (assign to whoever is next in rotation), union all members' available slots and show the full combined availability, then assign the booking to the appropriate team member after selection.

Round-Robin Distribution

Basic round-robin assigns bookings in order: Alice, Bob, Carol, Alice, Bob, Carol. Weighted round-robin lets you bias distribution: Alice gets 50% of bookings, Bob gets 30%, Carol gets 20%. Priority routing assigns to the first available team member in a defined order rather than rotating. Build these as pluggable strategies so you can swap them per event type without rewriting core logic.

Buffer Times

Buffer times prevent back-to-back meetings. A 15-minute buffer after each booking means a 1 PM meeting blocks availability from 1 PM to 2:15 PM for a 1-hour event. Implement this by expanding the busy window when querying availability: when you fetch free slots, treat each existing event as lasting event_duration + buffer_after minutes. You can also add a buffer before each event for preparation time.

Meeting Types and Limits

Let hosts set a daily or weekly cap on bookings. A consultant might want no more than 4 calls per day regardless of calendar availability. Enforce this at the slot-generation layer, not just at booking time. If you enforce it only at booking, two people can simultaneously select the same slot and one gets a confusing error. Generate slots with the cap already factored in, and use optimistic locking in your booking transaction to handle the rare true simultaneous booking.

Notifications and Reminders

Notifications are the most visible part of a scheduling app to end users. A missed reminder or a broken calendar invite erodes trust faster than almost any other failure.

Email Notifications

Send transactional emails through SendGrid, Postmark, or AWS SES. The events that always need an email: booking confirmed, booking cancelled, booking rescheduled, reminder 24 hours before, reminder 1 hour before. Use a templating system (React Email or MJML) to keep your HTML emails consistent and maintainable. Store each notification in your database with its sent status and timestamp. You'll need this for debugging and for "resend confirmation" features.

SMS Reminders via Twilio

SMS reminders have dramatically higher open rates than email. Twilio's Messaging API is the standard implementation path. Store opt-in consent explicitly before sending any SMS. A simple checkbox at booking time ("Send me a text reminder") covers your compliance obligations for most markets. Keep SMS messages short: "Reminder: your call with Acme is tomorrow at 2 PM EST. Reply STOP to unsubscribe." Twilio charges roughly $0.0075 per outbound SMS in the US.

Calendar Invites (ICS Files)

Attach an ICS file to every confirmation email. The ICS format is a plain-text standard that every calendar application understands. Use the ical-generator library in Node.js to generate ICS files programmatically. Include the event title, start time, end time, timezone, description, location or video conference link, and the organizer and attendee email addresses. When a booking is rescheduled or cancelled, send an updated ICS with the METHOD:CANCEL property set so calendars remove or update the event automatically.

Reminder Sequences

Build your reminder logic on a job queue (BullMQ or Temporal) rather than cron jobs. Scheduled jobs let you enqueue a "send reminder" job for a specific future timestamp when the booking is created, and cancel the job if the booking is cancelled or rescheduled. Cron-based reminder systems require you to query all upcoming bookings on every run, which creates unnecessary database load and logic complexity as your volume grows.

Development Cost and Architecture

Here's what it actually costs to build a scheduling app, and how to structure the technical architecture to avoid painful rewrites later.

Development Cost Ranges

  • MVP (8 to 12 weeks): $50K to $90K. Single-host scheduling, one calendar integration (Google), basic email notifications, Stripe payments. Enough to validate demand and sign your first paying customers.
  • Full-Featured App (16 to 22 weeks): $100K to $180K. All three calendar integrations, team scheduling with round-robin, SMS reminders, cancellation policies, admin dashboard, and a white-label booking page.
  • Calendly Competitor (24 to 36 weeks): $180K to $250K. Multi-org support, Stripe Connect for marketplace payouts, workflow automations, analytics, API access, and the engineering depth to handle scale.

Recommended Tech Stack

  • Backend: Node.js with TypeScript, Express or Fastify, PostgreSQL for bookings and availability data, Redis for caching free/busy results.
  • Frontend: Next.js with React, a date/time picker built on react-day-picker, Luxon for all timezone math.
  • Job Queue: BullMQ backed by Redis for reminder scheduling and async notification delivery.
  • Infrastructure: Vercel or Railway for the frontend and API, Supabase or Neon for managed PostgreSQL, Upstash for managed Redis.

Architecture Decisions That Matter Early

Multi-tenancy: design your database schema with an organization_id on every table from day one. Adding it later means a migration that touches every query. Event sourcing for bookings: store every state change (created, confirmed, rescheduled, cancelled) as an immutable event log rather than mutating a single row. This gives you audit trails, undo capability, and much easier debugging when something goes wrong.

The teams that build scheduling apps well treat time as a first-class concern in their data model, not an afterthought. If you want to talk through the right architecture for your specific use case, book a free strategy call and we'll scope it out together.

Need help building this?

Our team has launched 50+ products for startups and ambitious brands. Let's talk about your project.

scheduling appbooking app developmentCalendly alternativecalendar integrationappointment scheduling

Ready to build your product?

Book a free 15-minute strategy call. No pitch, just clarity on your next steps.

Get Started