A lightweight, database-free webmail client in a single Go binary.
Part of VulOS — the open, self-hostable web OS & app suite. Runs standalone, or as an app hosted by the Vulos OS.
lilmail is a self-hostable webmail client that connects to any IMAP/SMTP
mailbox and ships as one self-contained Go binary. The UI is server-rendered
HTML (Go templates + HTMX + Alpine.js) with every frontend asset embedded via
embed.FS — no build step, no CDN, and no external services to run by default.
Drop the binary next to a config.toml and it runs, comfortably, on 64 MB of RAM.
Log in with a classic username/password or OAuth2 / OpenID Connect (full PKCE flow with XOAUTH2 and OAUTHBEARER SASL and automatic token refresh). Everything beyond core mail — CalDAV calendar, CardDAV contacts, an AI mail assistant, real-time notifications, Web Push, and multi-account support — is opt-in via config keys and adds zero overhead when disabled.
VulOS is an open, self-hostable web OS + app suite. Each product is self-hostable on its own and can be surfaced together inside the Vulos Workspace hub app, which the OS hosts:
- Vulos Mail — mail + calendar + contacts (engine: lilmail; UI:
@vulos/mail-ui; server: vulos-mail) - Vulos Talk — team chat + channels/Spaces + huddles
- Vulos Meet — video meetings (LiveKit SFU)
- Vulos Office — documents: docs, sheets, slides, PDF
- Vulos Relay — sovereign connectivity fabric (
@vulos/relay-client) - Vulos Workspace — an OS-hosted productivity hub that consolidates Mail, Office, Talk & Meet
- Vulos OS — the web-native desktop (the shell that hosts the suite apps)
lilmail is the engine of the Vulos Mail product (mail + calendar +
contacts): a complete IMAP/SMTP webmail client that also exposes a clean /v1
JSON API consumed by the shared @vulos/mail-ui React components and the
vulos-mail server. It runs standalone and as an app hosted by the Vulos OS
(the OS is the shell; the Workspace hub app can surface its Mail). Products
link/embed each other only through clean seams (here, the /v1 HTTP
contract) — they never import one another's code.
- Single binary (~30 MB), no external database — templates and vendored JS
embedded with
embed.FS; durable state uses an embedded bbolt file by default (nothing to run), with an optional Postgres backend for shared / multi-instance deploys; runs fully offline/air-gapped with onlyconfig.toml - IMAP mailbox browsing and SMTP sending
- JSON API (
/v1) — a clean REST surface (folders/labels, paginated messages, search, flags, move/archive/spam, delete, snooze, compose + drafts, attachment upload/download, scheduled send, calendar, contacts, and rules) for rich clients, served alongside the HTMX UI from the same engine and the same session auth. Powers the Vulos Mail React webmail and the Workspace hub app. See docs/API.md. - OAuth2 / OpenID Connect — authorization-code flow, PKCE (S256), automatic refresh-token handling, XOAUTH2 and OAUTHBEARER SASL; password login still works
- Conversation threading — JWZ algorithm (
References/In-Reply-To/Message-ID) backed by an embedded bbolt store - Compose — plain-text and HTML rich-text (contenteditable toolbar), file
attachments (multipart/mixed MIME) with
cid:inline images (multipart/related), scheduled send (send-later,/v1), drafts with 30-second auto-save plus IMAP APPEND/restore. Outgoing headers are guarded against CR/LF/NUL header injection - Recipient autocomplete — recent-recipients store with optional CardDAV address-book lookup
- Calendar (CalDAV) + meeting invites — month/week views, event CRUD,
free/busy, and end-to-end iTIP/iMIP invites (send a
METHOD:REQUEST, parse a received invite, RSVP withMETHOD:REPLY) — opt-in via[caldav] - Real-time notifications — IMAP IDLE watcher, SSE stream, browser
notifications, native desktop toasts, and VAPID Web Push — opt-in via
[notifications] - AI mail assistant — smart compose, thread summaries, reply suggestions,
action-item extraction, and phishing detection via any OpenAI-compatible
endpoint — opt-in via
[ai] - Multiple accounts — add/switch IMAP accounts and a unified inbox with
concurrent fan-out and per-account error isolation — opt-in via
[accounts] - Security-first — JWT sessions, AES-256-GCM encrypted credentials at rest,
strict Content-Security-Policy,
SameSite=Laxcookies, sandboxed email iframe - Dark mode — hand-written CSS, no CDN dependency
- Builds and runs on Linux, macOS, and Windows
lilmail is a server-rendered Fiber application. There is no SPA and no asset pipeline — HTML templates and vendored JS/CSS are compiled into the binary at build time, and HTMX swaps in server-rendered partials so the page never does a full reload.
flowchart TD
UI["HTMX/Alpine UI (HTMX/SSE)"] --> Server
React["React clients (Vulos Mail, Workspace) (fetch JSON)"] --> Server
Server["Fiber HTTP server — HTMX routes + /v1 JSON API (one Go binary);<br/>same mail engine + session auth under both"]
Server --> IMAP["IMAP/SMTP (your mail server)"]
Server --> Store["durable store (seam): bbolt by default;<br/>optional Postgres (threads, drafts, recipients, accounts)"]
Server --> Services["opt-in services (CalDAV, CardDAV, AI, Web Push) — off by default"]
State that must survive a restart (conversation threads, recent recipients,
extra-account credentials, VAPID keys, scheduled sends) lives in the durable
store — an embedded bbolt file by default, or a shared Postgres database when
configured; session credentials are AES-256-GCM encrypted. The same mail engine
backs both the server-rendered HTMX UI and the /v1 JSON API. See
docs/ARCHITECTURE.md for the request lifecycle and
docs/API.md for the JSON API reference.
CP-brokered mode (Vulos Cloud, off by default). In the Vulos Cloud
deployment lilmail runs behind the control plane (CP), which custodies each
user's external mailbox credentials and reverse-proxies to /v1, injecting the
per-request connection spec as X-Vulos-Broker-Auth + X-Vulos-Mail-* headers.
This path is gated by a shared secret (LILMAIL_BROKER_SECRET, matched in
constant time): if the secret is unset or mismatched, the headers are ignored
entirely and the request falls back to normal session auth, so standalone
lilmail never trusts client-supplied connection headers. Each brokered request's
connection spec is copied out of the transport buffer as it is parsed, so one
request can never mutate another's retained spec — per-account routing stays
isolated even under a pooled/concurrent server. See
docs/API.md → CP-brokered credential mode.
In the Vulos hosted topology every user's mailbox lives in a cell — a
self-contained vulos-mail instance that
owns that account's mail. lilmail is the /v1 JSON mail-API library the cell
serves: vulos-mail embeds/points a lilmail engine at its own IMAP/SMTP and
reverse-proxies /v1 to it (brokered mode above), so @vulos/mail-ui and Vulos
Workspace talk to exactly one contract regardless of where the mailbox is hosted.
The cell is the source of truth; the central tier is only a minimal forwarding
relay for inbound/outbound SMTP (mail into the cell, mail out of the cell), plus
the shared @vulos.to login that federates identity. lilmail itself stays
transport-agnostic and content-blind about that topology: it just speaks IMAP/SMTP
to whatever mailbox the (brokered or configured) spec points at, and serves /v1.
# Clone
git clone https://github.com/exolutionza/lilmail.git
cd lilmail
# Configure — copy the example and fill in your mail server details + secrets
cp config.toml.example config.toml # then edit
# Run
go run main.go # or: make build && ./lilmailOpen http://localhost:3000 and sign in.
Prefer a pre-built binary? Grab the latest archive from
Releases — only config.toml
needs to be present alongside it.
All configuration lives in config.toml. A minimal setup needs only an IMAP
server and a couple of secrets:
[server]
port = 3000
[imap]
server = "mail.example.com"
port = 993
tls = true
[smtp]
# Derived from the IMAP server if omitted
port = 587
use_starttls = true
[jwt]
secret = "your-secure-jwt-secret"
[encryption]
key = "your-32-character-encryption-key" # exactly 32 chars (AES-256)Optional sections — [oauth2], [ssl], [notifications], [caldav],
[carddav], [ai], [accounts] — are all default-disabled. See
config.toml.example for an annotated reference of every
key, or docs/CONFIGURATION.md for the full walkthrough.
| Document | Description |
|---|---|
| docs/GETTING-STARTED.md | Installation, first-run, and basic configuration walkthrough |
| docs/ARCHITECTURE.md | Code layout, request lifecycle, and subsystem overview |
| docs/API.md | /v1 JSON API reference — endpoints, auth, payloads |
| docs/CONFIGURATION.md | Complete config.toml reference — every key, section, and default |
| docs/SCREENSHOTS.md | Screenshot gallery and how to regenerate them |
| ROADMAP.md | Shipped features, planned work, and exploratory ideas |
| CHANGELOG.md | Per-release changelog (Keep a Changelog format) |
| Login | Inbox | Message view |
|---|---|---|
![]() |
![]() |
![]() |
| Compose | Settings | Search |
|---|---|---|
![]() |
![]() |
![]() |
See docs/SCREENSHOTS.md for the full gallery and how to regenerate screenshots.
make build # go build -o lilmail .
make test # go test ./...
make vet # go vet ./...
make check # build + vet + test
go run main.go # run (requires config.toml)
./lilmail -version # print version and exitCross-compile for any supported platform:
GOOS=linux GOARCH=amd64 go build -o lilmail-linux-amd64
GOOS=darwin GOARCH=arm64 go build -o lilmail-darwin-arm64
GOOS=windows GOARCH=amd64 go build -o lilmail-windows-amd64.exemake screenshots # boots lilmail + runs the Playwright screenshotter
make demo-screenshots # uses the in-memory demo inbox — no IMAP/SMTP neededRequires Node 18+ and Playwright Chromium. See docs/SCREENSHOTS.md for which screenshots need a live IMAP account.
Contributions are welcome. Please open an issue to discuss substantial changes before sending a pull request, and make sure the following passes first:
make check # go build ./... && go vet ./... && go test ./...Released under the MIT License — see LICENSE.





