Build it yourself

intermediate a weekend scheduling

Build your own booking page (a personal Calendly)

You will build a personal booking page: a public URL where guests pick from time slots computed from your weekly availability, minus what your Google Calendar already holds, then get an emailed confirmation with a video-call link. Along the way you run a complete OAuth handshake (the standard way your app gets permission to act on your calendar), render times correctly across time zones, and deploy the result to a real domain. Calendly keeps its customers because reliable calendar sync at scale, team routing, and a wide integration catalog are full-time work, and a missed or doubled meeting costs most people far more than $12 a month.

What you'll learn

  • Modeling availability as data: weekly windows, slot length, buffers, and subtracting busy time
  • Running a real OAuth flow end to end, including refresh tokens and redirect URIs
  • Timezone-safe slot rendering with Intl.DateTimeFormat
  • Sending transactional email over SMTP and signing cancel/reschedule links with HMAC
  • Deploying a single-process Node app backed by SQLite

Before you start

  • Node.js 22 and npm installed locally
  • A Google account whose Calendar you will book against
  • A Google Cloud project, created during the lesson; the free tier is enough
  • SMTP credentials for sending mail, such as a Gmail app password
  • A deployment target for the last step: any host that runs Node with persistent disk, plus a domain if you want a clean URL
  • Comfort running terminal commands and editing files

The build

DELEGATE

Hand the skeleton to your assistant as one fixed package: a Node server, a SQLite database (a single-file database engine), and an availability config file. You edit the config afterward to change your hours, which is the point: availability lives as data, not code. Test locally by booking through the page until the buffer rule visibly pushes the next slot back.

step prompt
Build the project skeleton for a personal booking app. Requirements:

- Single-process Node 22 server using the built-in http module, HTML returned as template strings, no frontend framework
- better-sqlite3 database at data/bookings.db with one table: bookings(id TEXT PRIMARY KEY, start_utc TEXT, end_utc TEXT, guest_name TEXT, guest_email TEXT, note TEXT)
- config/availability.json holds weekly windows (example: monday 10:00-17:00), slotMinutes: 30, bufferMinutes: 15, timezone: "America/New_York"
- GET /book renders slots for the next 14 days, computed from availability.json minus existing booking rows and their buffers, displayed in the visitor's timezone using Intl.DateTimeFormat
- POST /book validates name, email, note, rejects a start that collides with an existing booking or its buffer inside a transaction, inserts the row, returns a plain confirmation page
- dotenv loads .env, commit a .env.example containing PORT only
- Out of scope: user accounts, CSS frameworks, calendar sync, emails
- Prove it works: curl the page, then attempt two bookings whose buffers overlap, the second must fail
BY HAND

Sign in to console.cloud.google.com, create a project, and enable the Google Calendar API. Configure the OAuth consent screen as External, add yourself as a test user, then create an OAuth client ID of type Web application with the redirect URI http://localhost:3000/oauth/callback. Copy the client ID and secret into .env. This is dashboard clicking no agent can do for you, and the upstream entry warns it alone can eat an hour.

WE

Drive the assistant request by request here rather than handing the whole thing over. Your app exchanges your approval for a refresh token (a long-lived credential that lets it read and write your calendar without asking again), blocks slots for busy events, and creates events when guests book. Watch each call happen so you learn what a failed redirect URI looks like versus an expired token.

step prompt
Add two-way Google Calendar sync to the booking app from step 1. Requirements:

- Use the googleapis npm package · read GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_REDIRECT_URI from .env
- GET /oauth/start redirects to Google's consent screen with the calendar.events scope and access_type=offline so we receive a refresh token, store it at data/tokens.json, gitignored
- Before rendering /book, call freebusy for the next 14 days and subtract busy periods from available slots alongside booked rows
- On a successful POST /book, insert a calendar event with the guest as attendee and conferenceDataVersion=1 to attach a Meet link, save the returned event id on the booking row
- If the token refresh fails, render a plain error page linking to /oauth/start instead of crashing
- Append a README section titled "OAuth reality check" warning that console setup can take an hour · name which Google error messages indicate a wrong redirect URI
- Out of scope: Outlook sync, background polling, retry queues
WE

Email goes out through SMTP (the plain protocol mail servers use to hand messages to each other) to both sides of every booking. Guests also get cancel and reschedule links protected by an HMAC signature, a checksum that proves the link was created by someone holding your secret key, so nobody can cancel a stranger's meeting by guessing IDs. Send yourself a booking first and check the spam folder before assuming anything is broken.

step prompt
Add email confirmations with signed cancel and reschedule links to the booking app. Requirements:

- Use nodemailer · SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS live in .env, a Gmail app password works for testing
- After a booking commits, email both sides: the host gets guest name, email, and note, the guest gets the time in their timezone plus the Meet link fetched from the calendar event saved in step 3
- Sign links with CANCEL_SECRET from .env using HMAC SHA-256 over bookingId plus start_utc, shaped /cancel/<id>?token=... and /reschedule/<id>?token=...
- GET /cancel verifies the token, deletes the calendar event by its saved id, removes the booking row, emails the guest once more
- GET /reschedule verifies the token and reuses the same slot picker as /book, then moves both the row and the calendar event
- Note inline in the README that dev SMTP frequently lands in spam, check spam before debugging
- Out of scope: HTML email templates, attachments, bulk sending
WE

Put the app on a host so guests can reach it from anywhere, then walk the full journey again on the live URL: book, receive both emails, see the event appear in Google Calendar. Updating the OAuth redirect URI to the new domain is the step people forget, so do it deliberately. DNS changes at your registrar remain your clicks.

step prompt
Deploy the finished booking app to a public URL. Requirements:

- Choose a host that runs a single Node process and offers persistent disk for data/bookings.db, a small VPS qualifies
- Move every .env value into the host's environment variable settings, never commit real secrets
- Add a start script and an engines.node field to package.json matching the local Node version
- Update the Google OAuth redirect URI to https://YOUR-DOMAIN/oauth/callback and rerun the consent flow once
- Point the domain's DNS records at the host yourself in your registrar dashboard
- Smoke test end to end after deploy: book a slot, confirm both emails arrive, verify the event exists in Google Calendar with a Meet link
- Out of scope: certificate management if the host terminates HTTPS, monitoring, automated backups

What you won't get

  • This build covers one person's calendar; distributing meetings across a team with round-robin and routing is a separate system.
  • Reminders travel by email only; text-message reminders require a paid SMS provider layered on top.
  • The integrations available to you are the ones you wire yourself; there is no directory of third-party connections.
  • When Google or Microsoft changes consent screens or token behavior, updating the sync becomes your maintenance work.

Why people still pay — and what that teaches you

integrations: Calendly's advantage is that calendar sync works across Google and Microsoft for millions of accounts, which means permanent upkeep against other companies' changing APIs. Builders learn that an integration is not a feature you finish, it is a relationship you maintain.

execution-polish: Guests judge scheduling tools by the failures they never see prevented: double-books, wrong time zones, invitations caught in spam. Calendly wins by making those edge cases boring. Builders learn the booking flow's reliability is the product, and polish means handling concurrent requests and clock edges, not decoration.

Stretch goals

  • Add a second event type, like 60-minute deep-dive sessions, purely by extending availability.json and the picker.
  • Sync an Outlook calendar alongside Google to feel directly why multi-provider support is its own sustained effort.
  • Collect payment at booking time with Stripe Checkout and compare your fee math against competitors' commissions.

About Calendly

Calendly costs $12/month. They pay because double-booking or missed meeting invites is costly.

Sources & further reading

Finished alternatives (if you'd rather not build)

  • Cal.com Free — Unlimited solo event types, calendars and bookings, with payments and notifications; team routing costs money.
  • Google Calendar appointment schedules — One bare appointment page inside the calendar you already use; reminders, payments and polish are paid.
  • Koalendar Free — Unlimited booking pages and two calendar connections; payments, forms, reminders and team routing are paid.
  • TidyCal Free — Unlimited one-person booking types and paid bookings on one calendar; group meetings and fancy emails cost extra.
  • YouCanBookMe Free — One booking page, but it keeps forms, payments, confirmations, polls and the calendar-overlay trick.
  • zcal Free — Unlimited links and calendars, custom questions, email reminders and Stripe payments; teams are paid and zcal takes 3%.
  • Zoho Bookings Free — A real one-person booking desk with forms, reminders and two-way sync; staff and payment plumbing cost extra.

Keep building

New lessons and honest build notes, by email. No spam, one-click out.

Signups open when the site goes live.