# ihasmail
**A fast, friendly, Gmail-class webmail for [Stalwart Mail Server](https://stalw.art) โ built on JMAP, from the ground up.**
Mail, calendars, contacts, files and filters in a responsive single-page app
that works equally well on a desktop monitor and a phone. It talks only JMAP
(plus Stalwart's blob/upload/EventSource endpoints) โ no IMAP, no SMTP, no
database.
| | |
| --- | --- |
| ๐ **[ihasmail.org](https://ihasmail.org)** | What it is, what it looks like, the full feature list |
| ๐ **[docs.ihasmail.org](https://docs.ihasmail.org)** | [Installing](https://docs.ihasmail.org/install/) ยท [Configuring](https://docs.ihasmail.org/configure/) ยท [Using it](https://docs.ihasmail.org/using/) ยท [Shortcuts](https://docs.ihasmail.org/shortcuts/) ยท [Rebranding](https://docs.ihasmail.org/rebranding/) ยท [Troubleshooting](https://docs.ihasmail.org/troubleshooting/) |
| ๐งช **[KNOWN-ISSUES.md](KNOWN-ISSUES.md)** | What was verified live, and where Stalwart departs from a spec |
| ๐ฃ **[ROADMAP.md](ROADMAP.md)** | What ihasmail does not do, and why |
This file is for people working *on* ihasmail. Everything about running it
lives in the docs.
## Screenshots
*Taken against the built-in mock server (`npm run dev:mock`) with sample data โ no real mailbox involved.*
| | |
| --- | --- |
| **Inbox & conversation (dark)**  | **Inbox & conversation (light)**  |
| **Composer**  | **Calendar**  |
| **Contacts**  | **Sieve filter builder**  |
More, including the mobile layout, on [ihasmail.org](https://ihasmail.org/#screenshots).
## What's in it
- **Mail** โ three-pane Gmail-style layout, conversation view, virtualised list, labels, undo, Gmail search operators and keyboard shortcuts, Sieve rules from a message's context menu, sanitised HTML with remote images blocked, read receipts, invitations and RSVP, multi-composer rich-text editing with signatures, scheduled send and undo send
- **Calendar** โ JMAP Calendars / JSCalendar: month/week/day/agenda, recurrence, attendees and free-busy, colour categories
- **Contacts** โ JMAP Contacts / JSContact: address books, groups, full editor, vCard import/export
- **Files** โ JMAP FileNode: browse, upload, download, rename, move, delete
- **Settings that follow the account**, not the browser โ kept in a `settings.json` in the account's own JMAP Files, so ihasmail itself stays stateless
- **Platform** โ installable PWA, Web Push with ihasmail closed, `mailto:` handler, no credentials in the browser, strict CSP, SSRF-safe image proxy
The long version is on [ihasmail.org](https://ihasmail.org/#features); how to
drive each one is in [Using ihasmail](https://docs.ihasmail.org/using/).
## Requires Stalwart 0.16 or newer
Sign-in refuses anything older, by name. 0.16 replaced the REST management API
with JMAP registry objects, changed the shape of `FileNode`, split its rights up
and moved configuration into the store; supporting both generations meant a
wrong guess had somewhere to fall back to, so it failed *quietly* โ and that
reached production. With one supported generation a wrong guess is a loud error
on the first call.
- Still on 0.15? The last release that runs on it is tagged [`stalwart-0.15-support`](https://github.com/LINUXexpert-org/ihasmail/releases/tag/stalwart-0.15-support).
- Upgrading? [stalwart-migrator](https://github.com/LINUXexpert-org/stalwart-migrator) does it in place, checkpointing every phase and validating afterwards. The live instance moved 0.15.5 โ 0.16.19 with eight seconds of downtime and nothing lost.
## Quick start (Docker)
```bash
cp .env.example .env
# edit: STALWART_URL=https://mail.example.com and APP_SECRET=$(openssl rand -base64 48)
docker compose up --build -d
# โ http://localhost:8080 (put Caddy/nginx in front for TLS; see Caddyfile.example / nginx.example.conf)
```
Users sign in with their Stalwart mailbox credentials. **An account with
two-factor authentication needs an app password**, created in Stalwart's own
settings โ Stalwart accepts a TOTP code only through an OAuth flow and offers no
password grant, so no client holding a username and password can exchange them
plus a code for a token.
Full instructions, TLS, and every environment variable:
[Installing](https://docs.ihasmail.org/install/) ยท
[Configuring](https://docs.ihasmail.org/configure/).
## Architecture
```
browser โโ(same-origin /api/*)โโโบ ihasmail server (Node + Hono) โโ(JMAP over HTTPS)โโโบ Stalwart
React SPA โข session cookie โ Basic auth
JMAP client + stores โข /api/jmap, /api/blob, /api/upload, /api/events (SSE), /api/image
```
- `web/` โ Vite + React 19 + TypeScript SPA. `src/jmap` (client, push, types), `src/store` (zustand: session, mail, compose, contacts, calendar, files, sieve, settings), `src/views`, `src/lib` (sanitiser, search parser, Sieve codec, locale-aware dates, vCard, โฆ).
- `server/` โ Node/Hono backend: authenticates against Stalwart's JMAP session endpoint, seals the credentials with a key derived from the cookie secret, proxies JMAP/blob/SSE, serves the SPA under a strict CSP. `src/mock/` is an in-memory fake Stalwart for development and demos.
Capabilities used: `core`, `mail`, `submission`, `vacationresponse`, `sieve`,
`contacts`(+`parse`), `calendars`(+`parse`), `principals`(+`availability`),
`quota`, `blob`, `filenode`, EventSource push, plus Stalwart's own
`urn:stalwart:jmap` (read-only). Features degrade gracefully when one is
missing.
## Development
Requirements: Node โฅ 20.10 (22 recommended), npm โฅ 10.
```bash
npm install
npm run dev # real Stalwart (STALWART_URL in .env) โ server :8080, Vite :5173
npm run dev:mock # built-in mock Stalwart (demo@example.com / demo), mock on :8788
npm run dev:mock:no-future-release # mock that advertises FUTURERELEASE and drops every hold
npm run typecheck # tsc for both packages
npm test # vitest (web) + node:test (server)
npm run build # web/dist + server/dist
npm start # serve the production build
```
Open http://localhost:5173 in dev, or http://localhost:8080 for the production
build. Running it for real is covered in
[Installing](https://docs.ihasmail.org/install/) and
[Configuring](https://docs.ihasmail.org/configure/).
### The mock
An in-memory fake Stalwart 0.16 โ enough JMAP to develop and demo against
without a real mailbox. It reproduces the things a naive fake would get wrong,
because each cost a live debugging session: `urn:stalwart:jmap` advertised
**per-account** rather than session-level, identity signatures capped at 2047
**bytes**, and `CalendarEvent/set` speaking Stalwart's vocabulary rather than
RFC 8984's. Two switches: `MOCK_NO_FUTURE_RELEASE=1` advertises FUTURERELEASE
and then drops every hold; `MOCK_NO_REGISTRY=1` omits the Stalwart capability so
the sign-in refusal can be tested.
### Version numbers
`ihasmail v2.16.84` โ `2` is ihasmail's own major, `16` the Stalwart generation
this build targets, `84` the pull request the commit came from. The first two
live in the root `package.json`; the third comes from git at build time, since
it does not exist until the PR has merged. A commit that did not arrive through
a PR carries the last number plus its short SHA โ `2.16.84+g1fa6578`.
```bash
node scripts/version.mjs # the version for the current checkout
docker build --build-arg IHASMAIL_VERSION="$(node scripts/version.mjs)" -t ihasmail:2.16 .
```
`.dockerignore` excludes `.git` deliberately, so an image build cannot work this
out for itself โ pass it in. Left out, the build falls back to the base version
from `package.json`, so a version with no PR number means whoever built the
image did not pass one.
### Deploying
[`deploy.example.sh`](deploy.example.sh) is a single-host Docker deploy: it
fetches, refuses anything held back by `.deploy-hold`, shows what is about to be
introduced and asks, rebuilds with the right version baked in, replaces the
container, waits for healthy, then prunes all but the newest
`IHASMAIL_KEEP_VERSIONS` images โ never the one actually running.
```bash
./deploy.sh # origin/main, asks before shipping new commits
./deploy.sh --dry-run # run the guards and stop
./deploy.sh v2.16.84 --yes # a named ref, no prompt (there is no tty over ssh)
```
`--yes` does not override a hold; clearing one means deleting its line.
## Contributing
[CONTRIBUTING.md](CONTRIBUTING.md) ยท [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) ยท
[SECURITY.md](SECURITY.md) โ please report vulnerabilities privately.
## License
Copyright (C) 2026 LINUXexpert.org โ AGPL-3.0-or-later. See
[LICENSE](LICENSE).
ihasmail was relicensed from GPL-3.0 to AGPL-3.0 on 2026-08-25: 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.
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. See
[Rebranding](https://docs.ihasmail.org/rebranding/).