Inline CID images were replaced with raw JMAP download URLs that require authentication. When the browser loaded these as <img src>, the server responded with WWW-Authenticate: Basic, triggering a native login popup. - Add fetchBlobAsObjectUrl() to JMAPClient that fetches blobs via authenticated request and returns blob: object URLs - Update email-viewer and thread-conversation-view to fetch CID images asynchronously with auth, using blob: URLs instead of raw server URLs - Add ALLOWED_URI_REGEXP to DOMPurify config so blob: URLs are not stripped during sanitization - Add tests for fetchBlobAsObjectUrl and CID/blob URL sanitization
Bulwark Webmail
A modern, self-hosted webmail client for Stalwart Mail Server.
Built with Next.js and the JMAP protocol.
Screenshots
|
|
|
|
Features
- Read, compose, reply, reply-all, forward with rich HTML rendering
- Threading — Gmail-style inline expansion with thread navigation
- Draft auto-save with discard confirmation
- Attachments — upload, download, and inline preview
- Search — full-text with JMAP filter panel, search chips, and cross-mailbox queries
- Batch operations — multi-select, archive, delete, move, tag
- Color tags/labels and star/unstar
- Virtual scrolling for large mailboxes
- Quick reply from the viewer
Calendar
- Month, week, day, and agenda views with mini-calendar sidebar
- Drag-and-drop rescheduling, click-drag creation, edge-resize (15-min snap)
- Recurring events with edit/delete scope (this / this and following / all)
- Participant scheduling — iTIP invitations, organizer/attendee UI, RSVP
- Inline calendar invitations in email viewer — auto-detect
.ics, RSVP, import - iCalendar import with preview and bulk create
- Notifications with configurable sound and alert persistence
- Real-time sync via JMAP push
Contacts
- Contact management with JMAP sync (RFC 9553/9610) and local fallback
- Contact groups with group expansion and member management
- vCard import/export (RFC 6350) with duplicate detection
- Autocomplete in composer (To/Cc/Bcc)
- Bulk operations — multi-select, delete, group add, export
Filters & Automation
- Server-side email filters via JMAP Sieve Scripts (RFC 9661)
- Visual rule builder — conditions (From, To, Subject, Size, Body…) and actions (Move, Forward, Star, Discard…)
- Raw Sieve editor with syntax validation
- Vacation responder with date range scheduling and sidebar indicator
- Email templates — reusable, categorized, with placeholder auto-fill (
{{recipientName}},{{date}}, etc.)
Security & Privacy
- External content blocked by default — trusted senders list for auto-load
- HTML sanitization via DOMPurify with XSS prevention
- SPF/DKIM/DMARC status indicators
- OAuth2/OIDC with PKCE for SSO (Keycloak, Authentik, or built-in)
- TOTP two-factor authentication
- "Remember me" — AES-256-GCM encrypted httpOnly cookie (opt-in)
- Security headers — CSP with per-request nonce, X-Frame-Options, Referrer-Policy
- Newsletter unsubscribe (RFC 2369)
Interface
- Three-pane layout — sidebar, email list, viewer
- Dark and light themes with intelligent email color transformation
- Responsive — desktop sidebar + mobile bottom tab bar
- Keyboard shortcuts — full navigation without a mouse
- Drag-and-drop email organization between mailboxes
- Right-click context menus, toast notifications with undo, form validation with shake feedback
- Accessibility — WCAG AA contrast, reduced-motion support, focus trap, screen reader live regions
Internationalization
8 languages: English · Français · 日本語 · Español · Italiano · Deutsch · Nederlands · Português
Automatic browser detection with persistent preference.
Identity Management
- Multiple sender identities with per-identity signatures
- Sub-addressing —
user+tag@domain.comwith contextual tag suggestions - Identity badges in viewer and email list
Quick Start
Docker (recommended)
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 # Start dev server (mock JMAP server included)
npm run typecheck # Type checking
npm run lint # Linting
Configuration
Edit .env.local:
# Required
JMAP_SERVER_URL=https://mail.example.com
# Optional
APP_NAME=My Webmail
All variables are runtime — Docker deployments can be configured without rebuilding.
OAuth2/OIDC (SSO)
OAUTH_ENABLED=true
OAUTH_CLIENT_ID=webmail
OAUTH_CLIENT_SECRET= # optional, for confidential clients
OAUTH_ISSUER_URL= # optional, for external IdPs (Keycloak, Authentik)
Endpoints are auto-discovered via .well-known/oauth-authorization-server or .well-known/openid-configuration.
Remember Me
SESSION_SECRET=your-secret-key # Generate with: openssl rand -base64 32
Credentials encrypted with AES-256-GCM, stored in an httpOnly cookie (30-day expiry).
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 mail server written in Rust with native JMAP support — not IMAP/SMTP with JMAP bolted on. It handles JMAP, IMAP, SMTP, and ManageSieve in a single binary. Self-hosted, no third-party dependencies.
Contributing
See CONTRIBUTING.md for guidelines.
Roadmap
See ROADMAP.md for planned features and current status.
License
Acknowledgments
Thanks to root-fr/jmap-webmail and @ma2t for doing most of the groundwork that this project builds upon.





