Bulwark Webmail: a JMAP client that assumes you already run Stalwart
Self-hosted JMAP webmail for Stalwart Mail Server. Mail, calendar, contacts, and files in one client.
At a glance
- What is it?
- Bulwark bundles mail, calendar, contacts and files into one Next.js front end that speaks JMAP to Stalwart Mail Server. It is a good fit for self-hosters already committed to Stalwart, and a poor fit for anyone who wants a client to pick the server for them.
- Who is it for?
- Adopt Bulwark if you already run Stalwart Mail Server and want mail, calendar, contacts and files behind one login instead of stitching together Roundcube plus a separate CalDAV/CardDAV client. Do not adopt it if your mail server is not Stalwart or if you expect the client to discover and configure your backend for you.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The gap Bulwark fills: Stalwart has a server but no shipped web client
Stalwart Mail Server speaks JMAP, and JMAP is a capable protocol: it covers mail, calendars, contacts and file nodes in one session. What it does not give you is a user interface. The README positions Bulwark as exactly that missing layer, describing it as a self-hosted webmail client for Stalwart built with Next.js and the JMAP protocol. The audience is narrow and clearly stated by the feature set: people who run their own mail server and would otherwise assemble a mail client, a calendar app, a contacts app and a file browser, each with its own login and its own sync story. Bulwark's pitch is consolidation. The README says the four apps share one login, one settings store, and one admin dashboard, and that SSO, 2FA, multi-account, 27 languages, PWA install, themes and plugins apply across all four. That is the whole argument. If you are not running Stalwart, there is nothing here for you, because the project does not claim to talk to anything else.
One Next.js app, four JMAP-backed surfaces, and a settings layer that decides where config lives
The architecture visible in the README is a single Next.js application that renders four feature areas on top of one JMAP session. Mail covers threading, a unified inbox, cross-account All accounts views, full-text search, Sieve filters, S/MIME and templates. Calendar covers month, week, day and agenda views, recurring events, iMIP invitations and CalDAV subscriptions. Contacts covers multiple address books, groups, vCard import and export, and autocomplete inside the composer. Files maps to what the README calls Stalwart's JMAP FileNode storage, with previews and folder upload. The interesting part is not the feature list but the configuration model underneath it. Most deployments are configured through a setup wizard on first launch and then the admin dashboard, and those values live in an admin config directory rather than in .env.local. Environment variables still work and the README recommends them for read-only or immutable infrastructure. The precedence rule is explicit: an environment variable always wins over the admin-managed value, so setting JMAP_SERVER_URL hides that field from the wizard and locks it in the admin UI. Nearly all variables are evaluated at runtime, which means a Docker deployment can be reconfigured without a rebuild. The exceptions are the NEXT_PUBLIC_* variables, which Next.js bakes in at build time. That single distinction is the most operationally important thing in the README, and it is easy to miss.
Getting it running: Docker first, source second, mock server for development
The README gives three paths. The shortest is a single container: docker run -d -p 3000:3000 ghcr.io/bulwarkmail/webmail:latest, or docker compose up -d. On first launch you open http://localhost:3000 and the setup wizard takes over. Installs that already define JMAP_SERVER_URL skip the wizard and keep the environment-managed flow. From source the sequence is git clone, cd webmail, npm install, then npm run build && npm start, then the same wizard at http://localhost:3000. For development the README points at a built-in mock JMAP server, so you do not need a real mail server: cp .env.dev.example .env.local, then npm run dev. The other scripts are npm run typecheck, npm run lint, npx vitest run for unit tests, and npm run test:integration, which the README describes as a Dockerized Stalwart plus Playwright suite documented in integration/README.md. The configuration surface splits into a few groups. Server binding uses HOSTNAME (default 0.0.0.0, with :: for IPv6) and PORT (3000). OAuth2/OIDC uses OAUTH_ENABLED, OAUTH_ONLY to hide the username and password form entirely, OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, OAUTH_CLIENT_SECRET_FILE, OAUTH_ISSUER_URL, OAUTH_AUTHORIZE_URL, OAUTH_SCOPES, OAUTH_EXTRA_SCOPES and AUTO_SSO_ENABLED. Sessions and settings use SESSION_SECRET, SESSION_SECRET_FILE, SETTINGS_SYNC_ENABLED and SETTINGS_DATA_DIR, and the README notes that credentials are encrypted with AES-256-GCM and stored in an httpOnly cookie with a 30-day expiry. Two of those keys deserve attention before you expose anything: SESSION_SECRET is generated with openssl rand -base64 32, and SETTINGS_DATA_DIR is described as something to mount as a volume in Docker, which implies settings do not survive a container replacement otherwise.
The SSRF guard that will break your split-DNS deployment
OAUTH_ALLOW_PRIVATE_ENDPOINTS is the config key most likely to cost someone an afternoon. The README states that it is off by default as an SSRF guard, and that endpoints are auto-discovered through .well-known/oauth-authorization-server or .well-known/openid-configuration. The instruction is blunt: enable it only for split-DNS deployments where the issuer's public hostname resolves to an internal IP. That is a real trade-off rather than a bug. Turning it on widens what the discovery step will accept, and the project chose a secure default over a convenient one. If your identity provider is reachable from the webmail container only through an internal address, you will hit this. If your IdP is public, leave it alone. The same security posture shows up in the release history: version 1.9.2 is titled as a security fix for DNS-rebinding SSRF under GHSA-24w9-8r42-8jwm. A client that makes server-side requests on behalf of a browser session has a genuine attack surface here, and the project has already had to patch it once. That is worth knowing before you put it on the public internet behind only a reverse proxy.
Where Bulwark is the wrong tool
Three constraints are visible in the material. First, the server dependency is absolute. Bulwark is a client for Stalwart Mail Server, and the Files app is described as reading Stalwart's JMAP FileNode storage. Nothing in the README suggests an IMAP fallback or a generic JMAP profile that would let you point it at a different implementation. If you run Dovecot, Postfix, Cyrus or a hosted provider, Bulwark is not a partial fit, it is not a fit. Second, the licence metadata is inconsistent. The repository's licence field reads NOASSERTION while the README badge and the shield image both say AGPL v3. That mismatch is not a reason to avoid the project, but it does mean the metadata is not a reliable signal and anyone evaluating redistribution terms needs to read the LICENSE file itself. Third, the deployment model has a sharp edge around settings. The README says most configuration lives in an admin config directory rather than .env.local, and that environment variables override the admin-managed values. That means a container that loses its mounted SETTINGS_DATA_DIR volume can come back with configuration the admin dashboard no longer controls, because an environment variable is silently winning. The README also notes that NEXT_PUBLIC_* variables are baked in at build time, so anything in that family cannot be changed by restarting a container. Neither of these is documented as a failure mode, but both follow from the stated precedence rules.
What you would be replacing, and how the approach differs
The natural comparison is Roundcube, the long-standing PHP webmail client that most self-hosters reach for first. The difference is not cosmetic. Roundcube talks IMAP and SMTP and gets calendars and contacts through separate plugins and separate CalDAV/CardDAV endpoints, so a deployment typically means a PHP stack plus a calendar server plus a card server, each with its own authentication. Bulwark takes the opposite position: one protocol, JMAP, one session, one login, and the server does the work of exposing mail, calendar, contacts and files through that single protocol. That is why the Stalwart dependency is so tight. Bulwark can present four apps behind one auth flow precisely because Stalwart already serves all four over JMAP. The trade is portability for cohesion. A Roundcube deployment can be pointed at almost any IMAP server with a config change. A Bulwark deployment cannot be pointed anywhere else at all. If you are already on Stalwart, that trade costs you nothing and saves you three logins. If you are not, it is disqualifying.
Upgrade cost, telemetry defaults, and what the release cadence tells you
The release list shows three versions inside roughly a month: 1.9.0 on 2026-08-25, then 1.9.1 and 1.9.2 on 2026-08-26, the latter a security fix. That is a fast-moving project, and it means upgrade cost is not zero. The 1.9.1 title mentions a fix for a Next 16.3 navigation redirect loop and DAVx5 calendar sync, and 1.9.2 covers a DNS-rebinding SSRF advisory plus spam-free push notifications. Two of three releases in that window address regressions or security, which is normal for active software but relevant if you pin versions and upgrade quarterly. On the privacy side, telemetry is opt-in and off by default. The README states that heartbeats carry version, platform, bucketed account counts and feature toggles, with no email addresses, hostnames or IPs, and that setting BULWARK_TELEMETRY to either value locks the choice and disables the admin toggle. TELEMETRY_DATA_DIR defaults to ./data/telemetry and is described as something to mount as a volume, so the instance id and consent state persist. Licence-wise, the README badge and the LICENSE link both point at AGPL v3, which carries source-availability obligations if you modify the software and let users interact with it over a network. That is the standard reading of AGPL, not legal advice, and the NOASSERTION field in the repository metadata means you should confirm the actual file contents before relying on either label.
Who should deploy this, and what to check before you do
Bulwark is for the operator who has already chosen Stalwart Mail Server and wants the client side solved without running four applications. The README's own framing supports that: it bundles the four apps most self-hosters end up wanting, behind one login and one admin dashboard. It is not for anyone still deciding on a mail server, and it is not for anyone who needs IMAP compatibility as an escape hatch. The verification steps are concrete. Confirm whether your OAuth discovery path resolves to an RFC-1918 address, because that determines whether you need OAUTH_ALLOW_PRIVATE_ENDPOINTS and whether you are accepting the wider SSRF exposure that comes with it. Confirm that SESSION_SECRET and SETTINGS_DATA_DIR are set and mounted, since a missing volume plus an overriding environment variable produces a configuration state the admin dashboard cannot correct. Read the LICENSE file directly rather than trusting the NOASSERTION field. And check the CHANGELOG before pinning a version, given that two of the three most recent releases shipped within a day of each other and one of them was a security fix.
Editorial conclusion
Adopt Bulwark if you already run Stalwart Mail Server and want mail, calendar, contacts and files behind one login instead of stitching together Roundcube plus a separate CalDAV/CardDAV client. Do not adopt it if your mail server is not Stalwart or if you expect the client to discover and configure your backend for you. Before deploying, verify two things: whether your reverse proxy and DNS setup would trip the OAuth private-endpoint guard, and whether the AGPL v3 obligations in the LICENSE file are acceptable for how you plan to modify and redistribute it. The GitHub licence field reads NOASSERTION while the README badge says AGPL v3, so read the file rather than the metadata.
Community notes