Waxwing

A serverless webmail client for JMAP.

Waxwing runs entirely in your browser and talks JMAP straight to your mail server. It ships as plain static files: no middleware, no database, no container. Stalwart can host it itself, or any web server can.

Waxwing reading a newsletter: a folder rail on the left, the message list in the middle, and the message on the right with a banner saying remote content was blocked.
Reading a message. Light and dark both ship; this one follows your system setting.
Status: v0.24.0, pre-1.0. The source is at github.com/Heiko-W/waxwing and the release carries a checksum file and a build-provenance attestation. A 0 major because 1.0 should be earned by being used, not by a feature list. Signed and encrypted mail is recognised but not verified — read the threat model before deciding it fits your situation, including the parts that say what it does not do.

Why a client with no server

Classic webmail needs its own server-side stack because IMAP was never meant for browsers. JMAP is: JSON over HTTPS, parsed and indexed by the mail server. That makes the extra webmail server redundant — Waxwing is the client that follows through on the idea.

The practical difference is what you operate. There is no Waxwing process to run, patch, monitor or back up. An upgrade is a new zip file. If Waxwing disappeared tomorrow, your mail would be exactly where it was, because it was never anywhere else.

What it does

Mail

Conversations, a virtualized list that stays smooth at 100 000 messages, full-text search, labels, and triage that works entirely from the keyboard.

Offline

A local replica of a configurable recent window, an outbox that survives a reload and a reconnect, and undo that does not depend on the network.

Installable

A Progressive Web App with push notifications, an offline shell, and deep links that survive a reload.

Accessible

WCAG 2.2 AA self-assessed, contrast and target size measured in a real browser, and the gaps written down rather than glossed over.

Fourteen languages

Čeština, Deutsch, English, Español, Français, Italiano, Nederlands, Polski, Português, Türkçe, Русский, Українська, 中文, 日本語 — two of them read by a native speaker, and the page that says which twelve are not.

Yours to brand

Product name, logo, colours and theme come from files next to index.html — edited in place, with no rebuild.

Free software

AGPL-3.0. The JMAP and JSContact packages are MIT so other clients can use them.

On a phone

The same static files. Below 40rem the folder rail becomes a drawer, navigation moves to the bottom, and reading takes the whole screen with a way back to the list.

Waxwing on a phone: the message list, with Mail, Contacts and Settings along the bottom.
The list
A message open on a phone, filling the screen, with a Back to messages link and the remote-content banner above the body.
Reading
The folder drawer open over the message list on a phone, showing Inbox, Drafts, Sent, Archive, Junk and Trash.
Folders
Writing a message on a phone: recipient, subject, a formatting toolbar and an editor filling the rest of the screen.
Writing

A closer look

A reply being written in a composer window docked over the message list, with a rich-text toolbar.
Writing a reply. The composer docks over the list so the conversation stays in view.
A suspicious message open in Waxwing. Under the sender's name a red warning reads: the name shown is not the sender's real address.
A message whose display name is an address it does not own. Waxwing says so — and see the limitations below for what that check does not cover.

Deploying it

The recommended path has Stalwart serve the app from its own origin:

curl -u 'admin:PASSWORD' -X POST https://mail.example.com/jmap/ \
  -H 'Content-Type: application/json' \
  -d '{"using":["urn:ietf:params:jmap:core"],"methodCalls":[["x:Application/set",
       {"create":{"waxwing":{"enabled":true,
        "description":"Waxwing webmail",
        "resourceUrl":"https://github.com/Heiko-W/waxwing/releases/latest/download/waxwing-stalwart.zip",
        "urlPrefix":{"/webmail":true},
        "autoUpdateFrequency":604800000}}},"c1"]]}'

Replace the host and the admin password; the rest is literal. Then restart Stalwart — it fetches the archive on start — and open https://mail.example.com/webmail/.

autoUpdateFrequency is a week in milliseconds, so new releases arrive on their own. That is a standing decision to run whatever this project publishes next; the deployment guide shows how to pin a version instead, and covers the two other paths — a reverse proxy, and a CDN with the trade-off it carries. Downloads and checksums are on the releases page.

Do not use /mail as the prefix. It is reserved: Stalwart accepts the registration, fetches the archive, and then answers 404 for every path under it without logging anything.

Honest limitations

Because a project page that lists only strengths tells you nothing about the ones it left out: