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 -dFrom 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 lintConfiguration
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
Subpath / reverse proxy mount
To serve the webmail at a subpath (e.g. https://example.com/webmail):
NEXT_PUBLIC_BASE_PATH=/webmail NEXT_PUBLIC_LOCALE_PREFIX=always # avoids next-intl rewrite loops
Unlike most other variables, NEXT_PUBLIC_BASE_PATH is read at build time because Next.js bakes it into emitted asset URLs. To use it with the published Docker image, build your own image with the variable set:
docker build --build-arg NEXT_PUBLIC_BASE_PATH=/webmail -t bulwark-webmail .Then point your reverse proxy at the container without stripping the prefix - the app expects to receive requests under /webmail/... and serves all routes (/webmail/api/..., /webmail/_next/static/..., /webmail/sw.js, etc.) accordingly.
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.




