ihasmail

Immutable webmail
for Stalwart.

Run it with a read-only filesystem and no volume at all: ihasmail keeps nothing of its own, because everything durable belongs to Stalwart Mail Server. What you get on top is a Gmail-class client — mail, calendars, contacts, files and filters in one responsive app that works as well on a phone as on a desktop monitor.

AGPL-3.0-or-later Stalwart 0.16+ JMAP only No database Read-only container Installable PWA

Features

Everything Stalwart exposes, and nothing it doesn't

One app for mail, calendars, contacts and files. Where a capability is missing from the server, the feature that needs it steps aside rather than breaking.

Mail that behaves like Gmail

Three-pane layout with a drag-to-resize splitter, conversation view, virtualised infinite list, multi-select, drag & drop, context menus and the keyboard shortcuts your fingers already know — j/k, e, r/a/f, g i, /. Archive, label, move and delete all come with Undo.

Calendar

Month, week, day and agenda views over JMAP Calendars, your own and anyone's shared with you. Recurrence with a custom rule builder, time zones, reminders, attendees with real invitations and RSVP, free/busy lookup, and Outlook-style colour categories that sync as JSCalendar. Right-click menus on events and on empty slots — and on a message, which turns it into an event with its subject, its body and its guests already in place.

Contacts & files

Address books with groups, a full contact editor, vCard and LDIF import/export and a letter index — and a way to pick recipients by reading the books rather than remembering a name. Re-import a corrected export and it updates what is there rather than duplicating it. Files browses JMAP FileNode storage with a folder tree, drag-and-drop between folders, and whole folders uploaded by dragging them in. Attach anything already there without uploading it again. Right-click anyone named in a message to add or edit them without leaving the mail.

Search you can actually type

Gmail operators work as written — from:, subject:, has:attachment, is:unread, label:, before:, larger: — with an advanced panel for when you'd rather click. The query runs on the server, so it searches the whole mailbox rather than the part the browser happens to have loaded.

A composer that keeps up

Multiple floating composers, rich text with inline images and emoji, recipient chips autocompleting from contacts, the directory and recent recipients, multiple identities with HTML signatures, templates, undo send, and scheduled send that waits in the server's queue whether or not you're still online.

Live, with no refresh button

Mail appears as it arrives. JMAP push rides an EventSource the server proxies on the page's behalf, with polling behind it for networks that cut long-lived connections. Desktop notifications, an optional sound, and an unread count in the tab title and on the favicon — so the tab tells you before you look.

Sieve filters, built visually

Right-click a message and Filter messages like this… writes a Sieve rule pre-filled from the sender or list, creating target folders on the fly — and can apply it to the mail already sitting in the folder. There's a raw script editor with server-side validation underneath.

Private by default

Remote images are blocked until you say otherwise, with a per-sender allow-list and an optional proxy that fetches them without handing the sender your address. Mail renders sanitised inside a Shadow DOM. Read receipts are offered, never sent automatically.

Nine new languages, and honest about them

German, Spanish, French, Dutch, Portuguese (Brazil), Russian, Ukrainian, Simplified Chinese and Japanese, alongside English. All nine are marked Beta: they were made by AI and no native speaker has read a word of them. ihasmail says so in Settings, beside a link for reporting anything that reads wrongly, because the alternative is asking you to trust text nobody has checked.

The date and number format is chosen separately, so an English interface with German dates is a setting and not a compromise: every one of the ~620 locales CLDR knows about, each named in its own language and script, with your choice of date order and a 12- or 24-hour clock. Numerals follow the locale, and dates are entered through pickers in the same format, because browsers ignore the page's and use their own.

Settings follow the account, theme and all

Your default identity, locale, date and time formats, theme, labels, templates and folder colours live in a settings.json in the account's own JMAP Files — so they are the same wherever you sign in, private windows included. ihasmail still stores nothing itself: the file sits in the mail store and is backed up with it.

The look is one of those settings. Follow the system or pick a side, with accent colours over the top and dense, cozy or comfortable spacing; a new account starts on ihasmail, the palette this site is painted in. Sender HTML is left exactly as it was designed, on a light card — one setting lets mail that brings no colours of its own follow your theme instead.

Installs like an app, handles like one

A real PWA with a service worker, mobile layout, bottom tab bar and full-screen composer. On a touchscreen it picks up the gestures your thumb already knows: swipe a message to archive or delete it — either direction, your choice — hold one to select it, hold a folder for its menu, pull the list to refresh, swipe back from a conversation. Register it as your mailto: handler and the operating system will offer ihasmail wherever it asks for a mail client — links arrive with recipients, Cc, Bcc, subject and body already filled in.

One ihasmail, several Stalwarts

Put one installation in front of more than one server and the domain somebody signs in with chooses which. A read-only JSON file names the domains that go elsewhere; anything unlisted — and a bare username, which has no domain to map — goes to the default, so an installation that sets nothing behaves exactly as it always has. A listed domain never falls back: if its server is unreachable that sign-in fails rather than quietly landing somebody on the server their domain was routed away from. One server per person, chosen at sign-in — not several at once.

The one that matters

Immutable, in the exact sense of the word

Plenty of software calls itself stateless. The word has degrees, and most of what claims it sits a rung or two down. So here is the whole ladder — how much each one keeps of its own, and who is standing where. We are on it twice, which is the honest version.

  1. a database the most there is to keep

    Configuration and user state live in SQL beside the app. An instance is the schema plus everything written to it since install — so reproducing one elsewhere means reproducing that too.

    RoundcubeSOGoOpen‑Xchange
  2. a config directory no database, still stateful

    A setup wizard writes settings somewhere that has to stay read-write, so two installs of one image are not the same thing. Configuring by environment variable instead is usually possible and lifts the rung — Bulwark documents exactly that — but it means not using the wizard the product is built around.

    Bulwark, as set up side by side →
  3. a mounted volume what "immutable" usually means

    The root filesystem is read-only, which is where most people stop. The durable state has not gone anywhere though — it moved to the volume.

    ihasmail, by defaultmost "immutable" deployments
  4. nothing the whole point

    Read-only root, no volume, and no anonymous mount hiding behind one. Delete the container and nothing is lost, because there was nothing in it.

    ihasmail with IMMUTABLE=1

Don't take our word for it

The claim is checkable in three commands, and worth checking on anything that makes it:

# is the root filesystem actually read-only?
docker inspect <name> --format '{{.HostConfig.ReadonlyRootfs}}'

# and is anything mounted, anonymous volumes included?
docker inspect <name> --format '{{json .Mounts}}'

The second one is the one people skip. A VOLUME line in a Dockerfile makes Docker mount an anonymous volume whether you asked for one or not, and that mount stays writable under --read-only — a writable hole in a container you believed had none.

ihasmail's own image carried exactly that bug until 2.16.117. We found it by running the check above rather than by trusting the flag, which is precisely why we are suggesting you do the same to us.

Screenshots

Light or dark, desktop or phone

The app follows your system theme by default, with a toggle in the top bar and a three-way choice in Settings — the same arrangement this site uses.

The ihasmail sign-in screen

Sign-in

Stalwart mailbox credentials, with TOTP where it's enabled.

ihasmail inbox and conversation view in the dark theme

Inbox & conversation — dark

Three panes, collapsed conversation, labels with colours.

ihasmail inbox and conversation view in the light theme

Inbox & conversation — light

The same screen, following a light system preference.

The ihasmail reply composer

Reply composer

Identities, Reply-To, rich text, signature and quoted text.

ihasmail choosing recipients from the address books

Choosing recipients

Read the address books instead of remembering a name — yours and anyone's shared with you, several at a time.

ihasmail calendar, month view

Calendar

Month view, multiple calendars, colour categories.

ihasmail files view with a folder tree

Files

A folder tree, drag-and-drop between folders, and folders shared by other people beneath your own.

ihasmail contacts view

Contacts

Address books, groups, and a full JSContact editor — yours kept apart from anyone else's.

The ihasmail Sieve filter builder

Sieve filter builder

Also reachable from a message's right-click menu.

ihasmail on a phone

Mobile layout

Bottom tab bar, drawer navigation, full-screen composer.

All screenshots are taken against the built-in mock server with sample data — no real mailbox involved.

Sharing

Shared where you were already looking

Files, calendars and address books can be shared with other accounts on the same server, over JMAP Sharing. What somebody shares with you turns up in the part of the app it belongs to — not behind an account you have to switch into.

In place, not elsewhere

  • A shared folder is in Files, a shared calendar is in the calendar, a shared address book is in Contacts — each under Shared with me, beside your own
  • Their events draw in the grid in their own colour; their folders open in the same pane as yours
  • Contacts from a shared book are offered when you address a message, and can be browsed from the compose window along with your own
  • Attaching a file the server already holds costs no upload, however large it is — including one from somebody else's folder
  • New shares appear without signing out and in again

Yours to accept, and to withdraw

  • Anything shared with you waits under Available to add until you add it, and contributes nothing until then — an unadded calendar draws no events, an unadded address book puts nobody in your To field
  • That is not caution for its own sake: the server reports every collection in an account you can reach, so being handed one is not evidence anybody offered it
  • Sharing something of your own marks it as shared, so you can see at a glance what is out there
  • Stop sharing withdraws access from everyone at once, without walking a dialog and removing people one at a time
  • Per-person rights are still there when you want them — viewer, editor, or set by hand
  • Mail folders are not shareable. Stalwart accepts the share, stores it, and never delivers it, so ihasmail stopped offering something that only looked like it worked
  • Sharing reaches accounts on the same server. There are no public links: JMAP shares between accounts, and ihasmail keeps no storage of its own to serve a link from

Architecture

A client, not a second mail server

ihasmail holds no state of its own. Every mailbox, event, contact and file lives in Stalwart; the server in the middle exists to keep your password out of the browser and to speak JMAP on the page's behalf.

Browser

React SPA

JMAP client, push, and the zustand stores behind each view.

Your host

ihasmail server

Node + Hono. Session cookie in, sealed Basic auth out.

Upstream

Stalwart

The only source of truth. Mail, calendars, contacts, files.

What the backend is for

It authenticates against Stalwart's JMAP session endpoint and stores those credentials sealed with a key derived from the cookie secret — the server never persists a plaintext password, and the browser never sees one at all. From there it proxies /api/jmap, /api/blob, /api/upload, /api/events and /api/image, and serves the SPA under a strict CSP.

There is no database to install, back up or migrate. Stop the container and nothing has been lost but sessions.

Stalwart 0.16 or newer

0.16 replaced the REST management API with JMAP registry objects, reshaped FileNode, split its rights up and moved configuration into the store. ihasmail spoke to both generations for a while, which meant every call site had to ask which one had answered — and a wrong answer always had an older path to fall back on, so it failed quietly instead of loudly. One supported generation trades that for an error on the first call.

Sign-in now refuses an older server by name, once, and says plainly that the credentials were fine. Still on 0.15 and not ready to upgrade? The last release that runs on it is tagged stalwart-0.15-support, and it stays where it is.

Get the 0.15 release

  • Optional capabilities are still negotiated, not presumed
  • A missing one steps the feature aside rather than erroring
  • Live push via EventSource, with polling as a fallback
  • A built-in mock Stalwart for development and demos

Nothing to persist

The server has exactly one path that ever writes to disk: an optional session file. Switch it off and there is nothing left to write, so the container can run with no volume and no writable root filesystem at all.

  • No database to install, back up or migrate
  • No volume, and no writable root
  • An instance is its image plus an environment file
  • Replace the container to upgrade; there is nothing to carry over

Comparison

Less to run, less to patch, less latency

Not because the code is cleverer, but because the architecture doesn't spend its time working around IMAP. Roundcube, SnappyMail and SOGo were all designed in the IMAP era and carry its shape; ihasmail is JMAP-only from the ground up.

Looking for ihasmail and Bulwark Webmail? That one is JMAP-native too, so none of the below applies — it is a different comparison and it has its own page.

Dimension ihasmail JMAP-native Roundcube / SnappyMail / SOGo IMAP-based
round trips One request carries several method calls, and later calls can reference earlier results — fetch a page of messages, flag one, move another, in a single exchange. Commands pipeline, but anything that depends on a previous result still costs another round trip. The tax is per action, and it compounds over a mailbox.
sync Ask what changed since a state string and get exactly that, for mail, calendars and contacts alike. It is part of the protocol, not an extension — every JMAP server has it. IMAP does have this: CONDSTORE and QRESYNC resynchronise a mailbox, expunges included, in one round trip. They are optional extensions though, scoped one mailbox at a time, and only help when server and client both implement them.
data model JSON. The server has already decomposed MIME, so the client asks for the parts it wants and gets them. Raw MIME and IMAP responses, parsed client-side. It is where a great deal of legacy webmail's bug surface and slowness actually lives.
own database None. Stalwart's store is the only source of truth, so there is nothing to migrate, back up, or let drift. Roundcube requires a database. SOGo requires one and is a second stateful system beside the mail server. SnappyMail is the exception — it keeps user data in files.
what an instance is An image and an environment file. With IMMUTABLE=1 the container runs read-only with no volume, so two hosts given the same two things are running the same thing. Upgrading is replacing the container. An image plus whatever the database and the config directory have accumulated since install. Reproducing an instance means reproducing that state as well — which is why upgrades are a migration rather than a replacement.
constrained networks What JMAP was designed for. Batching and delta sync are the same mechanisms that make a phone on a bad connection feel workable. Efficiency comes from extensions bolted onto a protocol that predates the problem — or, in SOGo's case, from carrying Exchange ActiveSync alongside it. That buys Outlook and stock mobile clients, which is worth having; it is also a second protocol to implement and keep secure, for something JMAP already does on its own.

Where that shows up

  • Fewer moving parts. No database of its own means nothing to migrate, back up, or reconcile. You administer Stalwart, and that is the whole list.
  • Lower latency per action. The round-trip tax is the reason JMAP was built. ihasmail gets the win structurally, just by being on the protocol.
  • Less to audit. One protocol, no plugin surface, no compatibility layer for a second one. Roundcube's plugin ecosystem is its greatest strength and its largest attack surface at the same time.
  • Nothing to emulate. No pretending to be an Exchange server for the benefit of a client that wanted one.

Stack

What it's built on

Small, current, and boring where boring is a virtue.

LayerChoiceWhy
web/ Vite + React 19 + TypeScript The SPA: src/jmap (client, push, types), src/store (zustand), src/views, src/lib.
server/ Node + Hono A thin proxy and session holder. Small enough to read in an afternoon.
protocol JMAP No IMAP, no POP3, no SMTP fallback path. One protocol, used properly.
storage Stalwart There is no second store. Nothing to keep in sync, nothing to reconcile.
sanitiser DOMPurify + Shadow DOM Sender HTML renders isolated from the app's own styles and scripts.
deployment Docker One container, configured entirely by environment variables.
licence AGPL-3.0-or-later Run it as a service for other people and they get the source too.

Security & privacy

Your password never reaches the browser

A webmail client sits between someone's mailbox and the open internet. These are the parts that decision touches.

In the app

  • Server-side sessions with per-session encrypted upstream credentials — no password in the browser, ever
  • httpOnly, SameSite cookies; CSRF header plus Sec-Fetch-Site checks
  • Strict Content-Security-Policy, sandboxed blob downloads
  • Sender HTML sanitised with DOMPurify inside a Shadow DOM
  • Remote images blocked by default, with an SSRF-safe proxy when you allow them
  • Login rate limiting and the usual security headers

Self-service, in Settings

Change your password and manage app passwords — a separate one per device, revocable on its own, so losing a phone costs you that phone rather than every session you have.

Read receipts are worth calling out: when a sender asks for one, ihasmail offers to send a real RFC 8098 report — never automatically. Bulk mail, mailing lists and anything marked Auto-Submitted aren't offered one at all, and a receipt aimed somewhere other than the sender says so first.

License

AGPL-3.0-or-later, and why it changed

ihasmail was relicensed from GPL-3.0 to AGPL-3.0 in August 2026. Webmail is nearly always run as a network service rather than handed to anyone as a binary, and the AGPL's section 13 closes that gap: anyone running a modified ihasmail for other people has to offer them its source, which the GPL alone does not require.

That offer has to point at your source, not this one.

If you run a modified ihasmail, set SOURCE_URL to your own repository. The sign-in page and Settings › About both show it, so the people using your instance are told where the code they are actually running can be found.

Free software, no feature held back

There is no paid tier, no enterprise edition and nothing switched off behind a licence key. Copyright © 2026 Coffey Labs.

Read the licence

About

Built for one mailbox, then everyone else's

ihasmail exists because Stalwart is an excellent mail server with a genuinely modern protocol, and the web clients pointed at it were mostly IMAP clients wearing a JMAP hat. Building JMAP-first from the ground up turns out to change what the client can do: conversations, push, server-side scheduled send, free/busy lookup and Sieve round-tripping all stop being bolted on.

What runs today is a ground-up rewrite, against a live Stalwart 0.16.20. The earlier FastAPI/HTMX prototype has been removed entirely — only the logo survived.

Builds say what they are: 2026.8.30+pr129 is the date of the commit the image was built from, then the pull request it arrived through — or a short SHA, +g1fa6578, where a commit arrived some other way. The date is the commit's own rather than the build's, so rebuilding an old commit gives the version it had the first time, and a build reporting 0.0.0 is one nobody told what it was.

The number says nothing about Stalwart, deliberately. It used to: 2.16.x carried the generation it targeted in the middle, which leaves nowhere to go once Stalwart reaches 1.0 — 2.1 sorts below the 2.16 already deployed, so every upgrade would read as a downgrade. Which Stalwart a build needs is stated where it can be precise, in the install docs, instead of compressed into one digit.

A companion project, stalwart-migrator, handles the 0.15 → 0.16 upgrade that ihasmail now requires: it checkpoints every phase, refuses to start on the things that cannot be fixed mid-migration, and validates the server afterwards. That path is treacherous by hand — the store is migrated in place with no way back, and Stalwart's own converter drops settings without saying so. The live instance made the jump in eight seconds of downtime with nothing lost.

ihasmail is written and maintained by Coffey Labs. Issues, feature requests and patches are all welcome on GitHub.