Skip to main content
04Privacy-first productivity

Purelymail
Calendar

by Yashesh Bharti
An unofficial calendar and meeting-invite web app for Purelymail mailboxes. Real iTIP scheduling delivered from your own address, RSVP tracking via IMAP, canonical events stored in your Purelymail CalDAV.
Status
Live
License
MIT
01Problem

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.

02Product

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.

03What I built

Capabilities

Real iTIP scheduling
REQUEST and CANCEL invites sent over SMTP from your own address. Multipart MIME layout that all three major mail clients render correctly.
Calendar UI
Month, week, and day views. Click-to-create, drag-to-move, live 'now' line. React + Vite + Tailwind.
RSVP automation
IMAP poller scans for METHOD:REPLY messages, matches by UID and attendee mailto, updates PARTSTAT on the event. Optional Sieve script pre-files replies to an RSVPs folder server-side.
Magic-link auth
Sign in by email link. Sessions are cookies, not JWTs. Resend for delivery with SMTP fallback.
Per-user data isolation
User and Mailbox separation in Postgres. Each user's mailbox password is Fernet-encrypted at rest.
CalDAV auto-discovery
PROPFIND on /webdav/ discovers the Purelymail account ID so the user only types their email and password.
04Architecture

Three protocols, one mailbox

The app is a thin orchestration layer on top of three Purelymail surfaces:

CalDAV
purelymail.com
443 HTTPS
Canonical event store. Every accepted event is PUT to /webdav/<account>/caldav/. METHOD is stripped on PUT per RFC 4791 4.1.
SMTP
smtp.purelymail.com
465 SSL / 587 STARTTLS
Outbound iTIP. REQUEST for new and updated events, CANCEL for deletions, all from the organizer's own address.
IMAP
imap.purelymail.com
993 SSL
RSVP polling. Scans the RSVPs folder for METHOD:REPLY messages, matches them to events, updates PARTSTAT idempotently.
ManageSieve
mailserver.purelymail.com
4190 STARTTLS
Optional. Installs a Sieve script that server-side files METHOD:REPLY to a dedicated RSVPs folder. Keeps INBOX clean and lets the poller scan a small mailbox.
05Technical stack

Stack

Backend
FastAPI (Python)
Frontend
React + Vite + TypeScript + Tailwind
Database
Postgres (SQLite for local dev)
ORM
SQLAlchemy
Auth
Magic-link via Resend (SMTP fallback)
Encryption
Fernet for mailbox credentials at rest
Event store
Purelymail CalDAV (canonical)
Invites
SMTP iTIP REQUEST/CANCEL
RSVPs
IMAP poller + ManageSieve filter
Deploy
Docker multi-stage, Railway, GHCR
06Product decisions

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=REQUEST as a first-class invite with RSVP buttons. Plain .ics attachments 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=REPLY lives 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/version endpoint to a SHA badge in the page footer. "Trust me bro" is unnecessary by construction.
07On-device AI

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:

  1. You click Scan inbox in the browser.
  2. The backend opens an IMAP connection to your Purelymail mailbox and fetches recent messages, trimmed to roughly 8 KB per email.
  3. Those email bodies are returned to your browser over TLS.
  4. The browser runs Gemini Nano locally. No network calls during inference.
  5. The model returns structured JSON, constrained by a schema with has_tasks and a tasks array (max five).
  6. 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.

08Security and privacy

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.

09Outcome

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.

10Links

Read more

← All work notesPurelymail Calendar / by Yashesh Bharti