GitHub - bulwarkmail/webmail: JMAP Webmail built for the 21st Century. A modern, self-hosted email client for Stalwart Mail Server powered by the JMAP protocol. Email, calendar, contacts and files. Fast, private, and open source.

4 min read Original article ↗
Bulwark Webmail

Bulwark Webmail

A modern, self-hosted webmail client for Stalwart Mail Server, built with Next.js and the JMAP protocol.

License: AGPL v3 Discord Version Docker Grafana


Screenshots

Mail view
Calendar Contacts
Calendar – month, week, day, and agenda views with drag-to-reschedule, iMIP invitations, and CalDAV subscriptions. Contacts – multiple address books, groups, vCard import/export, and autocomplete in the composer.
Themes Plugins
Themes – bundled color themes or upload your own as ZIP bundles; admins can enforce presets. Plugins – extend the client with bundled or third-party plugins installed from a .zip file.
Light mode Settings
Light mode – full theme support with intelligent color transformation for HTML emails. Settings – appearance, identities, filters, templates, security, and more.

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
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.