LS / MTLLuke Sidney
Lighting the room0%
10th floor · Quartier Latin · golden hour
Montreal, QC·lukesidney.me·Fresh 2 · DenoNo cookies · No forms
Plate01

Front page.What it is

Live

skedual

Booking that asks its questions before it shows a calendar.

Every button on clevrstart.com books through it.

Riso illustration: a clipboard questionnaire in front of a wall calendar with only three open days, an envelope and a torn ticket stub beside a desk bell.
Plate02

Set in type.The stack

What it runs on

A small Next.js app on the shared database, with Google Calendar and Stripe at its edges.

Interface

  • Next.js16.2booking pages and forms
  • React19.2

Data

  • Neon Postgresits own schema
  • Drizzle0.45queries
  • Payload3.82form answers, in the shared CMS
  • Better Auththe owner's sign-in

Edges

  • Google Calendar APIbusy times
  • Stripe20optional deposits
  • date-fns-tztime zones
  • Zod3.23every input checked

Host

  • Verceldeployed from the monorepo
Plate03

On the press.How it works

5 steps, in order

What a visitor goes through, and what the app checks at each step.

  1. Answer first

    A short form sorts the request: what it is, the budget band, the timeline. The free-text brief is read by AI before the call.

    The first step of the booking form: a work email field before any calendar.
  2. See only real free time

    Slots come from the owner's hours, minus existing bookings, minus Google Calendar busy times.

  3. Confirm

    The email carries a calendar invite and a cancel link.

  4. Cancel without an account

    The link proves itself with a signature, so the guest never signs up.

  5. The answers travel

    The answers are saved in the shared CMS, where my quote and content apps read them.

Plate04

Pulled proof.The code

A cancel link that proves itself

apps/skedual/src/lib/cancelToken.ts · lines 23–37real source
/** Scoped so a token minted here can never be replayed against another feature. */function sign(meetingId: string, party: CancelParty) { return createHmac("sha256", secret()) .update(`cancel:${meetingId}:${party}`) .digest("base64url")}export function cancelToken(meetingId: string, party: CancelParty) { return sign(meetingId, party)}export function cancelUrl(meetingId: string, party: CancelParty) { const base = process.env.NEXT_PUBLIC_APP_URL ?? "" return `${base}/book/cancel/${meetingId}?t=${cancelToken(meetingId, party)}`}

No token table and no expiry sweep: the signed meeting id is the record. The host's link and the guest's are different tokens.

Plate05

The reel.The walkthrough

Watch it run

Software demo coming soonAbout 90 seconds: the form, the calendar, the confirmation email, the one-click cancel.
Plate06

Corrections.What it fixes

The problems it solves

  • Calls booked with nothing known in advance.

    The questions come first; the brief is read before the call.

  • Double bookings against Google Calendar.

    Busy times are read from Google before a slot shows.

  • Guests sent to someone else's domain and brand.

    The booking page wears each site's own theme.

  • Cancelling needs an email thread.

    Every confirmation carries a signed cancel link.

Plate07

Editor's letter.Why I built it

Why it exists

“Nothing is known before the call today.” That line from my own spec is why skedual exists.

I wanted one call to take the questions, present, close and sign. A Calendly embed cannot do that without a second form bolted in front, so the form and the calendar became one app. It started in December 2025 as a Calendly clone from a tutorial.

Riso illustration: questions answered on paper before a calendar opens.
Plate08

Against the rack.Better than average

Against the usual tools

Against Calendly and Cal.com. Their side was read from each vendor's own pages on 2026-09-27.

WhatThe market averageThis app
Questions firstRouting forms, on paid plans.Built in, on every booking, before the calendar.vendor's page
Where answers goThe scheduler's database, pushed to a CRM or webhook if you wire it.The same CMS my quote and content apps read.vendor's page
Cancel linkA link to the vendor's page; your own domain only on Cal.com's top plan.A signed link on your own domain.vendor's page
LookThe vendor's page with your logo and colours.Each site's own theme.vendor's page
Plate09

The imprint.How long it took

67 commits in 297 days

commits
67
days with a commit
23
days, first commit to today
297
2025-12-05commits per week · busiest: 182026-09-27
  1. First commit: a Calendly clone
  2. Moved into the monorepo, history and all
  3. Latest commit

Measured: git log, my own commits on the app and its imported history, 2026-09-27. The tutorial author's three commits are excluded.

Plate10

Colophon.Where it sits in the ledger

Remote▲ ▼ section · ◀ ▶ project · OK opens