A personal scheduling tool — my cal, if you will.
Michael is a self-hosted alternative to Calendly. Participants express their availability naturally — via free text or a calendar screenshot — and Michael finds the overlap with the host's calendar. No rigid time slot grids; just a conversational booking flow powered by AI.
See docs/design.md for the full product design.
Booking page (Elm) — conversational, chat-like interface where participants provide availability in natural language. Gemini parses the input, extracts availability windows, and prompts for missing info. Participants confirm parsed availability, pick a slot, and book.
Admin dashboard (Elm) — login, dashboard overview, booking list with detail views, cancellation, calendar source management, host availability configuration, merged calendar view, and settings.
Backend (F# / Falco) — full API for the booking flow (/api/parse,
/api/slots, /api/book) and admin operations (bookings, calendars,
availability, settings). Session-based password auth for the admin area.
Calendar sync — CalDAV integration with Fastmail and iCloud. Background polling syncs external calendar events into a local cache so host availability stays current.
Email notifications — booking confirmations and cancellation notices via SMTP (Fastmail).
Database — SQLite with Atlas-managed migrations.
| Layer | Choice |
|---|---|
| Backend | F# with Falco |
| Frontend | Elm (booking page + admin) |
| Styling | Tailwind CSS |
| Database | SQLite |
| AI | Google Gemini |
| SMTP (Fastmail) | |
| VCS | Jujutsu (jj) |
If you have direnv installed, the dev shell activates automatically:
cd michael
direnv allowOtherwise, enter the dev shell manually:
nix developSet the required environment variables and start the server:
export MICHAEL_HOST_TIMEZONE="America/New_York"
export GEMINI_API_KEY="your-key"
export MICHAEL_ADMIN_PASSWORD="your-password"
dotnet run --project src/backend| Variable | Required | Description |
|---|---|---|
MICHAEL_HOST_TIMEZONE |
Yes | IANA timezone for the host (e.g. America/New_York) |
GEMINI_API_KEY |
Yes | Google Gemini API key for natural language parsing |
MICHAEL_ADMIN_PASSWORD |
Yes | Password for the admin dashboard |
MICHAEL_DB_PATH |
No | SQLite database path (default: michael.db) |
MICHAEL_SMTP_HOST |
No | SMTP server host |
MICHAEL_SMTP_PORT |
No | SMTP server port |
MICHAEL_SMTP_USERNAME |
No | SMTP username |
MICHAEL_SMTP_PASSWORD |
No | SMTP password |
MICHAEL_SMTP_FROM |
No | Sender email address |
MICHAEL_SMTP_FROM_NAME |
No | Sender display name (default: Michael) |
MICHAEL_CALDAV_FASTMAIL_URL |
No | Fastmail CalDAV calendar URL |
MICHAEL_CALDAV_FASTMAIL_USERNAME |
No | Fastmail username |
MICHAEL_CALDAV_FASTMAIL_PASSWORD |
No | Fastmail app password |
MICHAEL_CALDAV_ICLOUD_URL |
No | iCloud CalDAV calendar URL |
MICHAEL_CALDAV_ICLOUD_USERNAME |
No | iCloud username |
MICHAEL_CALDAV_ICLOUD_PASSWORD |
No | iCloud app-specific password |
SMTP variables are all-or-nothing — if any are missing, email notifications are disabled. CalDAV sources are configured independently; set all three variables for a provider to enable it.
# Booking page
cd src/frontend/booking && elm make src/Main.elm --output=../../backend/wwwroot/booking.js
# Admin dashboard
cd src/frontend/admin && elm make src/Main.elm --output=../../backend/wwwroot/admin.js
# Tailwind
tailwindcss -i src/frontend/styles/booking.css -o src/backend/wwwroot/booking-styles.cssE2E tests live in tests/e2e/ and use Playwright. They require a running
backend server and use MICHAEL_HOST_TIMEZONE to set the browser timezone so
that natural-language availability and slot labels align with the server.
The Nix dev shell pins MICHAEL_HOST_TIMEZONE to America/Los_Angeles. If you
override it, ensure the server is also configured with the same timezone —
time-dependent assertions (slot labels, date formatting) assume the browser and
server timezones match.
This project uses SelfCI for local-first continuous integration — no remote servers needed.
selfci check # lint, build, frontend, and tests in parallelCI runs four parallel jobs: lint (treefmt), backend build (dotnet), frontend
build (Elm + Tailwind), and tests. Configuration lives in .config/selfci/.
Use the deploy script from this repo:
scripts/deploy root@<server-ip>The script:
- builds frontend assets,
- publishes backend artifacts,
- uploads a timestamped release to
/var/lib/michael/releases/<release-id>, - updates
/var/lib/michael/currentsymlink, - restarts
michaelservice.
Optional environment variables:
RELEASE_ID— override release idKEEP_RELEASES— number of remote releases to retain (default:10)
You can use scripts/michael.env.example as a template for the server runtime
environment file at /var/lib/michael/env.
src/
backend/ F# backend (Falco, SQLite, CalDAV, email)
frontend/
booking/ Elm — participant booking page
admin/ Elm — host admin dashboard
styles/ Tailwind CSS
docs/ Product design
.config/selfci/ CI configuration
.tickets/ Task tickets