File
SMF·ZENCAL
Received on
31.07.2026
Reviewed on
28.09.2026
Exhibits annexed
3
Questions
4

SMF·ZENCAL

Can a prompt replace Zencal?

Scheduling — booking links and calendar automation

Almost Verdict recorded on 28.09.2026 · Verified on 31.07.2026
Price
$9.50/moSource: zencal.io · Checked on July 31, 2026
Per year
$114
Build time
A week
Category
Scheduling
Votes
0 votes
YesAlmost (checked)Not yet

Exhibit tracking slip

Exhibit A The prompt
Exhibit B What you lose
Exhibit C Why people still pay: calendar integrations and reliability
Exhibit Q Questions

Verdict

Booking pages are well covered in this catalogue. Zencal's distinguishing feature is paid bookings, and combining a reservation with a payment is genuinely fiddly: the slot has to be held while the visitor is in checkout, released cleanly if they abandon, and confirmed only when the webhook arrives — with all three paths correct under two people booking the same slot at once. That race is where a naive implementation either double-books or loses paid slots, and getting it right takes days.

Exhibit B — What you lose

Exhibit A — The prompt

Received on31.07.2026
Build a booking system where a meeting type can require payment before it is confirmed.

Calendar: connect one Google Calendar over OAuth with encrypted refresh tokens. Availability from working hours minus busy events, with buffers, minimum notice and a booking horizon. All arithmetic in UTC, rendered in the visitor's time zone with the zone named on screen.

Meeting types: name, duration, location, buffers, questions asked at booking, and an optional price with a currency.

The paid booking flow, which is the point of this build. A reservation moves through explicit states and every transition must be handled:

1. **Held** — created when the visitor chooses a slot and proceeds to checkout, with an expiry a few minutes out. A held slot is unavailable to everyone else. Create it in a transaction with a uniqueness constraint on the meeting type and start time, so two simultaneous visitors cannot both hold it; the loser is told the slot just went and is offered the next one.
2. **Pending payment** — a Stripe Checkout session created server-side against the held reservation, with the reservation id in the metadata and an idempotency key.
3. **Confirmed** — on `checkout.session.completed`, verified by signature and checked for prior processing, the reservation is confirmed, the calendar event created, and confirmation emails sent. Never confirm on the success redirect; the visitor may close the tab and the redirect may never fire.
4. **Expired** — a scheduled job releases holds past their expiry. Before releasing, re-check Stripe for a completed session against that reservation, because a webhook can arrive late and releasing a paid slot is the worst outcome in the system.

Also handle: a payment completed for a reservation that already expired and was released — refund automatically and email the visitor an explanation rather than silently keeping the money; and `checkout.session.expired`, which releases the hold immediately rather than waiting for the timer.

Write a test for each of those six paths. This is the whole reason the entry exists and it is where a homegrown booking-with-payment system goes wrong.

Refunds and cancellation: a cancellation policy per meeting type (a window within which a refund is automatic), a signed cancel link in the confirmation email needing no account, and refunds issued through Stripe with the calendar event deleted and the slot returned to availability.

Unpaid meeting types use the same reservation flow without the payment states, so there is one code path rather than two.

Also build: reminder emails at a configurable interval with the send recorded so a restart cannot double-send, an admin booking list showing state and payment status, and an audit log of every state transition.

Out of scope: Microsoft and Apple calendars, team round-robin, routing forms, CRM integration, and any hosted multi-tenant deployment.

Opening prefills the prompt — press enter to run it.

Exhibit B — What you lose

  • B.1 Outlook and Apple calendar accounts
  • B.2 team scheduling with round-robin and shared availability
  • B.3 hosted uptime for a page that takes money
  • B.4 the maturity around time-zone edge cases

Prior art

Exhibit C — Why people still pay: calendar integrations and reliability

Because a booking page that takes payment is a shop, and a shop that double-books or charges without confirming costs more in one bad week than years of subscription.

Questions

Can I import my Zencal setup?

Booking pages and event types have no structured export, so they are re-created — half an hour for a handful. Past bookings export as CSV for records. Anything already booked and paid should be allowed to run out on the old system rather than migrated.

Why so much attention to the reservation states?

Because every failure in a booking-with-payment system lives in the gaps between them. Confirming on the redirect loses bookings when a tab closes; releasing a hold without re-checking Stripe cancels slots people paid for; not handling the expired-session event leaves slots blocked. Each is invisible until it happens to a customer.

What does it cost to run?

Stripe's transaction fee, a small VPS with PostgreSQL at five to ten dollars a month, and a transactional email provider that is free at this volume. Below a few bookings a month the subscription is easier; above that the arithmetic favours building.

What is the one thing that does not survive the rebuild?

Not being the one on call. A booking page that takes payment has to work at any hour, and when it does not the failure is a customer who paid and has no meeting. That is a different weight of responsibility from a free scheduling link.

Receipt

Already built this yourself?