Skip to content

Repository files navigation

punctual:

CI License: MIT

Scheduling that shows up on time.

Punctual is an open, edge-native scheduler — a full single-team alternative to Calendly that runs entirely on Cloudflare Workers. Booking pages render at the edge; self-hosting is one wrangler deploy into your own Cloudflare account, $0 on the free tier.

Status: v0.1.1 released and live. Pre-1.0, so interfaces may still change.

What it looks like

A booking page, rendered at the edge — no client-side spinner between the guest picking a day and seeing open times.

Punctual booking page: a day picker calendar next to a list of available half-hour slots, in the light theme

The pledge

The open-source version is complete for a single team — forever. No seat limits, no gated scheduling features, no "non-production use" clauses. MIT makes this promise irrevocable: what we ship can never be taken back.

Quick start

One command, if you have Node 20+ and a Cloudflare account:

npx punctual-sh init

It clones this repo, creates the D1/KV/R2/Queues resources, writes their ids into wrangler.toml, generates secrets, migrates and deploys. Or do the same by hand:

git clone https://github.com/CCCrafts/punctual.git
cd punctual && npm install
npx wrangler login

npx wrangler d1 create punctual          # put the id in wrangler.toml
npx wrangler kv namespace create CACHE   # put the id in wrangler.toml
npx wrangler r2 bucket create punctual-avatars   # put the name in wrangler.toml

openssl rand -base64 32 | npx wrangler secret put ENCRYPTION_KEY_V1
openssl rand -base64 32 | npx wrangler secret put SIGNING_KEY

npm run migrate
npm run deploy   # prints your Worker URL — put it in wrangler.toml's BASE_URL
npm run deploy   # again, so emailed links point at the real URL

Full walkthrough, including connecting Google and Microsoft calendars: docs/self-hosting.md.

What's here

  • Booking pages rendered and streamed from the edge
  • Google Calendar and Microsoft 365 sync, using your own OAuth app
  • Event types with buffers, notice windows, horizons, daily caps, custom questions
  • Teams — round-robin and collective scheduling with required and optional hosts, each on their own schedule; team admins manage members, event types and each member's availability, and can add or swap hosts on a booking after the fact
  • Calendar events that say who the meeting is with — every participant named with their company, on the hosts' calendars and in the guest's invitation
  • Branding — a company logo and per-event-type logos, as a circle or in their own proportions; social cards with the hosts' faces
  • Emails with .ics invites, reschedule and cancel links, 24 h and 1 h reminders
  • REST API, HMAC-signed webhooks, embed widget
  • A built-in MCP server — your calendar as a tool an AI agent can call

How it works

Three decisions shape everything else.

Double-booking is impossible at the storage layer, not by convention. Every booking writes one row per five-minute bucket per host into a slot_locks table whose primary key is (host_user_id, bucket_start), in the same D1 batch() as the booking itself. A conflicting bucket violates the constraint and the whole batch rolls back. Range overlap can't be expressed as a SQL constraint; discretised buckets can — that substitution is the design. Durable Objects serialise attempts and re-check external calendars, but they are the fast path, not the guarantee: if they misbehave the worst outcome is a 409.

All time is UTC internally; wall-clock exists only at the edges. Daylight saving is handled by explicit rules rather than by hope — a wall-clock time inside a spring-forward gap clamps forward, an ambiguous time during fall-back takes the first occurrence, and both rules apply uniformly to window starts and ends. The test matrix covers Kyiv, New York, Lord Howe (a 30-minute DST shift), Chatham (+12:45), Kolkata and the southern hemisphere, because whole-hour assumptions fail silently rather than loudly.

The engine takes everything through ports. Storage, calendars, email, crypto, cache, clock and queue all arrive as interfaces, so the same engine runs single-tenant for you and multi-tenant for a hosted service without a fork. There is deliberately no "limits" or "plan" interface anywhere in this repository — see the pledge.

Development

npm test              # 874 tests
npm run test:core     # pure domain only — no Workers runtime, runs in ~150ms
npm run typecheck
npm run dev

The domain layer has zero Cloudflare imports and runs under plain Node, which is what lets the timezone and slot-engine suites stay exhaustive and fast. Adapters, Durable Objects and HTTP are tested against the real Workers runtime with Miniflare.

License

MIT. A hosted version with managed OAuth apps, custom domains and zero setup is a separate commercial service running on this same engine.

About

Scheduling that shows up on time — an open, edge-native Calendly alternative on Cloudflare Workers. MIT, no seat limits, ever.

Topics

Resources

Contributing

Security policy

Stars

103 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages