Pre-release. This is a test build for Bulwark Lite. Not recommended for production. The latest Docker tag stays on 1.10.0. To try the server build, use ghcr.io/bulwarkmail/webmail:1.11.0-beta.1.
Bulwark Lite
Bulwark Lite is a static build of the webmail. It has no Node server: the browser talks to your JMAP server directly.
bulwark-lite-1.11.0-beta.1.zip: unpack it on any static web host (nginx, Caddy, S3, Pagesβ¦). Set your JMAP server in config.json. The JMAP server must allow CORS from the webmail origin.
bulwark-lite-stalwart.zip: a Stalwart Application bundle. Install it as an Application in Stalwart and it serves itself under the configured prefix (e.g. /webmail).
The Lite zips are attached once the Build Static Lite workflow finishes (a few minutes after publishing).
Please report problems with the Lite build in the issue tracker.
Also in this pre-release
New "Flat fields" theme
Icon set migrated from Lucide to Tabler Icons
Hardened Lite login, deep-link replay and settings gating
This release fixes six vulnerabilities reported by Jan Kahmen (turingpoint). Please update.
Mail: HTML mail could run script in the webmail origin. cid: references were rewritten to blob: URLs that kept the sender's Content-Type, and the reverse proxy skipped every security header β CSP included β on app paths whose last segment contains a dot, although the signed-in mail, calendar, contacts and files routes render there. Blob URLs are now retyped as inert, link clicks from the message frame are gated by scheme, and the proxy only skips headers for an explicit static-asset allowlist (GHSA-xvjh-v9c6-qcvc, thanks @kah-ja)
Auth: POST /api/auth/session and POST /api/auth/stalwart-context minted identity cookies without checking the supplied credentials against the JMAP server, so anyone could obtain a cookie for an arbitrary username and read that user's server-side settings. Credentials are now verified upstream before a cookie is issued: Basic credentials are bound to the account they authenticate, Bearer tokens to the session's username and identity. Deployments whose webmail container cannot reach the JMAP server no longer receive identity cookies, so cross-device settings sync stops working there (GHSA-wxcm-j4jc-9fxq, thanks @kah-ja)
Plugins: The /plugin-sandbox runtime trusted whichever window posted the first message, so a foreign site could window.open() it, post its own init, and run code under the route's unsafe-eval CSP in the app origin β with or without plugins enabled. The sandbox now only accepts its framing window, and the proxy refuses to serve the route outside an iframe or when plugins are disabled (GHSA-96cx-gx36-3g79, thanks @kah-ja)
Auth: Encrypted payloads now carry a purpose, so a WOPI editor access token can no longer be presented as a jmap_stalwart_ctx session cookie (GHSA-cqqx-mjcf-mh55, thanks @kah-ja)
Auth: Reject cross-site requests to /api/auth/*. A malicious page could POST the victim's browser into an attacker-controlled account (session fixation) (GHSA-qvr9-m8cq-7wvg, thanks @kah-ja)
Mail: Strip CR/LF and other control characters from the header values of generated read receipts. A crafted, RFC 2047-encoded subject could inject additional headers into the receipt (GHSA-w38p-hpqv-g89c, thanks @kah-ja)
Auth / Branding: Pin the resolved IP at connect time for stored custom JMAP endpoints and branding URLs too, closing the DNS-rebinding gap left after GHSA-24w9-8r42-8jwm
Features
Search: Global search across mail, contacts, calendar and files β query parser, ranking and cross-account providers, a search palette, and a search tab in the Pro shell with avatars, tinted icons and structured previews (#641); hits open through the owning login on every surface (#847)
Files: Office document editing through WOPI β Collabora Online, OnlyOffice and EuroOffice (#425); the demo Files drive ships office document fixtures and a word-processor icon
Calendar: Freely scrolling month, week and day views (#759), infinite scroll in the agenda view, and a setting to turn free scrolling off
Calendar: Recurring occurrences use Stalwart's synthetic ids (#140)
Calendar: Attendee free/busy via Principal/getAvailability
Calendar: Invitations and updates surface from CalendarEventNotification, and invitations are organized as the default ParticipantIdentity
Mail: Per-message HTML / plain-text toggle (#1022)
Mail: Share mail folders with other users via mail:share; share notifications appear as toasts
Mail: Search hits are highlighted with SearchSnippet/get
Mail: Pushes are resolved with Email/changes and Mailbox/changes deltas instead of refetching the list
Mail: Attachments show on list rows and open from there (#947, thanks @shukiv)
Mail: Filter advanced search by message size
Mail: Deleting a non-empty folder offers to delete its messages along with it
Mail: "Clear search when switching folders" setting (#852, thanks @shukiv)
CI: The test suite runs on every push and pull request (#647)
Dev: FileNode and blob round-trip in the mock JMAP server, and WOPI on the same-origin dev server
Changes
Docs: Installer section removed from the README and formatting cleaned up
Security policy: New vulnerability report email address
Fixes
Filters: Render the "Keep" action as fileinto "INBOX" (#1027)
Mail: List tagged mail from every account in the tag view, not just the selected folder's account (#1038)
Mail: Dragging a message out of the list could hang the tab when generated .eml file names collided (#1039)
Mail: Preserve label filtering during mailbox refreshes (#1017, thanks @ctaoist)
Mail: Hide body-embedded cid: parts declared as application/octet-stream from the attachment list (#1005, thanks @dealerweb)
Mail: Stop stretching images that carry their own max-width (#1034, thanks @shukiv), and let sender tables keep theirs (#790)
Mail: Refetch the body when JMAP truncates the displayed part instead of rendering a blank message (#928, thanks @hildebrandttk)
Mail: Auto-detect text direction in the read, print, plain-text, thread and .eml preview views (#663, thanks @shukiv)
Mail: Open search hits from shared folders in the right account (#923)
Mail: Collapse search hits for the same server object reached through several logins (#641)
Mail: Guard quick search against stale responses (#872, thanks @vj1235432)
Mail: Only show the unified mailbox section when it can be populated (#843), and stop it missing accounts that had not connected yet (#959, thanks @hildebrandttk)
Mail: Surface Email/set failures on delete and move (#956)
Mail: Report a missing archive mailbox instead of failing silently (#578), and archive shared-inbox mail into the owner's archive (#889)
Mail: Mark as spam from the viewer after the message left the list (#695)
Mail: Sort folders in the role assignment dropdown (#984)
Mail: Add the missing "Scheduled" folder role label (#495)
Mail: Wire the x shortcut to thread expansion (#683)
Mail: Keep the reading-mode toggle mounted so toolbar buttons stop jumping (#964)
Mail: Stop the batch toolbar hiding the message you just selected (#948, thanks @shukiv), keep rows in place when it opens, and drop the duplicate selection checkbox
Mail: Align the unread dot with the first line (#715, thanks @lucletoffe) and centre the sender avatar against the row (#953, thanks @shukiv)
Mail: Use Simplified Chinese for the selected-messages label (#786)
Send: Send from the address a message was delivered to (#991, thanks @rotterp)
Send: Set the answered flag when a reply goes out with a send delay (#985)
Composer: Upload attachments through the composing identity's account (#943)
Composer: Pro compose tabs default their From to the open mailbox (#990, thanks @rotterp)
Calendar: Jump to the day picked in the mini calendar (#1037, thanks @dealerweb)
Calendar: Wait for the JMAP client before loading the account principal, and recognise a refused principal read by its JMAP error type (#1036, thanks @dealerweb)
Calendar: Dedupe participants, resolve contact names across alias domains, and show the organizer's status (#986, thanks @ibayue)
Calendar: Linkify URLs in the event description (#968, thanks @lucletoffe)
Calendar: Normalize task progress states (#994, thanks @mulatta) and omit progressUpdated from the task completion payload (#958, thanks @sanitz)
Calendar: Preserve task alarms that the edit dialog does not show (#504)
Calendar: Report calendar clear failures instead of counting zero (#434)
Calendar: Make the iCal subscription size limit configurable and show the real error (#692)
Calendar: Paginate the event fetch on import so UID deduplication sees all existing events (#113)
Contacts: Strip the local-account prefix from address-book ids on contact update (#1043)
Contacts: Set name.full on all write paths so vCards carry the mandatory FN (#430)
Contacts: Create new contacts in the selected address book (#940, thanks @ponchofiesta)
Accounts: Detect HTTP/2 from the initial navigation timing so accounts are not capped at five (#1003, thanks @lucamzanon)
Auth: Reject wrong passwords server-side so the browser never shows its Basic Auth dialog (#969)
Auth: Stop retrying token refreshes that fail permanently (#972)
Auth: Normalize the OAuth discovery base so a session JMAP_SERVER_URL refreshes (#971, thanks @thejdubb02)
Auth: Use the selected server's issuer for SSO discovery (#952)
Auth: Retry the JMAP session fetch when a redirect drops the auth header (#892)
Auth: Drop max_age=0 from OIDC re-authentication requests (#938)
Auth: Require a session for the translate API (#903)
Mobile: Keep the actions panel below the status bar and pad attachment preview overlays for the iOS PWA safe area (#936)
Mobile: Render plain-text-only mail as text in the thread view (#489)
Mobile: Stop the More menu flashing open when a message is opened
Mobile: Dismiss the search panel once a search runs, open the folder drawer from the right in RTL (#944, thanks @shukiv), and keep the account switcher header on screen
PWA: Focus the client before navigating on notification click (#914, thanks @bitfactory-dk)
Push: Resolve push previews for shared and group mailboxes (#839)
Plugins: Allow sandbox chunk loading with CORS (#922, thanks @mulatta)
Plugins: Fail fast when the plugin storage database is blocked (#840)
Plugins: Refill missing managed bundles from the server (#636)
Plugins: Translations for plugins installed from the marketplace (#939, thanks @paulhenry46)
Settings: Index newer settings and the flat calendar toggles in the settings search
Settings: Invalidate the persisted update status after an upgrade
UI: Readable native <select> option lists in dark themes (#999)
i18n: German update (#1031, thanks @GyroGearl00se), "Forward as attachment" in more languages (#1016, thanks @dulinux), toolbar keys for nb and zh-TW (thanks @ibayue), and managed sidebar-app keys for zh-TW
i18n: Preserve locale cookie precedence and normalize Chinese proxy locale detection (thanks @kchuang1015); keep basePath when normalizing a Chinese Accept-Language
Calendar / Auth: Pin the resolved IP address at socket-connect time when fetching caller-supplied URLs (iCalendar subscriptions, JMAP login and TOTP token-exchange servers). The public-host check used to run before fetch() opened its socket, so an attacker who controlled DNS for a hostname could rebind it to loopback, RFC-1918 or cloud-metadata addresses between the check and the connect and read up to 10 MB of the internal response through the unauthenticated /api/fetch-ical endpoint. Redirect targets are now validated the same way (GHSA-24w9-8r42-8jwm, thanks @Tike00)
Features
Push: Re-sync existing push registrations in the background on app start, so registrations created before the delivery filter existed β or whose Junk mailbox id went stale β get repaired without re-enabling notifications
Fixes
Push: Stop sending notifications for spam β the push subscription now carries a JMAP emailPush delivery filter that excludes $junk and the Junk mailbox (needs a server advertising the emailPush capability, e.g. Stalwart β₯ 0.16.16; older servers keep the previous behaviour)
Navigation: Forward every request header through the proxy β Next 16.3's RSC header check rejected the stripped router headers and sent navigations into a 307 redirect loop (#919)
Calendar: Stop emitting RSCALE=GREGORIAN;SKIP=OMIT on plain Gregorian recurrence rules β DAVx5 rejected them as invalid and Android sync broke (#805, thanks @hildebrandttk)
Mail: Search suggestions with recent searches and contact autocomplete (#845)
Mail: Render plain-text emails in the app font by default, with a monospace option (#830)
Mail: Pull-to-refresh indicator while dragging the list down (#826)
Mail: Redesigned unread favicon badge β a compact keyline badge
Mobile: Swipe message rows left or right for quick actions β archive, delete, toggle read, toggle star, or spam, configurable per direction, RTL-aware
Pro: Reworked split-screen shell with per-pane tab strips, drag & drop between panes, and pane-scoped overlays
Pro: Folder tabs via drag & drop
Pro: The address bar follows the focused tab, and deep links are delivered live to already-mounted surfaces
Composer: Real byte progress for attachment uploads, stock and plugin-offloaded; cancel now aborts the transfer itself
Calendar: Moving an event's start moves the end with it, keeping the event's length
Contacts: Trusted Senders address book enabled by default on contacts-capable accounts
Settings: Custom time zone setting that overrides browser detection (#755)
Login: Server dropdown on the OAuth-only login screen (#799)
Push: New-mail notifications grouped per account with a "+N more messages" line instead of one notification per message
Push: Per-device revoke for push subscriptions (#841)
Admin: Configurable Stalwart admin access to the dashboard (#870)
Admin: Push relay picked from an admin-defined list instead of a free URL field
Branding: OpenGraph/Twitter link previews with a generated card image
Plugins: onBeforeComposeOpenToReply and sibling hooks let a plugin edit an email before it populates the composer for reply or forward
Plugins: getPublicKeyFromWKD
Plugins: Privileged plugins can fetch a byte range of a blob
Plugins: progressFileId on api.http.post so an offloaded upload reports byte progress to the composer chip
Plugins: isActive on AccountResponse
Plugins: Label settings and label reordering exposed to extensions
Plugins: JMAP keyword helpers and gateway keywords exposed to extensions
Plugins: Mailbox refresh hook
Dev: AddressBook/set in the dev mock JMAP server
Changes
Plugins: The PRF getOrCreate flow is split into separate get and create steps for better authenticator compatibility (#851)
Dependencies: Next 16.3.3, pdfjs-dist 6.2.108, DOMPurify 3.4.14 (npm audit)
Fixes
Send: Route scheduled sends to the account that owns the submission β mail scheduled from a shared address can now be listed, cancelled, and rescheduled instead of going out silently (#874)
Send: Split recipient lists whose angle brackets never close without dropping recipients
Send: generateMessageId crashed on insecure origins (crypto.randomUUID undefined), failing the send after the draft save
Composer: Keep attachments when re-opening a draft, also in the Pro draft tab, and destroy old draft versions only after a successful create or send (#849)
Composer: Keep the signature in saved drafts and embed it into re-opened drafts (#848)
Composer: Double-click unlocks the embedded signature for editing
Composer: Keep already written text when applying a template (#540)
Composer: Preselect the shared folder's identity for new messages
Composer: Namespace all accounts consistently in the Pro composer identity list
Composer: Opening a mailto: link runs the unsaved-draft dialog instead of replacing the draft outright
Composer: Keep the fresh-compose tab title clear of the selected email subject
Composer: Clear the viewer when sending destroys the displayed draft
Reply: Honour an external Reply-To even on a self-sent message
Mail: Flip $junk/$notjunk keywords on spam and not-spam (#850)
Mail: Remove keywords with null rather than false in Email/set, per RFC 8620
Mail: Escape the JSON Pointer in keyword patches so nested tags like work/clients patch the right keyword
Mail: Route keyword writes β tags, pins, flags β to the selected shared account so they persist
Mail: Route shared-folder management to the owner account, scoped to one server
Mail: Open the right conversations in a shared mailbox (#814)
Mail: Route multi-account email lookups and invitation parsing by source account (#847), and parse invitations in directly viewed shared folders against the folder owner (#867)
Mail: Folders containing a system folder name no longer disappear from the sidebar (#771)
Mail: Folder drag & drop can move folders into other parents (#855)
Mail: Keep the folder tree when a refresh burst hits maxConcurrentRequests (#780)
Mail: Stop All-Mail and cross-account views emptying on delete, star, or mark-read (#791)
Mail: Keep just-read or unstarred mail in the open Unread/Starred view
Mail: Search folder filter defaults to all folders and persists (#788)
Mail: Fixed-width read/unread toolbar button so buttons don't jump when a message opens (#864)
Mail: Transparent hover-action background on tagged rows
Mail: Enforce the external media preference on plugin-rendered bodies (#797)
Mail: Apply the data: URI allowlist to media tags and srcset candidates too
Mail: Make label reordering atomic
Mail: Pass PDF bytes to pdf.js instead of fetching the blob: URL that CSP connect-src blocks (#871)
Mail: Include email templates in cross-device settings sync (#825)
Mail: Toast store crashed on insecure origins, breaking every post-action acknowledgement
Mobile: Reach the tag and move submenus in the more-actions menu (#779)
Mobile: iOS Safari no longer zooms the viewport on every input focus (#838)
Calendar: Click and double-click create events at the clicked slot instead of near the current time
Calendar: Save and Cancel are available when an event is created, and the toolbar no longer overflows in edit mode
Calendar: Edit a single recurring occurrence via a one-shot override patch
Calendar: Hide tasks-only calendars from the event calendar
Calendar: Exclude subscription and read-only calendars from event creation (#762)
Calendar: Rights-first event editability, including alias organizers
Calendar: Pin supported-calendar-component-set on created calendars (#760)
Calendar: Gate first-touch calendar and contacts requests to stop duplicate default calendars (#907)
Calendar/Contacts: Namespace all accounts consistently on switch, so multi-account address-book aggregation and calendar selection survive an account switch
Contacts: Import vCard dates as RFC 9553 PartialDate and map common X- extensions (#224)
Files: Decode percent-encoded FileNode names from WebDAV-created nodes (#869)
Files: Reset the account-scoped Files drive on every account switch
Accounts: Refresh the account display name from the Stalwart principal on login, restore, and switch (#900)
Auth: Reuse the cached access token on session restore (#552)
Auth: Refresh TOTP-minted tokens with the default client id when no OAuth client is configured (#873)
Auth: Store the session cookie for relative JMAP server URLs
Security: Close IPv6 transition-address and redirect bypasses in the endpoint guard
Security: Add embedded custom app origins to CSP frame-src (#787)
Security: Per-account isolation for encryption at rest
JMAP: Cap live SSE streams per tab and keep exactly one stream per client, so many logins can't starve JMAP requests (#702)
JMAP: Check the specific capability a request declares (principals:owner), not a broader one
JMAP: Resolve relative session URLs without corrupting URI templates
Settings: Merge per-account maps on server load, fixing the compose identity switch
Settings: Leaving the Pro interface returns to the surface in use and keeps the settings scroll position
UI: Position portalled popovers before first paint to stop the layout flash on open
Push: Recreate the push subscription on re-register (#841)
i18n: Key parity across all 24 locales, Catalan and Mongolian registered in the client provider, and scoped translation hooks instead of relative namespace paths
Docs: Lengthen the example SESSION_SECRET so it meets the minimum length
A GitHub Actions incident left the 1.8.0 release build queued indefinitely, so no Docker image was ever published for that tag. 1.8.1 re-runs the release with the same code, plus one plugin slot that merged in the meantime.
Security: Manage S/MIME and PGP public keys and configure Stalwart encryption at rest from account security settings
Notifications: Background notification onboarding, sequenced after the PWA install prompt
Navigation: Deep links for mail, calendar, contacts, files, and settings, with screen-reader improvements
Settings: Always show the Unified Mailbox switch in Layout settings
i18n: Catalan translation
i18n: Localized editor toolbar across every locale
Plugins: Contact API β contact.get, contact.create, contact.update, contact.search
Plugins: contact-cryptokeys UI slot, behind the ui:contact-cryptokeys permission, so a plugin can render a contact's crypto keys in place of the built-in list
Plugins: user.getAccounts and user.getIdentities
Plugins: user.logout method and logout hook
Plugins: Crypto API β public-key management and encryption-at-rest control on the privileged tier
Plugins: onBeforeBlobUpload can offload an attachment to external storage
Plugins: Binary Blob/File bodies for api.http.post
Plugins: upfiles.get moved behind email:blob-read, off the privileged tier
Dev: Mock JMAP defaults now include nested tags
Changes
Mail: The "Reset to defaults" button is gone from tag settings β one stray click wiped a carefully built tag list, with no confirmation and no undo
Fixes
Send: Send through the identity's own account client so DKIM matches the From domain (#461)
Send: Split Name <addr> recipients into the JMAP name and email fields (#672)
Send: Time out stalled JMAP requests so a send can't hang forever (#702)
Mail: Keep inline images when replying to application/octet-stream cid parts (#543)
Mail: Reply on your own thread message no longer re-addresses the original recipients (#703)
Mail: Empty folder no longer stops after 500 emails (#711)
Mail: Move messages across accounts from the "Move to" context menu, preserving read state and deferring source removal to Stalwart
Mail: Stop resurrecting deleted rows in the mailbox refresh merge
Mail: Keep the message/rfc822 attachment visible after inline unwrapping
Mail: Strip sender and recipient names from forward-as-attachment filenames
Mail: Open mailto: links in the built-in composer
Mail: Spell out the full tag path in drag-and-drop toasts, so Personal/Receipts and Work/Receipts no longer read as the same tag
Mail: Act on current email state in the context menu's mark-as-read instead of a stale copy
Mail: Match the selected-row tint between dark and light mode
Mail: Restore lost animations after the Tailwind config move
Drafts: Restore the sender identity when reopening a draft
Calendar: Stop re-adding the organizer to the attendee list on every save (#731)
Calendar: Stop re-probing shared accounts that have no calendar access
Calendar: Route the parse dump through the debug logger
S/MIME: The built-in S/MIME implementation has been removed from core and re-delivered through the new generic crypto plugin hooks (privileged same-origin plugin tier). S/MIME signing, encryption, decryption, certificate management, and the related settings UI now live in a plugin rather than the main app. Deployments that relied on built-in S/MIME must install the S/MIME crypto plugin to retain those features.
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