JMAP Webmail
A webmail client for Stalwart Mail Server, built with Next.js and the JMAP protocol.
Why Stalwart?
Stalwart is a mail server written in Rust with native JMAP support, not IMAP/SMTP with JMAP added as an afterthought. It handles JMAP, IMAP, SMTP, and ManageSieve. Self-hosted, no third-party dependencies.
Stalwart on GitHub | Documentation
Features
- Read, compose, reply, reply-all, and forward
- HTML rendering with DOMPurify sanitization and sandboxed iframe for complex emails
- Attachment upload and download
- Draft auto-save with discard confirmation
- Threading with inline expansion
- Mark as read/unread, star/unstar
- Archive and delete with configurable behavior
- Color tags/labels with sidebar counts
- Search with JMAP filter panel, search chips, cross-mailbox queries
- Virtual scrolling for large lists
- Empty folder (one-click empty Junk/Trash with batch progress)
- Sender info panel (click sender name to view contact, add to contacts, search)
- API retry with exponential backoff for transient failures
Interface
- Three-pane layout with dark and light themes
- Responsive (desktop sidebar + mobile bottom tab bar + mobile action bar)
- Keyboard shortcuts
- Drag-and-drop email organization
- Multi-select emails (checkboxes, shift-click) with batch move/delete toolbar
- Folder management (create, rename, move, delete) from sidebar context menu
- Drag-and-drop folder reparenting
- Right-click context menus (long-press on touch devices)
- Extra-compact, compact, regular, and comfortable density options
- Resizable sidebar (drag, touch, keyboard)
- Animations that respect
prefers-reduced-motion - Infinite scroll pagination
- Toast notifications with undo support
- Form validation with shake feedback
- Safe area insets for notched devices
- Screen reader live regions
Real-time
- Push notifications via JMAP EventSource
- Live unread counts
- Email arrival notifications
- Connection status indicator
Identity management
- Multiple sender identities with per-identity signatures
- Sub-addressing (user+tag@domain.com) with tag suggestions
- Identity badges in viewer and list
Address book
- Contact management with search and filtering
- JMAP server sync (RFC 9553/9610) with local fallback
- Email autocomplete in composer
- Contact groups with group expansion
- vCard import/export (RFC 6350) with duplicate detection
- Bulk operations (multi-select, delete, group add, export)
Calendar
- JMAP Calendar (RFC 8984) with capability detection
- Month, week, day, and agenda views
- Event create, edit, delete with recurrence and reminders
- Participant scheduling with iTIP invitations and RSVP
- Inline calendar invitation banner in email viewer (.ics detection, RSVP, import)
- Multi-day events, column-based overlap layout
- Mini-calendar sidebar with calendar visibility toggles
- Locale-aware date formatting
- Settings for first day of week, time format (12h/24h), default view
- Drag-and-drop rescheduling with time snap
- Click-drag on empty slots to create events
- Resize events by dragging (15-minute snap)
- Double-click quick create
- Event duplication (+1 day offset)
- Recurring event edit/delete scope (this, this and following, all)
- iCalendar (.ics) file import with preview
- Real-time updates via JMAP push
- Event notifications with configurable sound
Email templates
- Reusable templates organized by category
- Placeholder variables (
{{recipientName}},{{date}}, etc.) with auto-fill - Template picker in compose toolbar with search and filter
- Template manager in settings
Email filters
- Server-side filtering with JMAP Sieve Scripts (RFC 9661)
- Visual rule builder: conditions (From, To, Subject, Size, Body...) and actions (Move, Forward, Mark read, Star, Discard, Reject...)
- Raw Sieve editor with syntax validation
- Auto-save with rollback on failure
- Drag-and-drop rule reordering
- Only shown when the server supports Sieve
Vacation responder
- JMAP VacationResponse with date range scheduling
- Settings tab for message configuration
- Sidebar indicator when active
Security and privacy
- External content blocked by default
- Trusted senders list for automatic image loading
- HTML sanitization (DOMPurify)
- SPF/DKIM/DMARC status indicators with plain-language tooltips
- Session-based auth, no password storage by default
- TOTP two-factor authentication
- "Remember me" with AES-256-GCM encrypted httpOnly cookie (opt-in)
- OAuth2/OIDC with PKCE for SSO (opt-in, RP-initiated logout)
- External IdP support (Keycloak, Authentik) via configurable issuer URL
- CORS misconfiguration detection with detailed error messages
- Shared folder support
- Newsletter unsubscribe (RFC 2369)
- CSP, X-Content-Type-Options, X-Frame-Options, Referrer-Policy headers
Internationalization
- 8 languages: English, French, Japanese, Spanish, Italian, German, Dutch, Portuguese
- Automatic browser language detection
- Persistent language preference
Deployment
- Pre-built Docker images on Docker Hub and GHCR (amd64/arm64)
- Multi-stage build with standalone output
- Runtime environment variables (no rebuild needed)
- Health check endpoint
- Structured logging (text/JSON)
- Update check on startup (server logs only, no client exposure)
Tech stack
- Next.js 16 with App Router
- TypeScript
- Tailwind CSS v4
- Zustand for state management
- Custom JMAP client (RFC 8620)
- next-intl for i18n
- Lucide React icons
Getting started
Prerequisites
- Node.js 18+
- A JMAP-compatible mail server (Stalwart recommended)
Installation
git clone https://github.com/root-fr/jmap-webmail.git
cd jmap-webmail
npm install
cp .env.example .env.localConfiguration
Edit .env.local:
# App name displayed in the UI
APP_NAME=My Webmail
# Your JMAP server URL (required)
JMAP_SERVER_URL=https://mail.example.comThese are runtime environment variables, read at request time. Docker deployments can be configured without rebuilding. Legacy NEXT_PUBLIC_* variables still work as fallbacks.
OAuth2/OIDC (optional)
To enable SSO login alongside Basic Auth:
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. If your JMAP server delegates auth to an external IdP, set OAUTH_ISSUER_URL to the IdP's base URL (e.g., https://keycloak.example.com/realms/mail).
To disable Basic Auth:
OAUTH_ONLY=trueRemember me (optional)
To enable "Remember me" for Basic Auth login:
SESSION_SECRET=your-secret-key # Generate with: openssl rand -base64 32When set, a "Remember me" checkbox appears on the login form. Credentials are encrypted with AES-256-GCM and stored in an httpOnly cookie (30-day expiry).
Development
npm run dev # Start dev server
npm run typecheck # Type checking
npm run lint # LintingProduction
npm run build
npm startDocker
# Pre-built image
docker run -p 3000:3000 -e JMAP_SERVER_URL=https://mail.example.com rootfr/jmap-webmail:latest
# From GHCR
docker run -p 3000:3000 -e JMAP_SERVER_URL=https://mail.example.com ghcr.io/root-fr/jmap-webmail:latest
# With docker compose
cp .env.example .env.local
# Edit .env.local with your JMAP_SERVER_URL
docker compose up -d
# Build from source
docker build -t jmap-webmail .
docker run -p 3000:3000 -e JMAP_SERVER_URL=https://mail.example.com jmap-webmailKeyboard shortcuts
| Key | Action |
|---|---|
j / k | Navigate between emails |
Enter / o | Open selected email |
Esc | Close viewer / deselect |
c | Compose new email |
r | Reply |
R / a | Reply all |
f | Forward |
s | Toggle star |
e | Archive |
# / Delete | Delete |
u | Mark as unread |
/ | Focus search |
x | Expand/collapse thread |
Ctrl+Shift+T | Insert template |
? | Show shortcuts help |
Screenshots
|
Login
|
Inbox
|
|
Email Viewer
|
Compose
|
|
Dark Mode
|
Settings
|
Contributing
See CONTRIBUTING.md for guidelines.
Roadmap
See ROADMAP.md for planned features.
Acknowledgments
- Stalwart Labs for the mail server
- The JMAP working group for the protocol spec
License
MIT. See LICENSE.





