Opt-in tabbed multi-pane interface for power users. Open multiple mail, calendar, contacts, and file views side-by-side, drag tabs to reorder or split panes at the edges, and work across all logged-in accounts in one shell, cross-account email moves, a unified inbox with search, account-split calendar/contacts/files sidebars, and a per-account "From" dropdown in the composer. Enable from Settings β Appearance; the proInterface preference is per-device and not synced.
Pro mode is experimental and we need your feedback to shape it. If something feels off, breaks, or is missing, please don't hesitate to open an issue or start a discussion on GitHub!
Breaking Changes
Plugins: Plugins now run inside a null-origin iframe sandbox and talk to the host over a postMessage RPC bridge. The in-process plugin runtime is gone; the bundled in-tree plugins have been migrated. Third-party plugins built against the old in-process API need to be ported to the sandboxed runtime.
Plugins: Server-managed bundles must be Ed25519-signed by the host and approved by an admin before they load. The host public key is served from /api/plugin-signing-pubkey and each bundle response carries the signature in the X-Bundle-Signature header. User-uploaded bundles still load unsigned, but managed marketplace and dev-folder bundles do not.
Plugins: bundleHash is now a full SHA-256 over the bundle. Legacy short hashes are migrated on first load; any out-of-band tooling that pinned the old hash format needs to be updated.
Features
Pro: Tabbed shell with drag-to-reorder, drag-to-edge to split, side-by-side panes, and pane-aware responsive layout with a scoped sidebar overlay
Pro: Auto-redirect to the Pro shell when Pro mode is on; proInterface is kept per-device instead of syncing
Pro: Multi-account mail sidebar with client routing and a per-account mailbox cache
Pro: Unified mailbox always visible, with full-text search
Pro: Cross-account email moves
Pro: Multi-account calendar sidebar split into owned vs shared per account
Pro: Multi-account contacts and a cross-account file picker
Pro: Composer From dropdown grouped by account
Plugins: Per-plugin admin approval workflow with Ed25519 bundle signing verified on load
Plugins: Marketplace update flow for installed plugins and themes
Setup: Allow the setup wizard over plain HTTP with a dismissable warning gate
Setup: Warn when the JMAP URL points at a local-only host
Account: List and reorder logged-in accounts from settings (#282)
Mail: Mobile handoff page with JMAP authentication verification for cross-device OAuth
First-launch web setup wizard. New installs no longer need to hand-edit .env.local - point a browser at the container and the wizard probes the JMAP server(s), configures OAuth/OIDC, generates the session secret, accepts branding uploads, and provisions the initial admin password. Admin storage is now split into ADMIN_CONFIG_DIR (operator-authored, mountable read-only after setup) and ADMIN_STATE_DIR (runtime audit log and login timestamps); the legacy ADMIN_DATA_DIR keeps working for existing installs.
Features
Setup: Web setup wizard with multi-step flow: Server, Auth, Security, Logging, Branding, Review, Admin
Setup: Admin config/state directory split with optional ADMIN_CONFIG_READONLY for immutable deployments (#226)
Setup: File uploads on the wizard branding step
Setup: Redesigned review step with grouped summary and an advanced toggle for the full config
Setup: Require explicit confirmation when JMAP probe finds no session
Mail: Drag attachments out of the viewer to the local file system (#267)
Mail: Configurable signature position β above or below quoted text (#266)
Mail: Signature position is now searchable from the email behavior settings
Mail: Show avatar in Focused list for compact density and above
Mail: Align Focused list preview with other layout previews
Compose: From-header override in the composer with catch-all auto-reply, replies to an alias on a domain you own pre-fill the alias as the sender even when it isn't a configured identity (#246)
Performance
Mail: Prefetch initial email data on login
Auth: Parallelize login round-trips and drop redundant JMAP re-verify
Fixes
Auth: Skip upstream JMAP reverify for trusted URLs (#237)
Auth: Show account identity in the switcher header instead of the sending alias
Compose: Fall back to the primary identity signature on reply
Setup: Drop redundant first-login banner about removing ADMIN_PASSWORD (#222)
UI: Consistent notice cards for server probe results
New: Help shape Bulwark Webmail. Each instance now sends a lightweight daily heartbeat (version, platform, bucketed account counts, feature toggles - never message data or PII) so we can see which platforms and features actually get used and prioritize fixes where they matter most. You're in control: opt out any time from Admin β Telemetry or by setting BULWARK_TELEMETRY=off. Full schema in the privacy notice.
Features
Telemetry: Anonymous instance telemetry, on by default. Reports schema version, platform, bucketed account counts, and feature toggles only - disable from the admin UI, with BULWARK_TELEMETRY=off, or by clearing the endpoint
Telemetry: Track unique logins (HMAC'd per instance, 90-day retention) so the heartbeat can report bucketed account totals without storing usernames
Plugins: Theme API v2 with token compiler and skin slot
Plugins: Extension preview page and detailed extension info API
Calendar: Right-click context menu on empty calendar space
Docker: Persistent named volume for telemetry data so the instance id and admin's consent choice survive container upgrades
Fixes
Security: Block telemetry endpoint from pointing at internal/loopback hosts (validation + DNS-rebind re-check at fetch time)
Plugins: New composer-sidebar slot and ui:composer-sidebar permission β plugins can now render a panel on either side of the New Message dialog. See repos/subway-surfers for an example
Plugins: Manifests can declare frameOrigins β a strictly-validated list of https://host origins the plugin needs to embed. The proxy reads the union from enabled plugins and merges it into the host CSP frame-src, so the host CSP no longer needs to know about specific embed providers
Calendar/Contacts: JMAP sharing for calendars and address books
i18n: Czech language support
Fixes
Security: Validate URLs before outbound fetch
Calendar: Prevent drag creation on touch events in the time grid
Contacts: Emit RFC 9553 name kinds and decode QUOTED-PRINTABLE in vCard import (#224, #187)
Mail: Hide preview line in compact density to match settings preview (#223)
Proxy: Inline matcher for Next.js proxy and drop unnecessary Node.js runtime config
i18n: Portuguese fixes for "ficheiro" and "contactos" variants
Self-service portal now needs Stalwart 0.16+: Stalwart dropped its self-service HTTP API in 0.16.0 and replaced it with JMAP. Bulwark Webmail only talks to the new JMAP endpoint, so the self-service portal (account settings, app passwords, API keys) requires Stalwart 0.16 or newer. STALWART_API_URL is deprecated, these actions go through the normal JMAP session.
Features
Stalwart: Migrate Stalwart management API to JMAP x: methods for Stalwart 0.16
Admin: Add API Keys management and IP allowlist for App Passwords
Contacts: Revamp contact detail view with filters, photo, print, and duplicate actions
Logging: Add logging categories for better log management
Fixes
Security: Harden security with CSP enforcement, SSRF redirect validation, reenabled S/MIME chain verify, IP spoofing prevention, and PDF iframe sandbox
Security: Harden proxy authentication and SSRF defenses
Security: Block plugins with dangerous JS patterns and enforce strict session secret length validation
S/MIME: Add self-signed certificate detection and update status messages for S/MIME signatures
Email: Auto-focus input fields in email composer for improved user experience (#126)
Mailbox: Prevent orphaning of nested mailboxes by restricting deduplication to root-level folders
JMAP: Strip server-immutable fields from updates before sending to JMAP (#128)
Files: Update file feature disabled messages and add stability warnings
i18n: Add missing translation keys to all non-English locales