lilmail
v1.14.0MIT or Apache-2.0

Your mailbox, your machine, one small binary.

A self-hostable PIM client — mail, calendar and contacts — that talks to the IMAP/SMTP, CalDAV and CardDAV account you already own. Server-rendered HTML with every asset compiled into one Go binary: no build step, no CDN, no database to provision, and no service in the middle.

lilmail with a message open in the three-pane reading view
lilmail in dark mode with a message open in the three-pane reading view
no database · no CDN · no build step
01

Everything a mail client needs. Nothing it doesn't.

Core mail is always on. Everything past it is a config section you opt into — and costs nothing while it's off.

One process, one file on disk, one thing to back up. lilmail is deliberately small: it doesn't index your mail into a second database, it doesn't run a queue, and it doesn't need a build step to serve a page.

Nothing it doesn't

  • No database server. Durable state is an embedded bbolt file. Postgres is optional, for shared or multi-instance deploys.
  • No build step. Templates and vendored JS are compiled in with embed.FS; there is no bundler and no asset pipeline.
  • No CDN. Every font, script and stylesheet is served from the same binary — and the render gate fails the build if any page fetches off-origin.
  • No lilmail account, and no relay. It is a client. Your credentials go to your mail server and nowhere else.
  • No calendar or contacts unless you ask. The CalDAV routes are gated on [caldav] enabled; off by default, they cost nothing.
  • No multi-account unless you ask. Same shape — [accounts] enabled.
8 subsystems · 1 binary · 0 services to run
01

One binary, no database

Templates and vendored JS compiled in with embed.FS; durable state is an embedded bbolt file. An optional Postgres backend covers shared or multi-instance deploys. Runs air-gapped with only a config.toml.

02

Mail that just works

IMAP browsing and SMTP sending, conversation threading via the JWZ algorithm, plain-text and rich-text compose, attachments with inline cid: images, scheduled send, and drafts that auto-save every 30 seconds.

03

Sign in your way

OAuth2 / OpenID Connect authorization-code flow with PKCE (S256), automatic refresh-token handling, and XOAUTH2 / OAUTHBEARER SASL — with classic password login still working alongside it.

04

Calendar & contacts

CalDAV month and week views with event CRUD, free/busy, and end-to-end iTIP/iMIP meeting invites. CardDAV address-book lookup powers recipient autocomplete.

[caldav] · [carddav]
05

Real-time notifications

An IMAP IDLE watcher feeding an SSE stream, browser notifications, native desktop toasts, and VAPID Web Push — so a new message lands without a refresh.

[notifications]
06

An AI assistant, if you want one

Smart compose, thread summaries, reply suggestions, action-item extraction and phishing detection — through any OpenAI-compatible endpoint you point it at, including one on your own machine.

[ai]
07

A stable /v1 JSON API

Folders, paginated messages, search, flags, move/archive/spam, snooze, compose and drafts, attachments, calendar, contacts and settings — the same engine and session auth that serve the HTMX UI.

08

Security you don't configure

JWT sessions, AES-256-GCM encrypted credentials at rest, a Content-Security-Policy that pins every origin to self (object-src 'none', base-uri 'self', no off-origin connect-src) — though not a script-strict one, because Alpine needs unsafe-inline and unsafe-eval — plus SameSite=Lax cookies, a mail iframe sandboxed without allow-scripts, and CR/LF/NUL header-injection guards on everything outgoing.

02

No one in the middle. Not even us.

lilmail is a client, not a service. It runs on your machine and speaks straight to the mail server you already have — no lilmail account, no relay, nothing phoning home.

You Your browser Server-rendered HTML over HTMX — or any client on /v1 Your server lilmail one Go binary + a config.toml bbolt on disk · optional Postgres Your accounts IMAP · SMTP CalDAV · CardDAV, on Gmail, Outlook or any standards server HTTP TLS · OAuth
YouYour browser

Server-rendered HTML over HTMX — or any client on /v1.

Your serverlilmail

One Go binary and a config.toml. bbolt on disk, or your own Postgres.

Your accountsIMAP · SMTP · DAV

Gmail, Outlook, or any standards-compliant server, over OAuth or password.

a lilmail relay— does not exist. There is no hosted lilmail to sign up for, and no key we hold.

03

Warm, quiet, and quick.

A hand-written design system — no framework, no utility soup — with a real dark mode. Pick a screen; every one is a live capture from the repo's own Playwright script.

04

Running in about a minute.

Nothing to sign up for and nothing to provision. Point lilmail at the mailbox you already have, and open it.

~/ — build and run
git clone https://github.com/vul-os/lilmail.git
cd lilmail

# copy the annotated example, then fill in your server
cp config.toml.example config.toml

go run main.go        # or: make build && ./lilmail
# → http://localhost:3000
config.toml — the whole minimum
[server]
port = 3000

[imap]
server = "imap.example.com"
port   = 993
tls    = true

[smtp]
port         = 587
use_starttls = true

[jwt]
secret = "change-me-to-a-long-random-string"

[encryption]
key = "a-32-character-encryption-key!!"
  1. 1
    Get the binary

    Build it with Go 1.23+, pull a release archive, or run the container. Only config.toml has to sit alongside it.

  2. 2
    Point it at your mailbox

    The minimum is an IMAP server, a JWT secret and a 32-character encryption key. Every optional section stays off until you add it.

  3. 3
    Sign in

    Open http://localhost:3000 and use your email and mail-server password — or the OAuth2 button once [oauth2] is configured.

Generate real secrets before deploying: openssl rand -hex 32 for jwt.secret, and a random 32-character encryption.key — it's the AES-256 key protecting stored credentials.
05

One engine, two front doors.

The HTMX UI and the /v1 JSON API are the same mail engine under the same session auth. Build a rich client, a script or a widget on it — the shapes are a stable, additive contract.

GET/v1/folders — mailboxes and labels
GET/v1/messages — paginated, per folder
GET/v1/messages/:uid — full message + parts
GET/v1/search — server-side IMAP search
PATCH/v1/messages/:uid/flags — read, flagged, …
POST/v1/messages/:uid/move — archive, spam, folder
POST/v1/messages — send now, or sendAt
POST/v1/attachments — upload, then reference
GET/v1/calendar/events — CalDAV, when enabled
GET/v1/contacts — CardDAV, when enabled
the first page of the inbox
curl -s --cookie jar.txt \
  'http://localhost:3000/v1/messages?folder=INBOX&limit=25' \
  | jq '.messages[0] | {from, subject, date}'

The API reuses the session cookie from POST /login or the OAuth2 flow — there's no second token scheme to manage. Unauthenticated calls answer 401 with JSON instead of redirecting, so a fetch() client can react in code.

Full API reference
06

Independent, all the way down.

Think Evolution plus Evolution-Data-Server, for the web: a fully independent project with no hosted service behind it and nothing it needs from anyone else's infrastructure.

Your own accounts

lilmail connects to the mailbox you already own — Gmail, Outlook or any standards-compliant server — over OAuth or password.

  • No lilmail account to create
  • No relay handling your mail
  • Multi-account with per-account error isolation

No external services

Frontend assets are embedded with embed.FS and durable state is a local bbolt file, so an offline box is a supported deployment.

  • No CDN, no asset pipeline
  • No database server to provision
  • Prebuilt for Linux and macOS, amd64 and arm64; Windows builds from source

Security-first

Sensible defaults rather than switches you have to remember, and a threat model written down in the docs.

  • JWT sessions; AES-256-GCM credentials at rest
  • Origin-pinned CSP and SameSite=Lax cookies
  • Sandboxed mail iframe; header-injection guards

2560 × 1600 · live capture