Resolve the Inbox mailbox id before running Email/query. The previous query passed a JMAP result reference object directly into the inMailbox filter, which can make the preview endpoint return 502 and cause push notifications to fall back to the generic “New mail” text.
Bulwark Webmail
A modern, self-hosted webmail client for Stalwart Mail Server, built with Next.js and the JMAP protocol.
Screenshots
Overview
Bulwark is a full webmail suite, not just an inbox. It bundles the four apps most self-hosters end up wanting on the same login:
- Mail – threading, unified inbox, full-text search, Sieve filters, S/MIME, templates
- Calendar – month/week/day/agenda, recurring events, iMIP invitations, CalDAV subscriptions
- Contacts – multiple address books, groups, vCard import/export
- Files – Stalwart's JMAP FileNode storage with previews and folder upload
Plus the infrastructure around them: OAuth2 / OIDC SSO, TOTP 2FA, multi-account (up to 5 at once), 15 languages, PWA install, dark/light themes, a plugin system with an extension marketplace, and a admin dashboard.
Full feature list: FEATURES.md.
Quick Start
Docker
docker run -d -p 3000:3000 \
-e JMAP_SERVER_URL=https://mail.example.com \
ghcr.io/bulwarkmail/webmail:latest
Or with Docker Compose:
cp .env.example .env.local
# Edit .env.local – set JMAP_SERVER_URL
docker compose up -d
From Source
git clone https://github.com/bulwarkmail/webmail.git
cd webmail
npm install
cp .env.example .env.local
# Edit .env.local – set JMAP_SERVER_URL
npm run build && npm start
Development
npm run dev # Dev server with a mock JMAP server
npm run typecheck
npm run lint
Configuration
All variables are evaluated at runtime, so Docker deployments can be reconfigured without rebuilding. Edit .env.local:
# Required
JMAP_SERVER_URL=https://mail.example.com
# Optional
APP_NAME=My Webmail
Server listen address
HOSTNAME=0.0.0.0 # Default; use "::" for IPv6
PORT=3000
OAuth2 / OIDC
OAUTH_ENABLED=true
OAUTH_CLIENT_ID=webmail
OAUTH_CLIENT_SECRET= # optional, for confidential clients
OAUTH_CLIENT_SECRET_FILE= # path to a file containing the secret
OAUTH_ISSUER_URL= # optional, for external IdPs
Endpoints are auto-discovered via .well-known/oauth-authorization-server or .well-known/openid-configuration.
Session & settings sync
SESSION_SECRET= # openssl rand -base64 32
SESSION_SECRET_FILE=/session-secret # path to a file containing the secret
SETTINGS_SYNC_ENABLED=true
SETTINGS_DATA_DIR=./data/settings # mount as a volume in Docker
Credentials are encrypted with AES-256-GCM and stored in an httpOnly cookie (30-day expiry). Settings sync stores per-account preferences encrypted at rest and requires SESSION_SECRET.
Custom JMAP endpoint
ALLOW_CUSTOM_JMAP_ENDPOINT=true
Shows a "JMAP Server" field on the login form. External servers must CORS-allow the webmail origin.
Branding & PWA
APP_NAME=My Webmail
APP_SHORT_NAME=Webmail
APP_DESCRIPTION=Your personal mail
FAVICON_URL=/branding/favicon.svg
PWA_ICON_URL=/branding/icon.svg # falls back to FAVICON_URL
PWA_THEME_COLOR=#3b82f6
PWA_BACKGROUND_COLOR=#ffffff
APP_LOGO_LIGHT_URL=/branding/logo-light.svg
APP_LOGO_DARK_URL=/branding/logo-dark.svg
LOGIN_LOGO_LIGHT_URL=/branding/login-light.svg
LOGIN_LOGO_DARK_URL=/branding/login-dark.svg
LOGIN_COMPANY_NAME=My Company
LOGIN_WEBSITE_URL=https://example.com
LOGIN_IMPRINT_URL=https://example.com/imprint
LOGIN_PRIVACY_POLICY_URL=https://example.com/privacy
Extension directory
EXTENSION_DIRECTORY_URL=https://extensions.bulwarkmail.org
Enables the admin marketplace for browsing and installing plugins and themes.
Stalwart integration & logging
STALWART_FEATURES=true # password change, Sieve filters, etc.
LOG_FORMAT=text # "text" or "json"
LOG_LEVEL=info # error | warn | info | debug
Keyboard Shortcuts
| Key | Action |
|---|---|
j / k |
Navigate between emails |
Enter / o |
Open email |
Esc |
Close / deselect |
c |
Compose |
r / R |
Reply / Reply all |
f |
Forward |
s |
Star |
e |
Archive |
# |
Delete |
/ |
Search |
? |
Show all shortcuts |
Tech Stack
| Framework | Next.js 16 with App Router |
| Language | TypeScript |
| Styling | Tailwind CSS v4 |
| State | Zustand |
| Protocol | Custom JMAP client (RFC 8620) |
| i18n | next-intl |
| Icons | Lucide React |
Why Stalwart?
Stalwart is a Rust mail server with native JMAP support – not IMAP/SMTP with JMAP bolted on. It handles JMAP, IMAP, SMTP, and ManageSieve in a single self-hosted binary with no third-party dependencies.
Contributing
See CONTRIBUTING.md.
License
GNU AGPL v3. This repository preserves the original MIT attribution for the fork lineage in NOTICE.
Acknowledgments
Thanks to root-fr/jmap-webmail and @ma2t for the groundwork this project builds upon.




