Purelymail
Calendar
Privacy-first email, no calendar half
Purelymail is a great email host. People pick it because they want a mailbox they own, on a small provider, without Big Tech sitting in the middle. The trade-off is the calendar story: Purelymail supports CalDAV so you can store events, but it does not ship an iMIP/iTIP scheduler. That means no real meeting invites, no RSVP tracking, no proper interop with Apple Mail, Gmail, or Outlook. The fix today is to glue a third-party calendar in, which defeats the entire reason for picking Purelymail.
A real scheduler, on top of your mailbox
Sign in with a magic link, connect your Purelymail mailbox once, and Purelymail Calendar gives you month, week, and day views with click-to-create and drag-to-move; real iTIP REQUEST and CANCEL invites sent from your own address with the MIME structure Apple Mail, Gmail, and Outlook all render correctly; and an IMAP poller that matches attendee replies back to events and updates each PARTSTAT automatically. Your events live in your Purelymail mailbox, not in this app's database.
Capabilities
Three protocols, one mailbox
The app is a thin orchestration layer on top of three Purelymail surfaces:
Stack
Choices that matter
- Canonical event store stays in the mailbox. Events are PUT to Purelymail CalDAV; this app's database holds users and sessions, not your schedule. If this app disappears, your calendar does not.
- Real iTIP, not iCal attachments. Every major mail client treats
text/calendar; method=REQUESTas a first-class invite with RSVP buttons. Plain.icsattachments get a download button. The MIME layout matters:multipart/mixed[multipart/alternative[text/plain, text/calendar; method=REQUEST], application/ics attachment]covers Apple Mail, Gmail, and Outlook on Windows. - Organizer added as attendee with PARTSTAT=ACCEPTED. Otherwise Purelymail does not reliably surface the event on the organizer's own calendar listing. The organizer is then filtered out of the SMTP recipient list so they do not get an invite to their own event.
- SEQUENCE rules respected. New events
SEQUENCE:0. Updates and cancels bump it. Replies do not bump it. Same UID through the lifecycle so clients dedupe correctly. - METHOD stripped on CalDAV PUT. Per RFC 4791 4.1 stored CalDAV resources must not include METHOD. Purelymail rejects PUTs that do. The same blob without METHOD is fine to send via SMTP.
- Body-test in Sieve, not header-test.
method=REPLYlives inside the calendar MIME part's Content-Type, so a header test would miss replies entirely.body :contains "METHOD:REPLY"catches them reliably. - Publishable verification chain. Every deploy ties a git SHA to a public Actions build log to a public GHCR image digest to a live
/api/versionendpoint to a SHA badge in the page footer. "Trust me bro" is unnecessary by construction.
Tasks, extracted by a model in your browser
The Tasks feature scans recent email for actionable items and surfaces them as todos. The interesting bit is where the inference happens. It does not go to OpenAI, it does not go to Anthropic, it does not go to Google, it goes to your browser. The language model runs on your machine, on your CPU or GPU, using Chrome's Built-in AI Prompt API and the Gemini Nano weights that ship with Chrome itself.
The flow:
- You click Scan inbox in the browser.
- The backend opens an IMAP connection to your Purelymail mailbox and fetches recent messages, trimmed to roughly 8 KB per email.
- Those email bodies are returned to your browser over TLS.
- The browser runs Gemini Nano locally. No network calls during inference.
- The model returns structured JSON, constrained by a schema with
has_tasksand atasksarray (max five). - The browser posts only the extracted fields back to the server.
What leaves the browser: task titles (max 500 chars), optional due dates, optional owner names, and per-message metadata (UID, subject, sender, date).
What stays local: the email bodies, the model prompts, and the raw model outputs before parsing. You can verify this yourself by opening DevTools and watching the Network tab while a scan runs. Requests go to this app's backend; none go to any third-party model provider.
The relevant source files are chromeAi.ts, extractTasks.ts, and schema.ts in the web/ directory.
Honest limits. The email body still passes through the backend in flight during the initial fetch, even though it is not persisted. Gemini Nano is a small model, so it is less good at multi-step reasoning than a frontier model. Browser support is uneven; the feature currently requires a recent Chrome desktop build. The full write-up is in the project wiki: On-device AI in Purelymail Calendar.
Threat model
Mailbox passwords are encrypted at rest with Fernet. Auth is magic-link only; no passwords on this app. Sessions are HttpOnly secure cookies. Per-user data isolation in Postgres at the schema level. The app does not read your email, only the RSVPs folder, and only the calendar MIME parts inside it. The repository is MIT-licensed and self-hostable end to end via the included Docker Compose setup, so the privacy posture is verifiable rather than asserted.
Where it stands
Live at purelymailcalendar.com. Multi-user, deployed on Railway, image published to GHCR. Every push to main triggers a build + deploy via GitHub Actions. The internal calinvite Python package is also callable as a CLI for users who want to script invites or run an external RSVP cron.