web/src/lib had grown to 85 flat modules -- 42% of the web source, about
12,800 lines -- with one subdirectory (smime/) to its name. The tell was
that a naming prefix had taken over a directory's job: eight adminX.ts
files sat adjacent because alphabetical order put them there, not because
anything said they belonged together.
lib/admin/ adminAccess, adminDashboard, adminDirectory, adminDomains,
adminGroups, adminLists, adminRoles, adminTenants
lib/calendar/ appointment, availabilityWindow, eventDrag, ics, recurrence
Tests move with their modules into lib/admin/__tests__ and
lib/calendar/__tests__, which is what views/ already does. describeRules
stays in lib/__tests__: it checks that sieve's describeRule and
recurrence's agree, so it belongs to neither.
recurrence.ts joins the calendar group and archiveDate.ts does not, which
is the opposite of the first guess from the filenames. archiveDate picks
the Archive/2026/09 mailbox for a message -- mail, not calendar --
while recurrence reads JSCalendarRecurrenceRule. schedule.ts is scheduled
*send*, so it stays put too. birthdays.ts is left alone deliberately: it
is read off the contact cards and only rendered by the calendar, so it
belongs to whichever of the two you ask.
docs/ held no documentation. It held ten JPEGs and the two scripts that
capture them, while the actual documentation is a separate site in the
ihasmail.org repository -- so anyone opening docs/ expecting prose found
a headless-Chrome driver. The images are now screenshots/, and the two
capture scripts join the other .mjs tooling in scripts/, which is where a
generator belongs. Renaming docs/ to screenshots/ wholesale would have
produced screenshots/screenshots/inbox-dark.jpg.
No behavior changes: every import was already on the @/ alias, so this is
path rewrites and nothing else.
115 lines
5.9 KiB
Markdown
115 lines
5.9 KiB
Markdown
<p align="center">
|
||
<img src="web/public/img/logo.png" alt="ihasmail" width="150">
|
||
</p>
|
||
|
||
<p align="center">
|
||
<strong><a href="https://demo.ihasmail.com">Try the demo</a></strong><br>
|
||
<sub>A working copy with an invented mailbox behind it — no sign-up, nothing real, nothing kept.</sub>
|
||
</p>
|
||
|
||
<p align="center">
|
||
<a href="LICENSE"><img alt="License: AGPL-3.0-or-later" src="https://img.shields.io/badge/license-AGPL--3.0--or--later-2dd4bf?style=flat-square"></a>
|
||
<a href="https://stalw.art" target="_blank" rel="noreferrer"><img alt="Requires Stalwart 0.16 or newer; tested against 0.16.22" src="https://img.shields.io/badge/Stalwart-0.16.22-6366f1?style=flat-square"></a>
|
||
<a href="https://docs.ihasmail.org" target="_blank" rel="noreferrer"><img alt="Documentation: docs.ihasmail.org" src="https://img.shields.io/badge/docs-docs.ihasmail.org-0ea5e9?style=flat-square"></a>
|
||
<a href="https://coffeylabs.org" target="_blank" rel="noreferrer"><img alt="by Coffey Labs" src="https://img.shields.io/badge/by-Coffey%20Labs-0f766e?style=flat-square"></a>
|
||
</p>
|
||
|
||
# ihasmail
|
||
|
||
**Immutable webmail for [Stalwart Mail Server](https://stalw.art).** Mail,
|
||
calendars, contacts, files and filters in one app that works as well on a phone
|
||
as on a desktop — and a container with nothing to persist.
|
||
|
||
ihasmail talks only JMAP to Stalwart. There is no database, no IMAP or SMTP,
|
||
and with `IMMUTABLE=1` no writable filesystem either: everything durable,
|
||
settings included, belongs to Stalwart, so the container is disposable.
|
||
|
||
| | |
|
||
| --- | --- |
|
||
| 🌐 **[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/) |
|
||
| 📋 **[FEATURES.md](FEATURES.md)** | Everything it does, feature by feature, with the capability each one needs |
|
||
| 🧪 **[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 |
|
||
|
||
## Screenshots
|
||
|
||
| | |
|
||
| --- | --- |
|
||
| **Inbox & conversation (dark)**  | **Inbox & conversation (light)**  |
|
||
| **Composer**  | **Calendar**  |
|
||
| **Contacts**  | **Sieve filter builder**  |
|
||
|
||
Taken against the built-in mock with sample data. More, including the phone
|
||
layout, on [ihasmail.org](https://ihasmail.org/#screenshots).
|
||
|
||
## What's in it
|
||
|
||
- **Mail** — conversations, labels, search operators, keyboard shortcuts, scheduled and undo send, invitations and RSVP, filters made from a message
|
||
- **Calendar** — month, week, day and agenda views, recurrence, attendees and free-busy
|
||
- **Contacts** — address books, groups, vCard import and export
|
||
- **Files** — browse, upload, move, share
|
||
- **Signature checking** — S/MIME signed mail verified as you read it
|
||
- **Settings that follow the account**, kept in the account's own storage on Stalwart
|
||
- **On a phone** — swipe to archive or delete, pull to refresh, hold to select
|
||
- **Administration** — a dashboard, accounts, groups, mailing lists, roles, tenants and domains, each shown only when the Stalwart role allows it
|
||
- **Ten interface languages and twelve themes** — the nine translations are marked Beta until a native speaker has read them
|
||
- **Platform** — installable PWA, Web Push, `mailto:` handler, no credentials in the browser, strict CSP
|
||
|
||
The long version is [FEATURES.md](FEATURES.md) and
|
||
[ihasmail.org](https://ihasmail.org/#features).
|
||
|
||
## Requirements
|
||
|
||
**Stalwart 0.16 or newer** — sign-in refuses anything older, by name. Tested
|
||
against 0.16.22; what changed in each release is in
|
||
[KNOWN-ISSUES.md](KNOWN-ISSUES.md).
|
||
|
||
- **No Stalwart yet?** [ihasmail-oneshot](https://github.com/Coffey-Labs/ihasmail-oneshot) deploys a new Stalwart and ihasmail together on one host, in one command.
|
||
- **On Stalwart 0.15?** [stalwart-migrator](https://github.com/Coffey-Labs/stalwart-migrator) upgrades it in place, or stay on the [`stalwart-0.15-support`](https://github.com/Coffey-Labs/ihasmail/releases/tag/stalwart-0.15-support) release.
|
||
|
||
## 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 a reverse proxy in front for TLS
|
||
```
|
||
|
||
Or pull the published image, `ghcr.io/coffey-labs/ihasmail`. Releases are
|
||
weekly, so it is usually a few days behind `main`.
|
||
|
||
People sign in with their Stalwart mailbox credentials. **An account with
|
||
two-factor authentication needs an app password**, created in Stalwart's own
|
||
settings.
|
||
|
||
Everything else — TLS, running immutably, several Stalwart servers, settings
|
||
the installation decides, every environment variable — is in
|
||
[Installing](https://docs.ihasmail.org/install/) and
|
||
[Configuring](https://docs.ihasmail.org/configure/).
|
||
|
||
## Development
|
||
|
||
```bash
|
||
npm install
|
||
npm run dev:mock # built-in mock Stalwart ([email protected] / demo)
|
||
npm test
|
||
```
|
||
|
||
Architecture, the mock's switches and how versions are numbered are in
|
||
[CONTRIBUTING.md](CONTRIBUTING.md#development-setup).
|
||
|
||
## 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 Coffey Labs — AGPL-3.0-or-later. See [LICENSE](LICENSE).
|
||
|
||
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/).
|