All notable changes are listed here. The format follows Keep a Changelog and versions follow SemVer.
0.20.1 - 2026-10-17
Changed
- Side panes open and close at once. Bookings, tasks, files, conversations and timeline entries used to wait for the server to re-render the whole page before the pane moved (and React held a pane's content back for about 300 ms after it arrived). A click now opens the pane straight away with a skeleton and fetches its content in the background; closing it (Escape, the backdrop, the close button) needs no request. Previous / next (and J / K) in the booking pane work the same way. One shared
PaneHost and a click handler for pane links (components/pane.tsx) replace the five pane wrappers. Measured on a local build: the pane shows in about 15 ms instead of about 350 ms, with its content after about 50 ms
- Opening a conversation renders the page once instead of three times (it is marked read while the pane renders), 16 database statements instead of 66; the unread count and members' profiles are read once per request
- Lighter animations: no full-screen backdrop blur behind panes and dialogs (a plain dim), panes slide on transform and opacity only, slightly faster in and faster out, and not at all with reduced motion
- Ticking a task done shows it done the moment it is pressed; the file viewer says it is loading while the file streams
- Production functions run in
cle1, next to the database (us-east-2), instead of iad1
0.20.0 - 2026-10-17
Added
- Notes in the call. On Bookly video, members in the call get a private Notes panel behind the
meeting_notes flag (Pro and up, meetingNotes): notes save as you type, each keeps the moment it was written, and Mark moment (button, call controls or ⌘/Ctrl+Shift+M) drops a flagged timestamp. The recap reads the notes together with the transcript (quoted as your notes, commitments and marked moments weigh in for action items, notes win over the transcript), and every action item and the task it becomes says whether it came from your notes, the transcript or both; a call that was not transcribed gets a recap from the notes alone. The notes stay editable on the booking (Your notes from the call) and are posted to the contact's timeline as a note after the meeting (edits update it). Migration 0037 (meeting_notes)
- The landing page shows notes in the call once the flag is on for everyone
Fixed
- On a dedicated meeting host (
MEET_URL), the call page's API calls were rewritten into /meet/… and failed, so live transcript lines from the meeting page never reached Bookly there (Daily's stored transcript still did). /api/ paths now keep their path on the meeting host, and the live-transcript endpoint finds the room's booking without a workspace host
Changed
- The e2e server runs with
TELEMETRY=off
0.19.0 - 2026-10-17
Added
- Messages. A Messages page (Admin → Messages) behind the
messages flag with a Beta badge. One conversation per client, seen by every member: their emails in and out (everything that was an email on their timeline, automatic ones included), what they wrote when booking, and internal notes with @-mentions the client never sees (Team). Write to the client from the conversation: an email from you with the signed Reply-To, so their answer comes back to the same conversation; leave the subject empty to answer their latest email, or give one to start a new thread; files from the drive go with it. Team threads (Team): a subject, the people in it and optionally the contact it is about; only participants see it, @-mentions bring people in, Add people adds more. Inbox, Unread, Clients, Team and Closed; search; keyset "Show more"; Close thread / Reopen thread (a client writing reopens). Gates guestMessages (Pro+) and teamMessages (Team+); attachments follow attachments
- Unread counts: per member read marks; automatic emails never make a conversation unread. The count shows in the sidebar and on a new Messages Home card, and updates live over the notifications stream (
event: messages), which also refreshes an open Messages page
- Notifications Team message (
message.posted) and Mentioned in Messages (message.mentioned)
- Open in Messages on a contact's page; the landing page shows Messages once the flag is on for everyone
- Contacts with emails on their timeline start with a conversation (migration 0036 backfills them, nothing unread). Conversations, notes and team threads are in the workspace export
Changed
- The @-mention textarea of task comments is a shared component (
MentionTextarea), used by notes and team messages too
- Files attached to a message are visible to everyone in that conversation; uploads from a message without a contact go to My files
- The notifications docs list every kind (guest replied, task mentions and the two message kinds were missing)
0.18.3 - 2026-10-17
Security
- Private files could be opened by anyone holding a copied link.
/files/<id> answered a signed-in member with a redirect to a presigned storage URL valid for 5 minutes; "Open in new tab" showed that URL, and anyone given it could open the file without signing in until it expired. Private files are now streamed by the app itself, which checks the session, the workspace and the drive's visibility on every request (private, no-store, no-referrer, noindex), so no bucket URL reaches a browser. Public files (photos, logos, backgrounds) are unchanged; signed 7-day links in emails still open the one file they were sent for
- The storage endpoint is no longer allowed in
frame-src; /files/<id> responses carry their own minimal headers so the browser's PDF viewer works
0.18.2 - 2026-10-17
Added
- File viewer. View (in a file's menu and pane) opens images and PDFs in a large dialog with Open in new tab and Download; other types are download only, with View disabled and the reason shown. File names on tasks, on the contact page's Files card and on the timeline open the viewer (or download)
Fixed
- File links opened "Not found" on a workspace with its own domain:
/files/<id> was sent on to the custom domain, where the admin is signed out. It now stays on the host it was opened from
- PDFs did not show in the file pane, and uploads to S3/R2 from the browser could be blocked: the Content-Security-Policy now allows the storage endpoint (
connect-src, frame-src) and the app's own frames
- A guest's reply in the timeline's side pane said "Attachments (not kept)" even when the attachments were kept; it now lists them like the timeline does, as links to the files, or with the reason one was not kept (including files deleted since)
0.18.1 - 2026-10-17
Fixed
- The same desktop notification kept appearing every minute or so, even after it was closed and read: the live notification stream resumed from a timestamp in milliseconds while the database keeps microseconds, so the newest notification counted as new on every reconnect and an open, hidden tab showed it again. The stream now compares at millisecond precision, and the bell never shows a notification on the desktop twice (nor one already read or older than a few minutes)
- The task list's "Show more" could repeat the last task of the previous page for the same reason
Changed
- Desktop notifications use a purpose-made Bookly icon (192 px) and a monochrome badge for Android's status bar; the admin app manifest gains the 192 px icon. The big icon on a computer stays the browser's unless Bookly is installed as an app (docs/notifications.md)
0.18.0 - 2026-10-17
Added
- Files. A drive in the sidebar (Admin → Files): My files (yours alone, nested folders), Team folders (Team plan), a folder per contact (what they sent and what is ours), Recent, search, and a 30-day Trash with restore. Upload several at once straight to storage (quota checked first), new folder, rename, move, "Belongs to…" a contact, delete; a side pane previews images and PDFs (everything else downloads) and shows where a file came from, its contact and its tasks. Keyset "Show more" everywhere. Behind the
files flag with a Beta badge; Pro and up (attachments), Team folders on Team (sharedFolders)
- Guests' attachments are kept in their contact's folder, linked to the email they came with, and shown as links on the timeline (inline signature images are skipped; files over 25 MB, a full quota or a plan without Files keep the name with the reason)
- Files on tasks: upload to a task (it goes into the drive, the task's contact folder when it has one, and the task) or link files from the drive; a file linked to a contact, or to a task of a contact, shows on the contact's timeline with links to the file and the task
- Send files: proposals, payment requests, follow-ups and timeline replies can attach files from the drive or upload them; up to 10 MB go as attachments, the rest as 7-day links; sent files show on the email's timeline entry
- A contact's page has a Files card with their latest files, upload and a link to their folder; the landing page shows the drive once the flag is on for everyone
Changed
/files/<id> follows the drive's visibility (someone's My files stay theirs), serves trashed files as gone, opens only images and PDFs inline (?download=1 forces a download), and accepts signed 7-day links (?t=) for files sent by email
0.17.1 - 2026-10-17
Added
- Settings → Emails → Guest replies: where replies to the workspace's emails go (each contact's timeline, or the sender's own address, with the reason: switched off, needs Pro, no inbound domain on this server, not enabled for the workspace yet), and a switch for owners and admins (logged as
settings.guest_replies.enabled / disabled)
Changed
- A reply to an email sent with a timeline address is always filed and forwarded, even after guest replies are switched off or the plan changes, so no reply is lost
Fixed
- The box shown in some guest replies (Apple Mail's placeholder for an inline attachment, and zero-width marks) is removed from the reply text
- The unread count on the notification bell has enough contrast (dark text on the brand colour)
0.17.0 - 2026-10-17
Added
- Contact timeline v2. The timeline is grouped by day under date dividers, newest first, with meetings linking to their booking, recap and transcript, sent emails showing their content in an accordion, and an entry side pane (
?entry=<id>: a meeting's full booking, or an email's thread with its quoted text). It is keyset-paginated from the start: the latest window of about 30 entries (never splitting a day), Show earlier for the next, then the following windows load as you scroll; the index is contact_events (contact_id, created_at, id) (migration 0034). Behind the timeline_v2 flag with a Beta badge; the landing page shows it once the flag is on for everyone
- Guest replies on the timeline (Pro and up,
inboundEmail): every email to a contact carries a signed Reply-To on INBOUND_EMAIL_DOMAIN; Resend's email.received webhook (/api/webhooks/resend, Svix-verified) puts the reply under the email it answers with the quoted history cut, clears the contact's follow-up date and notifies the sender ("Guest replied", whose email channel forwards the reply). Hosts answer from the timeline and the thread continues in the guest's mail client. Auto-replies are ignored and redeliveries are no-ops. Self-hosting: set INBOUND_EMAIL_DOMAIN, RESEND_WEBHOOK_SECRET and an MX record for the subdomain
Changed
- Emails to contacts are logged with their subject and text, and every booking confirmation, reminder and follow-up becomes a thread a reply can join
0.16.0 - 2026-10-16
Added
- Task board. Tasks get their own page (Admin → Tasks): a board with one column per status and drag and drop with the mouse, a finger or the keyboard (Space to pick up, arrows to move between and within columns, Space to drop; announced to screen readers), and a list view (soonest due first, keyset "Show more"). Statuses are the workspace's own flow: owners and admins add, rename, colour, reorder and archive them, mark which count as done and which one is the default, and set WIP limits the column header warns about; every workspace starts with To do → In progress → Done and existing tasks are placed in it. A side pane (
?task=<id>) edits title, notes, status, due date and assignee, links back to the booking and contact, and holds comments. Filters (assignee, due, status, contact, event type) live in the URL; views can be saved, for yourself or the whole workspace. On Team, tasks can be given to teammates and teammates @-mentioned in comments, both with notifications ("Task assigned to you", "Mentioned in a task"). Plans: the list with the default statuses on Free, the board, custom statuses and saved views from Pro (taskBoard), assignment, mentions and shared views on Team (taskAssignment); self-hosted has everything. Home's tasks card opens "My tasks". Behind the task_board flag with a Beta badge; the landing page shows the board once the flag is on for everyone
Changed
- "Mark done" moves a task to the workspace's first done status (and "Reopen" back to the default one);
GET /api/v1/tasks returns each task's statusId, and the workspace export includes task statuses, comments and saved views
0.15.0 - 2026-10-16
Added
- In-app and desktop notifications. A bell in the admin header with the unread count and a panel of your notifications (open one to go to what it is about, mark all read, show earlier, keyset-paginated), and a Notifications card on Home. Bookly tells you about new bookings, booking requests, cancellations, a guest joining the Bookly video room, meetings starting in an hour, recaps ready for review, payments received (bookings and payment requests) and tasks assigned to you or overdue. Notifications arrive live over Server-Sent Events while a tab is open (the notifications table is the fan-out: no Redis, the same on serverless and self-hosted), with polling as the fallback. Desktop notifications use Web Push with a service worker scoped to
/admin, so they arrive with every Bookly tab closed, and the admin installs as an app (/admin.webmanifest); without Web Push keys an open tab shows them itself. Each member chooses per kind: in Bookly, desktop, email; the email column also governs the host copies of booking, cancellation, reminder and recap emails. Browsers with notifications are listed and removable. Notifications are kept 90 days. Behind the notifications flag with a Beta badge; the landing page shows the feature once the flag is on for everyone. Self-hosting: set VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, VAPID_SUBJECT for push (npx web-push generate-vapid-keys)
- Paid payment requests are recorded on the contact's timeline ("Paid $450.00") and in the activity log (
payment_request.paid)
Fixed
- Text and Slack pings looked up the host's settings without the workspace, so a member of several workspaces could get another workspace's channel settings; they are now read for the workspace the event happened in
0.14.0 - 2026-10-16
Added
- Bookings list and calendar. The bookings page has a list and a calendar (day, week, month; own component, keyboard navigable, in the host's timezone), and remembers which one you use. Clicking a booking opens it in a side pane over a blurred backdrop, deep-linkable with
?booking=<id>, with previous and next (J / K) through the bookings in view and the full booking detail and actions inside. Bookings are tinted with their event type's colour (a dot in the list, blocks in the calendar, a legend with counts), group sessions are one block with their seats, and owners and admins get a host filter. The list is keyset-paginated ("Show more", 50 at a time). Behind the bookings_calendar flag with a Beta badge; the landing page shows the calendar in the tour once the flag is on for everyone
0.13.0 - 2026-10-16
Added
- Home dashboard. Home opens with summary cards, each a link to its page: this week's meetings with the next three, new bookings and cancellations since your last visit, what needs attention (requests, recaps to review, people to follow up), your tasks (open, overdue, due today, plus the team's count for owners and admins), contacts added this week and follow-ups due, and, for owners and admins, payments received this month per currency (or a prompt to connect Stripe). Members see their own meetings. Each card streams in on its own with a skeleton of its size and its own empty state; the Today list and the action sections keep their place below. Behind the
home_dashboard flag, with a Beta badge while it is in beta; the landing page's Home mock shows the cards once the flag is on for everyone
0.12.5 - 2026-10-15
Fixed
- Dropdowns inside WebMCP forms (booking questions with a fixed list of answers, the phone country) are described from the first render: the form gets a server-rendered native
<select> with the name, the options and the description, kept in step with the visible dropdown, and Base UI's own input is detached from the form. Chrome reads a declarative tool when the form first renders, so the description added after hydration in 0.12.4 came too late on the live booking page. Agents now get those questions as an enum of the allowed answers
0.12.4 - 2026-10-15
Fixed
- WebMCP schemas on the booking page are clean under Lighthouse with WebMCP enabled: the "how to meet" choice is described on its fieldset (how Chrome describes radio groups), dropdowns describe Base UI's own named input instead of adding an unnamed one, and the page-fixed hidden fields (host, event type, slot, timezone) carry no description, so they are not parameters an agent could change
0.12.3 - 2026-10-15
Fixed
- The ARD manifest's
updatedAt is a full RFC 3339 date-time (2026-10-02T00:00:00Z) as the ARD schema requires; the bare date failed Lighthouse's ard-schema audit
0.12.2 - 2026-10-15
Added
- Ready for AI agents. The marketing host publishes an Agentic Resource Discovery manifest at
/.well-known/ard.json (advertised with <link rel="ard">; /.well-known/ai-catalog.json returns the same for older consumers) pointing at an Agent Skill, /.well-known/agent-skills/scheduling.md, that explains how to read a host's event types and availability and book, cancel or reschedule. Every public form (booking, waitlist, routing, manage-booking, sign-up, sign-in, sign-out) carries WebMCP declarative tool annotations, and the marketing pages register get_pricing_plans and search_documentation tools in browsers that expose navigator.modelContext. Lighthouse's Agentic Browsing audits pass
0.12.1 - 2026-10-12
Added
- Uploads. Profile photos and workspace logos can be uploaded instead of linked (behind the
file_uploads flag): the image is resized once on the server (512×512 photos, logos up to 1024 px with transparency kept, metadata stripped), stored through a new storage layer (local directory for self-hosting, any S3-compatible bucket such as Cloudflare R2 in the cloud) and served at /files/<id> with a year-long cache. Every file is recorded in a files table that drives per-plan storage quotas (1 GB on Free, 20 GB on Pro, 20 GB per seat on Team, shown on Billing), cleanup on workspace deletion and the activity log. The primitive for large files is in place too: POST /api/uploads reserves a file and returns a presigned PUT (or the app's own route on the local driver), /api/uploads/<id>/complete confirms it, private files come back as short-lived presigned links, and unconfirmed uploads are swept after a day
- Docker: the compose stack mounts an
uploads-data volume at /app/data/uploads; STORAGE_DRIVER, STORAGE_LOCAL_DIR and the S3_* variables are in .env.example
0.12.0 - 2026-10-10
Added
- Activity log. Every change in a workspace is recorded with who did it (member, guest, API key, integration or Bookly), what changed field by field, the time, request id and client address, including sign-ins. Owners and admins read it under Settings → Data → Activity with filters and a "Show earlier" button (keyset-paginated, so a long history costs nothing extra); Pro and Team download it as CSV. Retention follows the plan: 90 days on Free, a year on Pro, two years on Team, unlimited self-hosted. A test enumerates every server action and fails when one neither audits nor is marked read-only
- Feature flags and beta cohorts. Features can go to a closed beta before everyone: flags are off, beta or on; beta cohorts are named groups of workspaces; a workspace can be granted or denied a flag with an expiry. Operators manage them under Console → Flags and on each workspace's console page; hosts can opt out of betas under Settings → General and see a "Beta" badge on pages that are still in beta
DESIGN.md documents the button scale, pointer rule, loading states and section navigation
Changed
- "Inbox" is now Home. The admin's first page keeps its address (
/admin) and content; the label, title, docs and marketing site say Home. "Inbox" is reserved for email
- One button scale across the app and the marketing site: 40 px by default (up from 32), 44 px for the primary call to action, 32 px inside tables and chips, heavier text and consistent icon spacing. Inputs and selects grew to the same 40 px so forms line up. The last hand-rolled buttons and inputs on the console pages use the shared components
- Every link and clickable control shows a pointer on hover, and every disabled control shows a not-allowed cursor, through one global rule instead of per-element classes
- Every field matches the button beside it: native text inputs and selects are 40 px through a base rule, and the date, number, colour, handle and timezone pickers, the availability rows, the contact outreach and recap forms, the bookings "reason" box and the task box follow. Icon buttons that belong to a row of fields (remove a location or question, move it, remove a time range) are full height too
0.11.3 - 2026-10-10
Changed
- The event type editor is grouped by topic: Basics, Time, Where, People, Booking rules, Payment, and Reminders & follow-up, each with a short intro. A jump bar sticks under the header and highlights the section in view, and the Save and Delete bar sticks to the bottom, so a long form reads as a few short ones
- Settings tabs wrap onto a second line on narrow screens instead of scrolling sideways
0.11.2 - 2026-10-10
Fixed
- The WhatsApp webhook logs failed deliveries as a JSON string, so nothing in Meta's payload can forge extra log lines (CodeQL log-injection finding)
- Two transitive build-time packages are pinned to their patched releases through pnpm overrides:
source-map-js 1.2.2 (Dependabot #4) and postcss-selector-parser 7.1.6 (Dependabot #3, pulled by the Tailwind typography plugin)
shadcn is a devDependency: it is a CLI run through pnpm dlx, not something the app imports, so it and its tooling tree no longer sit in the runtime dependency list
0.11.1 - 2026-10-10
Changed
- Every button that reaches the server shows it: submit buttons spin and disable while their form is in flight, click actions (connect, remove, send, toggle) do the same, and actions with a global effect (signing in or out, switching workspace, creating or deleting an account or workspace, accepting an invitation) cover the page until they land
- Username and workspace-address fields show the availability check inside the field: a spinner and "Checking…" while the server is asked, a tick and "Available" when free, a cross when taken. A value that is already yours shows nothing
- Settings tabs: the tab strip now has room below it, and the selected tab is the page's title instead of repeating it as a heading
Removed
- The Booking page's note about where passwords and passkeys moved; there was nobody to tell yet
0.11.0 - 2026-10-10
Changed
- The admin is regrouped by who a page is for. Account (the menu under your email in the header,
/admin/account) holds sign-in methods, passkeys, password and account deletion, which used to sit on the Booking page. The sidebar now reads Scheduling (your booking page, event types, availability, bookings, contacts, routing forms), Personal (your calendars, conferencing and notifications) and Workspace (settings, team, domains, billing), and only owners and admins see Workspace
- Settings is one page per concern with a tab strip: General (name, locale, timezone, block list, postal address, telemetry, and the workspace-wide "attendee joined" switch that used to sit on Notifications), Branding, Emails (guest emails plus the proposal, payment-request and follow-up templates), Integrations (CRM sync, API keys and webhooks, which moved from
/admin/api; the old address redirects), Payments (cloud) and Data (export, delete). Each tab saves on its own, so saving one never rewrites another
Fixed
- Workspace settings, API keys, webhooks and domains could be changed by any member; they now need an owner or admin, like Team and Payments already did. A member sees and edits only their own event types; owners and admins keep the whole list
- Single-tenant installs resolve "the" workspace by creation date; before, a database holding several workspaces (a dev or test database) could switch between them after any update to the workspace row
0.10.1 - 2026-10-10
Fixed
- Sent.dm template variables are filled by name (
attendee, event, host, when, text, link, token, code; anything else positionally) and a link variable takes the whole short link. 0.10.0 filled them in declared order, so a template with both an inline link and a URL button got the manage token in link and nothing in token, and Sent.dm refused every WhatsApp send with "Template variables cannot be empty"
0.10.0 - 2026-10-09
Added
- WhatsApp through Meta's Cloud API as a third text provider: messages come from the operator's own registered number under their verified business name, using approved templates for confirmations, reminders, cancellations, host pings and phone-verification codes (
WHATSAPP_ACCESS_TOKEN, WHATSAPP_PHONE_NUMBER_ID; template names and bodies in docs/notifications.md). Providers are now chosen per channel: WhatsApp via Meta, then Sent.dm, then Twilio; SMS via Sent.dm, then Twilio. A webhook at /api/webhooks/whatsapp verifies Meta's signature and logs failed deliveries
- Short links on the platform host,
/b/<manage token> for a guest's booking page and /h/<booking id> for the booking in the host's admin, so a link with a fixed prefix (a WhatsApp template button) reaches the right workspace or custom domain
- Event types with text reminders now also text the booker a confirmation and a cancellation notice when they gave a phone number
- Sent.dm sends templates: Sent.dm blocks free text on both channels, so
SENTDM_TEMPLATE_* ids map each message kind to a Sent.dm template (imported from Meta or one of Sent.dm's samples) whose declared variables Bookly fills in order
0.9.0 - 2026-10-03
Fixed
- The production Content Security Policy blocked Daily's call-machine bundle (served from the dailywebrtc CDNs and run through
eval()), so the call page could not load devices or join outside development. The call object now uses Daily's avoidEval loading and the CSP allows the Daily CDN hosts, blob: scripts and the Banuba/pluot media hosts; empty device lists say "No camera found" instead of opening a blank menu
Changed
- Bookly video has Bookly's own call UI instead of Daily Prebuilt: a prejoin screen with camera preview, device pickers and mute toggles (visitors without a link type their name and knock), a fitted tile grid with the active speaker outlined, screen sharing, in-call chat and emoji reactions, hand raising, a People panel where the host lets knocking visitors in, device switching, background blur and noise cancellation, picture-in-picture, full screen, a connection indicator, a room-closing countdown and keyboard shortcuts for mic and camera. Dark, in Bookly's theme, with the live transcript and the end-to-end-encryption banner as before. Built on
@daily-co/daily-react
- Call UI details: a raised hand shows as ✋ in the corner of that person's tile; background effects offer three blur strengths, four bundled scenes and an uploaded picture, remembered per browser; noise cancellation is on by default (switch it off under Settings → Audio and the choice sticks); live captions show people's names, with "(host)" after the host; on plans with their own branding the call carries the workspace's logo, name and accent instead of Bookly's
0.8.0 - 2026-10-01
Added
Text messages can go through Sent.dm as well as Twilio: SENTDM_API_KEY is the whole setup (one key for SMS and WhatsApp; Sent.dm handles sender registration and WhatsApp onboarding on its side), SENTDM_SANDBOX=true dry-runs requests, and TEXT_PROVIDER picks explicitly when both are configured. Twilio keeps working unchanged. The operator health page names the provider in use
Multilingual transcription: Bookly video calls are transcribed with Deepgram Nova-3 in multilingual mode unless the workspace fixes a language, so speakers can switch languages mid-call. Each transcript line records the language it was spoken in, the booking page lists the languages spoken, and the recap, follow-up and attendee recap are written in the language the host spoke most
Fixed
- Meeting pages open on the platform host as well, so Bookly video links built from
APP_URL (no MEET_URL, as on staging) no longer 404, and the "Sign in" link offered to a host who opens a room signed out points at the app host's login, which a dedicated meeting host would otherwise rewrite into a room path
Security
- End-to-end encrypted Bookly video: a per-event-type option on the Bookly video location that limits the room to two people and switches the call to Daily's peer topology once joined, so audio and video travel directly between the two browsers and no server (Daily's included) can decrypt them. Such calls are one-to-one (one seat, no guests, a single host), are never transcribed, are labelled "Bookly video (end-to-end encrypted)" on the booking page and in emails, and the meeting page reports whether the media path really is peer-to-peer
- The security, privacy and data processing pages now describe video calls as they are: media encrypted in transit and relayed by Daily's media server (which decrypts to forward and to transcribe), the end-to-end encrypted option, private rooms, and Deepgram as the speech-to-text engine behind transcription, now listed as a sub-processor
- Bookly video rooms are private with knocking: the host (signed in) and the booked attendee (their
?t= link) hold meeting tokens and join directly; anyone else who has the room link asks to join and waits until the host lets them in. Rooms created before the upgrade stay public until they expire, two hours after their booking ends
0.7.0 - 2026-09-30
Added
- Calendar feeds: subscribe to any calendar by its private ICS address (Google's secret iCal address, a published Outlook calendar, iCloud, Fastmail, Nextcloud, Proton) under Admin → Calendars, up to five per host. Busy time from the feed is hidden from the host's availability; nothing is written back. Feeds are refreshed every 15 minutes by the
feeds.sync job, or on demand from the card, and the busy blocks for the next 400 days are kept on the row so availability never waits on a remote calendar. Recurring events are expanded with their exceptions; transparent, cancelled and Outlook-free events are ignored. Addresses are stored encrypted and fetched with private-network, redirect, size and time limits (table calendar_feeds, migration 0028)
Changed
- Docker image build: the app is built directly on top of the dependency stage instead of copying
node_modules into a second stage, dependencies are fetched from the lockfile before the package manifests are copied (a version bump no longer re-downloads them), the build context leaves out tests, CI files and generated output, and CI exports the build cache only on pushes to main, which was the single longest part of the pull-request image job
0.6.1 - 2026-09-30
Changed
- Dependencies: Sentry SDK 11 (
withSentryConfig now comes from @sentry/nextjs/config; the old sendDefaultPii: false baseline is spelled out as an explicit dataCollection block, since Sentry 11 collects request headers, cookies, bodies, query data and user info by default), Next.js 16.3.6, AI SDK, drizzle, pg-boss, resend, lucide, marked, turbo, vitest, tsx, dotenv and the rest of the weekly Dependabot group
- Node stays on the 24 LTS line:
@types/node and the Docker base image no longer take major bumps from Dependabot (engines, .nvmrc, CI and Vercel all run 24), and ESLint stays on 9 until eslint-plugin-react loads on ESLint 10
0.6.0 - 2026-09-29
Added
- Passkeys: sign in with Touch ID, Windows Hello, a security key or a password manager instead of a password. "Sign in with a passkey" on the login page, plus browser autofill of saved passkeys in the email field; add and remove passkeys under Admin → Booking page → Passkeys. A passkey is bound to the site, so it cannot be phished to a look-alike host; on Bookly Cloud one passkey works on the platform host and every workspace subdomain (migration 0027, Better Auth 1.7.6)
0.5.0 - 2026-09-28
Added
- Workspace addresses and usernames are validated as typed, with the exact problem shown under the field while typing and the button held until the name passes and is free: lowercase letters, numbers and single hyphens only, 3–40 characters for an address (2 for a username), no reserved names (platform hosts and environments such as
www, api, staging, dev, preview, mail and DNS hosts, and app routes for usernames), no look-alikes of Bookly or well-known services (leetspeak such as b00kly and paypa1 counts), and no addresses built from sign-in or payment words (acme-login, secure-pay). The server applies the same rules on save, so nothing the form lets through is trusted on its own
0.4.9 - 2026-09-28
Added
- Cloud: Stripe invoices in the host's name, Pro and up, as a switch under Settings → Payments: every paid booking also gets an invoice issued by the host's connected account, with the PDF linked from the guest's receipt page. Off by default because Stripe charges a fee per invoice
Changed
- The guest booking page shows the cancellation form in the open, with the refund amount for a paid booking, instead of hiding the button behind "Cancel this booking"
- Paid bookings use one Stripe customer per guest email, named as the guest booked, so the invoice and receipt are addressed to the guest's name rather than to the cardholder name typed on the payment form
0.4.8 - 2026-09-28
Added
- Phone verification for host notifications: texts (SMS/WhatsApp) go only to a number the host has proved with a six-digit code sent over the chosen channel; a changed number has to be verified again (migration 0026)
- Releases migrate the production database themselves: the Release workflow applies pending migrations (
PRODUCTION_DATABASE_URL secret) after publishing the release and before moving release, so a failed migration stops the deploy
- "Send a test message" for the Slack incoming webhook on Notifications, reporting Slack's answer instead of failing silently
0.4.7 - 2026-09-28
Added
- A receipt page of the workspace's own, in its name and branding, for every paid or refunded booking (
/booking/<token>/receipt): paid to, paid by, date, what for, amount, status, reference, printable. Guest page, confirmation email and the host's Inbox, Bookings list and booking view link to it; Stripe's own card receipt, which is issued in the platform's name, is linked only for the host on the Bookings list
- Cloud: the workspace's name, logo and accent are pushed to its connected Stripe account, so the Checkout page a guest pays on shows the host's brand (on connect, and whenever Settings are saved)
- Paid bookings link to Stripe's receipt: on the guest's booking page and confirmation email, and for the host on the Inbox, the Bookings list and the booking view, next to the amount and its paid/refunded state. Stripe emails the receipt itself when the platform account's "Successful payments" customer email is on (live mode)
Changed
- Cloud: the plan-change and invitation review pages state when an existing credit balance pays for part or all of a charge, instead of showing "Nothing"
- Cloud: inviting a member who would add a billed seat on Team first shows what Stripe charges when they accept (prorated) and the plan's price from then on, with the card on file; the invitation is sent only after agreeing. Invitations within the seats already billed go straight through
- Cloud: moving to a plan with fewer members than the workspace has (Team → Pro with several members) is refused on the review page with the reason and a link to Team, instead of switching and leaving the workspace over its limit
- Cloud: changing plan or interval with a live subscription now shows what Stripe will charge first: the prorated amount due now (or the credit for unused time), the new price per period from the current period's end, and the card on file, with a confirm button, a way to change the payment method, and a way out. The change is applied only after confirming; a first purchase still goes through Stripe Checkout, which shows the same
Fixed
- Emails: labels and values in the details box now share a baseline, and long labels such as "Cancelled by" fit their column instead of running into the value
- Connecting a Google or Microsoft calendar uses PKCE (S256) on top of the client secret, as Google's app checkup expects; the verifier travels encrypted inside the signed state, so the round trip still works when the callback lands on the platform host
- Cloud: Team seats now follow the member count when an invitation is accepted from the browser or a member is removed (the auth plugin's membership hooks), and the added or removed seat is invoiced right away, prorated for the rest of the period, instead of at the next renewal
- Team: the invitation email field no longer gets cut off on narrow screens
- Console: "Sign in as owner" failed with "You are not allowed to impersonate users"; operators (PLATFORM_ADMIN_EMAILS) are granted the admin role Better Auth requires on first use
- Emails on desktop clients no longer grow wider than their card: long links and ids wrap inside the details table (the label column keeps its width), the raw "paste this link" URL breaks anywhere, and the paid row links "View receipt" instead of printing the receipt URL
- A QStash publish that times out is retried once before the job is given up on (reminder scheduling on staging showed occasional 8-second stalls)
- Collective event types offered times outside a co-host's working hours: only co-hosts' bookings and calendars were folded in. The time outside each co-host's schedule (and their day-off overrides) now counts as busy, so slots appear only when every host is actually working and free
- Cloud: paid bookings and payment requests failed at Stripe with "Creating direct charges with type=express … is not supported for new platforms". Checkouts are now destination charges on the platform account, transferred to the host's connected account as merchant of record, with the platform fee as before; refunds reverse the transfer. Legacy direct charges still refund on the host's account
- Cloud: returning from Stripe Connect onboarding crashed Settings ("revalidateTag … during render which is unsupported"); the return now goes through a route handler that refreshes the account's flags before landing on Settings
- Cloud: accepting a team invitation by creating an account failed with "Unauthorized". Sign-up returns no session while email verification is required, even though invitees are verified on creation; the form now signs the new account in before accepting, and the invitee lands on the workspace's own admin instead of the platform host. Covered by the cloud end-to-end suite
- Cloud: changing plan or billing interval with a live subscription now updates that subscription in place, so Stripe prorates the remaining time and charges (or credits) only the difference; before, a second subscription was created and the full new price charged. A pending cancellation is undone by the change, and the workspace ignores Stripe events for subscriptions it no longer follows
- Cloud: cancelling a subscription in the Stripe portal now shows on Admin → Billing as "cancelled, ends " instead of a renewal date; the plan stays until then
0.4.6 - 2026-09-28
Fixed
- Cloud: upgrading to a yearly plan failed at Stripe Checkout ("Checkout does not support multiple prices with different billing intervals"), because the monthly metered capture-overage price was added to the yearly subscription. The overage now rides on the plan only when both bill on the same interval; a yearly plan gets a monthly companion subscription for it the first time overage is reported, which is cancelled with the plan
- Cloud: auth cookies are named after the root domain (
bookly-app-io.session_token), so a production session no longer shadows a staging one on staging.bookly-app.io, where the browser sent both. Existing sessions are signed out once by this release
0.4.5 - 2026-09-27
Changed
- Social sign-in is a row of icon squares (Google, Microsoft, GitHub) instead of three stacked "Continue with…" buttons, on sign-in, sign-up and invitation pages; the provider name stays as the accessible label and tooltip, and the loading skeleton matches
0.4.4 - 2026-09-27
Changed
- Environments for the hosted service: production deploys from the
release branch, which the Release workflow fast-forwards on every vX.Y.Z tag; main is staging (staging.bookly-app.io, a Vercel custom environment); every pull request gets a single-tenant preview seeded with the demo workspace. Staging builds run migrations and PR builds migrations plus the seed, and APP_URL falls back to the deployment's own URL on Vercel when unset (docs/cloud.md, "Environments")
0.4.3 - 2026-09-27
Changed
- Every page shows a loading skeleton shaped like itself while its data streams in: the same width, headings and kind of content (list, table, form, calendar, prose) in the same place, instead of one generic placeholder. Covers the admin, the operator console, the public booking pages, the docs and the sign-in, sign-up and setup forms
0.4.2 - 2026-09-26
Fixed
- Admin → Bookings lists a booking's guests on single bookings too, not only on group-session seats
- Calendar invitations name the host's own address as the organizer instead of a placeholder
0.4.1 - 2026-09-26
Fixed
- Event type editor: "Guests per booking" sits next to "Seats per slot" in its own row; squeezed into the buffers row it clipped the minimum-notice field
0.4.0 - 2026-09-26
Added
- Guests on a booking: an event type can let the attendee bring colleagues ("Guests per booking" in the editor, migration 0025). The booking form asks for their emails, the manage page and Admin → Bookings list them, they are added to the calendar event and get the invitation, the reminders and any cancellation; only the person who booked can change or cancel. The API takes
guests on POST /bookings and returns them on every booking
0.3.5 - 2026-09-26
Fixed
- "Max bookings per day" and the schedule's weekly budget counted every busy interval, including events from a connected calendar, so a host with a normal calendar showed no times at all; both now count Bookly bookings only, while calendar events keep blocking the times they cover
0.3.4 - 2026-09-26
Fixed
- Signing in with an emailed link no longer removes the account's password. Better Auth 1.7 treats an account whose email was never confirmed as unproven and deletes its password (and every linked provider) on the first magic-link sign-in; Bookly never confirmed addresses, so any password user who tried the "Email link" tab was locked out of password sign-in. Sign-up on the hosted service now confirms the address first, invited members and the first self-hosted owner count as verified, and migration 0024 marks every existing password account as verified so this cannot happen to them
- Magic links sign in existing accounts only; an unknown address no longer gets an account without the consent step
Changed
- Cloud sign-up: after the account form, "Check your inbox" waits for the confirmation link (one hour), which signs the person in and opens the workspace step; a password sign-in before that explains and resends the link
0.3.3 - 2026-09-26
Fixed
- Calendar invitations (.ics) escape semicolons in titles and descriptions; a title such as "Intro call; with Ahmad" no longer breaks the event when a calendar app parses it
- Prometheus metrics escape backslashes and newlines in label values
- The embed script frames http(s) booking pages only, and adds
embed=1 as a proper query parameter
Changed
- Code-scanning follow-ups: transcript cues and doc heading ids strip tags until stable, the From header is parsed without a backtracking regex, and webhook and console-email logs keep payload fields on one line
0.3.2 - 2026-09-26
Changed
- Marketing site: the stylesheet is inlined so the first paint no longer waits on a blocking request (mobile LCP), the scroll-reveal reads its stagger delay without forcing layout, and the lifecycle dots pulse on the compositor
/llms.txt on the platform host: a Markdown map of the site and the manual for AI crawlers
- Every response carries
Cross-Origin-Opener-Policy: same-origin-allow-popups
Fixed
- Paths that look like files (
/foo.txt, /ai-catalog.json) returned the not-found page with a 200; they are real 404s now, on every host
0.3.1 - 2026-09-26
Fixed
- Connecting a provider whose account uses a different email than the Bookly account now says so (the message fell back to a generic sentence); expired sign-in attempts and unverified provider emails get their own wording, and Sign-in methods states which address the provider account must use
0.3.0 - 2026-09-26
Added
- Sign in with Google, Microsoft or GitHub. Each provider appears on the sign-in, sign-up and invitation pages once its OAuth client is configured (
GITHUB_CLIENT_ID/GITHUB_CLIENT_SECRET are new; Google and Microsoft reuse the calendar clients with an extra callback URL). Sign-in asks for identity only, never calendar access. A provider never creates an account by itself: the sign-up page records consent first and a self-hosted install still requires an invitation. Google and Microsoft attach to an existing account with the same verified email; Admin → Booking page → Sign-in methods connects or disconnects providers and lets an account created through one set a password
Changed
- Search and sharing: a workspace host (tenant subdomain, custom domain or self-hosted install) serves its own robots.txt and a sitemap of its public pages on its primary domain; profile, event and routing-form pages carry canonical and Open Graph tags and structured data (Person, Service, Offer); workspace pages are titled after the workspace and drop the "— Bookly" suffix on plans that remove branding. On the marketing site, docs pages get canonical tags, social tags and their own descriptions, the social card shows each page's title, the sitemap carries real dates or none, product mocks no longer inject headings, and the 404 page has a title. The event page streams its calendar behind a skeleton, and the build targets evergreen browsers
0.2.1 - 2026-09-25
Added
- Event types have a Waitlist switch (off by default). With it on, a full group session or an empty day offers "tell me when a spot opens"; with it off, a full session shows as "Full" and cannot be joined (migration 0023)
Changed
- The booking calendar strikes through days with no times
- Marking a booking as no-show removes the automatic "Meeting took place" note from the contact's timeline and, when the contact became active only through that meeting, returns them to lead; undoing the no-show does the reverse
- The marketing footer no longer carries a "Built by" line
Fixed
- Group sessions stayed unbookable while seats remained when the host's connected calendar returned the session's own event as busy time
- A booking made the same day no longer receives the "tomorrow" reminder right after booking; reminder offsets that had already passed when the booking was made are skipped
0.2.0 - 2026-09-25
Changed
- Group sessions, series and waitlists show their numbers everywhere: the booking page carries a "6 sessions" badge, the series note and "N seats left" on every time (full times stay in place as "Full · waitlist" or "Full · 2 waiting"); the attendee's booking page has a session card with "2 / 3 seats", the open seats and the waitlist size;
Admin → Bookings folds each session into one card with the seat count, every attendee (name, company, email, status, actions) and who is waiting, and the brief page lists the attendees. The profile page badges recurring and group event types. The layouts fit phones as well as wide screens
0.1.0 - 2026-09-24
First public release: the self-hostable scheduling platform with contacts, briefings, auto-capture and recaps, plus the hosted cloud mode at bookly-app.io.
Added
- Custom domains on the cloud: a verified domain marked primary under Admin → Domains is the workspace's public address. Booking pages, manage links, waitlist and unsubscribe links in emails and in the API use it, and guest pages requested on
<slug>.bookly-app.io or another verified host redirect to it; the admin stays on the slug host, where the session cookie lives
- Zoom deauthorization endpoint (
/api/webhooks/zoom, verified with ZOOM_WEBHOOK_SECRET): answers Zoom's URL validation, and removes a user's Zoom connection and tokens when they uninstall Bookly from their Zoom account, as Marketplace publication requires
- Compliance for the hosted service: outreach emails (proposals, payment requests, follow-ups) carry the workspace's postal address (new Settings field) and an unsubscribe link with one-click
List-Unsubscribe headers; a contact who unsubscribes is marked on their page and in the inbox and the composer refuses to send, and hosts can opt a contact out or back in on request (migration 0022). Event types set to always transcribe now say so on the booking form and in the calendar invitation as well as the confirmation, bookings store when consent was given, and the notetaker announces itself in the meeting chat to everyone who joins. Briefings, recaps and drafts are labelled AI-generated. Error reports have guest emails and phone numbers scrubbed before they reach Sentry. STRIPE_TAX=on adds Stripe Tax (address and VAT-id collection) to plan checkouts. New /dpa page with the sub-processor table, incorporated into the terms; terms and privacy updated for the operating entity (Cloudeo Solutions, LLC, address from OPERATOR_ADDRESS), an 18+ rule, no regulated (HIPAA) data, recording-consent and commercial-email responsibilities, US state privacy rights and tax at checkout
- Follow-up emails from a contact page: a third outreach button beside Proposal and Payment request, with its own editable template (Settings → Proposal, payment and follow-up emails, placeholder
{bookingUrl} for your booking page), the same preview-then-send flow, a timeline entry and a new follow-up date. The inbox's follow-up list links straight into the composer
- Several availability schedules per member: Admin → Availability lists them, adds one (a copy of the default), switches between them, makes any the default and deletes the others; event types choose their schedule and fall back to the default when it is removed. A date override can be applied to all of a member's schedules at once (on by default) and removed everywhere with one click
- Event types can offer several ways to meet (Bookly video, Meet, Zoom, Teams, phone, in person, custom): the editor lists them, the first is the default, and with more than one the attendee picks on the booking form; a phone call asks for the attendee's number with a country picker preselected from their location and stores it as E.164, an in-person meeting without a host address asks the attendee for one, and both show in the invitation, the emails and the booking pages (the choice is kept when rescheduling; the API takes
location and lists locations). Auto-capture is offered when any of them can be transcribed and applies to the chosen one; migration 0021
- Auto-capture on Google Meet, Microsoft Teams and Zoom: with
RECALL_API_KEY set, a notetaker bot (Recall.ai) joins calls that should be transcribed, streams the transcript live to Bookly and delivers the full transcript when it leaves; from there recap, tasks and follow-ups work exactly as on Bookly video. Same consent, plan and minute-budget rules; the bot is capped at the remaining minutes and cancelled with the booking. Event types on those providers now offer the Auto-capture setting, the confirmation email says a notetaker joins, and the health page shows the notetaker status
- Daily seat reconcile for Team subscriptions on the job tick: every Team workspace's Stripe quantity is compared to its member count and corrected, so a failed update at join or leave time cannot leave seats unbilled; the terms now state how added and removed seats are charged and credited
- Yearly billing (two months free), behind
STRIPE_PRICE_PRO_YEARLY / STRIPE_PRICE_TEAM_YEARLY. The landing page, pricing page and Admin → Billing share a monthly/yearly switch (yearly by default), Stripe's interval is mirrored onto the workspace, offers in the structured data list both periods, and the terms and pricing FAQ describe yearly renewal and cancellation
- Cloud deployment target bookly-app.io:
www redirects to the apex on the platform host, the update-check default points at https://bookly-app.io/api/telemetry, and docs/cloud.md gains a Vercel deployment walkthrough (domains, env import, migrations, Stripe endpoints)
- Stripe Connect for cloud mode: guests pay the host's own Stripe account, never the platform's. Owners connect through Stripe's hosted onboarding (Settings → Payments, Pro and Team); prices apply once Stripe enables charges, refunds run on the host's account, and payment requests follow the same route. A second webhook endpoint (
/api/webhooks/stripe/connect, STRIPE_CONNECT_WEBHOOK_SECRET) confirms those payments and tracks onboarding. Console → Payments shows the Stripe setup, the connected hosts and sets the platform fee, with a per-workspace override on the workspace page; migration 0020 adds stripe_account_id
- Booking pages carry the workspace name with the Bookly mark and a “Powered by Bookly” footer; on plans that remove branding (Pro, Team, self-hosted) the logo and accent colour from Settings → Branding replace them, and the colour recolours buttons, selected days and focus rings. Emails say “Powered by Bookly” too
- Operator console reads on a phone: the tab strip wraps, health checks stack their detail under the name, search rows stay on one line, long emails and JSON wrap inside their cards, and the installs table scrolls sideways with dates on one line
- Branding in Settings: a logo URL and accent colour used in every guest email, on the plans that remove Bookly branding (Pro, Team, and every self-hosted install); Free keeps the Bookly mark and footer
- Live captions under the built-in video call while auto-capture is on: the last lines transcribed, with speaker and time, as they arrive
- Landing page: “Emails in your name” section with a mock of the branded confirmation, proposal review and pay button in the follow-up copy, a “Your data, your call” card, two new comparison rows (branded emails, self-service export and deletion) and a note on self-host update notices and opt-in telemetry
- Proposals and payment requests are sent in two steps: edit the prefilled template, then review the exact branded email (recipient, subject, rendered body, and what the Pay button will link to) in a dialog before confirming; the Pay button comes from the request itself rather than a link in the text; proposal and payment templates have Preview links in Settings; a contact without a company gets their name in the default subject
- Emails rebuilt on React Email with the workspace's branding (logo, name, accent), a details card, buttons and a plain-text alternative; confirmations, reminders, cancellations, follow-ups, proposals, payment requests, client recaps, sign-in links, password resets and invitations. Workspaces can replace the wording of the confirmation, reminder and cancellation emails (Admin → Settings → Guest emails, with previews); defaults remain
- Guest-facing emails (confirmations, reminders, cancellations, follow-ups, proposals, payment requests, client recaps) are sent as “ via Bookly” at the platform address, with the host's email as Reply-To
- Plan cards are one shared component on the landing page and the pricing page, with each plan's standout features (contacts and briefings on Free, auto-capture and paid bookings on Pro, team scheduling and the larger capture budget on Team) listed first and emphasised
- Marketing SEO and mobile pass: JSON-LD (Organization, WebSite, SoftwareApplication with offers, FAQPage on pricing, breadcrumbs on every subpage), web manifest, light/dark theme colour, stable sitemap dates, noindex on the workspace chooser, a comparison table that reads at 320px, and a movement-only hero entrance so the headline paints immediately; Lighthouse mobile: SEO 100, accessibility 100, best practices 100
- Loading states everywhere: a navigation progress bar, route and page skeletons instead of blank streaming, and every form submit button disables itself with a spinner while its action runs (server-action forms included); task complete / remove controls show progress too
- Cloud-mode end-to-end suite in CI (
e2e-cloud job): marketing pages, social metadata, robots and sitemap, real 404s, telemetry receiver, sign-up consent (API and UI), a tenant booking, the workspace chooser and the operator console, plus axe checks; the e2e build omits upgrade-insecure-requests so plain-http test hosts work
- Accessibility: light-theme muted text darkened to pass AA contrast; consent and telemetry checkboxes have accessible names; landing-page mocks are marked decorative
- Jobs on QStash for serverless: one job registry with pg-boss (self-host), QStash (
QSTASH_TOKEN + signing keys; signed POST /api/jobs/<name>, delays, retries, dead-letter queue, cron schedules registered on boot) and inline drivers; reminders and follow-ups scheduled at their exact time per booking; webhook deliveries, transcript processing and CRM sync/notes run as retried jobs; Console → Health shows schedules and dead-letter count
- Shared rate limiting: one limiter behind public forms, the API, the live-transcript endpoint and Better Auth's sign-in limiter, counting in memory by default and in Upstash Redis (REST, no SDK) when
UPSTASH_REDIS_REST_URL / UPSTASH_REDIS_REST_TOKEN are set, so Vercel instances and replicas share counts; store outages fail open with a log line; Console → Health reports the limiter
- Data rights: owners can download a JSON export of the whole workspace (Admin → Settings → Your data,
GET /api/admin/export, secrets excluded) and delete the workspace permanently after typing its slug (cancels the cloud subscription; single-tenant installs return to the setup wizard); users can delete their account from the profile page once they own no workspace
- Update check and telemetry: self-hosted installs ask
TELEMETRY_URL once a day for the latest version (sending version, tenancy, Node version and a random install id; TELEMETRY=off disables), the admin shows an update notice, and workspaces can opt in to share coarse usage counts. The platform records pings in installs (migration 0019) and Console → Installs shows active installs, versions and totals
GET /api/health for uptime checks (200 when Postgres is reachable, 503 otherwise)
- Cloud sign-up requires accepting the terms of service and privacy policy; acceptance time and policy version are stored on the user (
consent_at, consent_version, migration 0018) and shown in the operator console
- README rewritten with the new mark, a feature map, self-hosting requirements, env overview and operations, and a contributor section
- Marketing site (cloud mode): rebuilt landing page with the meeting lifecycle, live-transcript, routing, seats and follow-up mocks, a comparison table, scroll animations, a full header and footer; pricing page with plan comparison and FAQ; about, contact, security, privacy, terms and changelog pages;
robots.txt, sitemap.xml, Open Graph and Twitter cards with a generated social image at /og; real 404s for unknown paths on the platform host
- Admin: a Help group pinned to the bottom of the sidebar (Documentation, Support, GitHub); Support goes to
SUPPORT_EMAIL in cloud mode and to the issue tracker when self-hosted; sticky header and sidebar
- Brand: the buckle logo mark (بُكلة), SVG favicon, ICO and Apple touch icon
- Booking pages per host with weekly hours, date overrides, buffers, notice and horizon rules, timezone-aware for visitors
- Google Calendar and Outlook sync for conflicts and events; meeting links on Google Meet, Zoom, Microsoft Teams or built-in video (Daily.co)
- Paid bookings via Stripe Checkout with refunds on cancel
- Email, SMS and WhatsApp reminders, follow-up emails, no-show tracking, host pings on Slack / text when someone books or joins
- Teams: invitations and roles, round-robin and collective event types
- Group sessions:
seats per event type; a slot stays bookable until full, attendees share one meeting link, seats left shown on the booking page and in the API
- Auto-capture for built-in video: opt-in or always-on transcription with speaker labels, live notice in the call, transcript on the booking page, retention and deletion; recap with summary, covered asks, decisions and action items with evidence, open questions, objections, next step, stage suggestion and follow-up, each one click; recaps to review in the inbox; client-facing recap email with chosen items; attendees can delete their transcript; cloud plans get a monthly transcription budget
- Production hardening: Playwright end-to-end suite (booking, admin, accessibility, headers) in CI, optional Sentry error tracking, and security headers (CSP, HSTS, Permissions-Policy, frame protection)
- Operator console: stats and sign-up trend, search and filters, workspace detail with usage and billing, manual plan overrides with expiry, users with ban / unban, audit log of operator actions, health checks, and a Prometheus metrics endpoint (
METRICS_TOKEN)
- Workspace switcher in the admin header (cloud mode): current workspace, switch to any other, all workspaces, new workspace
- Password reset by email ("Forgot your password?") and password change from the profile page
- Cloud landing page rebuilt around the meeting lifecycle with light-themed mockups of the real inbox, briefing, call, recap, availability, contact and booking screens
- Polish: icon-based admin navigation, Lucide icons instead of text glyphs everywhere, external links open in a new tab, and a documentation theme with sidebar, table of contents, anchors and prev/next
- Execution: proposal and payment-request emails from templates (Stripe Checkout link for the amount), HubSpot / Pipedrive sync of contacts, stages and meeting notes, contact and task webhook events and API endpoints
- Priority-aware availability: focus blocks and a weekly meeting budget that only existing customers (active / won / vip contacts) can book into
- Meeting Inbox as the admin home: today's meetings with briefs, requests with accept / reply-instead, contacts to follow up with snooze, open and overdue tasks
- Post-meeting capture: notes or a transcript become a summary, decisions, tasks with due dates, a stage suggestion and a follow-up email draft (assistant optional); tasks with overdue nudges
- Pre-meeting briefings for hosts, written by the assistant (Anthropic, optional) or as a plain summary; shown in the admin and sent with the last reminder
- Contacts: one record per person with stage, tags, notes and follow-up date, and a timeline of every booking, email, form answer and note (Admin → Contacts)
- In-app documentation at
/docs, rendered from the repository's docs/ folder
- Abuse controls: per-visitor throttles on public forms, a workspace email/domain blocklist, and per-plan booking and API budgets in cloud mode
- Routing forms: a questionnaire at
/r/<slug> with rules that send visitors to an event type, a link or a message
- Calendar push notifications: Google watch channels and Microsoft Graph subscriptions refresh busy time the moment a calendar changes (renewed automatically)
- Waitlist for full group sessions and fully booked days; first come first served when a spot frees up
- Recurring bookings: event types can repeat daily, weekly or monthly for 2–52 sessions; one booking reserves the series (a paid series is charged in one checkout), with per-session manage links and "cancel remaining"
- Public REST API with scoped keys, signed webhooks, embeddable widget, custom domains with automatic HTTPS
- Cloud (multi-tenant) mode with Free, Pro and Team plans, billing and an operator console (
packages/cloud, proprietary).
- Docker Compose self-hosting with optional Caddy HTTPS; Vercel deployment path.
- Documentation in
docs/.
Changed
- Cloud pricing: Pro is $24 a month or $240 a year ($20 a month billed yearly), Team $30 a member ($300 yearly, $25 a month; two seats minimum). Bookly video moves to Pro (Free keeps Meet, Zoom, Teams, phone and in-person; a Free workspace's Bookly video rows are hidden from booking pages and cannot be saved). Auto-capture is sold in hours: 5 a month on Pro, 8 per member pooled on Team, and past the budget transcription continues at $3 an hour billed by the minute on the next invoice through a Stripe metered price (
STRIPE_PRICE_CAPTURE_OVERAGE), with a switch on Admin → Billing to stop at the included hours instead. Paid bookings are available on every plan, Free included: paid plans pay no platform fee, Free pays 5%; Console → Payments sets a rate per plan. Team's shared customer memory (contacts, timelines, briefings across the workspace) is now stated on the plan. Pre-meeting briefings run on Haiku 4.5 (ASSISTANT_BRIEF_MODEL); recaps stay on the main model
- Dropdowns and date pickers use the app's own components everywhere (the shadcn Select and Calendar over Base UI) instead of the browser's: event type and routing editors, booking and routing forms including the country picker and choice questions, the booking page timezone picker, the console, team invites, notifications, contacts and calendar settings; dates are picked from a popover calendar with a clear button where optional; times in availability hours and date overrides are picked from a quarter-hour dropdown; colours (event type colour, branding accent) come from a popover with preset swatches and a hex field instead of the browser dialog; number fields have their own minus and plus buttons with the unit joined to the field
- The built-in video option is now called Bookly video everywhere: booking pages, admin, marketing, docs and health checks
- Team has a two-seat minimum: checkout and the seat sync bill at least two members, and the pooled capture budget counts the billed seats, so a one-person Team pays for two seats and gets two seats' worth of capture hours; the plan card, FAQ and terms say so
Fixed
- Admin → Domains: a newly added custom domain no longer stretches the list off the page; each domain is a card and the TXT record, token and target wrap on any screen
- Bookly video: the Daily webhook that drives auto-capture and "attendee joined" pings is registered automatically for the whole install (first room or job tick,
platform_state key daily.webhook) instead of only when a workspace turned join notifications on; the webhook route finds the booking by room across workspaces. Hosts are recognised by a Daily owner token issued when a signed-in member opens the meeting page, so transcription starts even if both sides typed the same name; attendee links carry their manage token to pre-fill their name. "Attendee joined" is now a plain on/off preference per workspace (on by default). Console → Health reports the webhook.
Removed
- The unused storage layer (
packages/storage, STORAGE_* and S3_* variables, the MinIO service in Compose): nothing in Bookly uploads files; profile photos and logos are URLs