docs: rework README with logo, improved layout, and updated features
This commit is contained in:
@@ -1,181 +1,196 @@
|
|||||||
|
<div align="center">
|
||||||
|
|
||||||
|
<img src="app/favicon.ico" alt="Bulwark Webmail" width="80" />
|
||||||
|
|
||||||
# Bulwark Webmail
|
# Bulwark Webmail
|
||||||
|
|
||||||
A webmail client for [Stalwart Mail Server](https://stalw.art/), built with Next.js and the JMAP protocol.
|
A modern, self-hosted webmail client for [Stalwart Mail Server](https://stalw.art/).<br/>
|
||||||
|
Built with Next.js and the JMAP protocol.
|
||||||
|
|
||||||
## Why Stalwart?
|
[](LICENSE)
|
||||||
|
[](CHANGELOG.md)
|
||||||
|
[](https://ghcr.io/bulwarkmail/webmail)
|
||||||
|
|
||||||
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.
|
</div>
|
||||||
|
|
||||||
[Stalwart on GitHub](https://github.com/stalwartlabs/mail-server) | [Documentation](https://stalw.art/docs/)
|
---
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td width="50%">
|
||||||
|
|
||||||
|
<img src="screenshots/02-inbox.png" width="100%" alt="Inbox — three-pane layout with sidebar, email list, and viewer">
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td width="50%">
|
||||||
|
|
||||||
|
<img src="screenshots/05-dark-mode.png" width="100%" alt="Dark mode">
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td width="50%">
|
||||||
|
|
||||||
|
<img src="screenshots/04-compose.png" width="100%" alt="Compose with templates and autocomplete">
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td width="50%">
|
||||||
|
|
||||||
|
<img src="screenshots/06-settings.png" width="100%" alt="Settings">
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>More screenshots</summary>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td width="50%">
|
||||||
|
|
||||||
|
<img src="screenshots/01-login.png" width="100%" alt="Login page">
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td width="50%">
|
||||||
|
|
||||||
|
<img src="screenshots/03-email-viewer.png" width="100%" alt="Email viewer with thread expansion">
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</details>
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
### Email
|
### Mail
|
||||||
|
|
||||||
- Read, compose, reply, reply-all, and forward
|
- **Read, compose, reply, reply-all, forward** with rich HTML rendering
|
||||||
- HTML rendering with DOMPurify sanitization
|
- **Threading** — Gmail-style inline expansion with thread navigation
|
||||||
- Attachment upload and download
|
- **Draft auto-save** with discard confirmation
|
||||||
- Draft auto-save with discard confirmation
|
- **Attachments** — upload, download, and inline preview
|
||||||
- Threading with inline expansion
|
- **Search** — full-text with JMAP filter panel, search chips, and cross-mailbox queries
|
||||||
- Mark as read/unread, star/unstar
|
- **Batch operations** — multi-select, archive, delete, move, tag
|
||||||
- Archive and delete with configurable behavior
|
- **Color tags/labels** and star/unstar
|
||||||
- Color tags/labels
|
- **Virtual scrolling** for large mailboxes
|
||||||
- Search with JMAP filter panel, search chips, cross-mailbox queries
|
- **Quick reply** from the viewer
|
||||||
- Virtual scrolling for large lists
|
|
||||||
|
|
||||||
### Interface
|
|
||||||
|
|
||||||
- Three-pane layout with dark and light themes
|
|
||||||
- Responsive (desktop sidebar + mobile bottom tab bar)
|
|
||||||
- Keyboard shortcuts
|
|
||||||
- Drag-and-drop email organization
|
|
||||||
- Right-click context menus
|
|
||||||
- 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
|
### Calendar
|
||||||
|
|
||||||
- JMAP Calendar (RFC 8984) with capability detection
|
- **Month, week, day, and agenda views** with mini-calendar sidebar
|
||||||
- Month, week, day, and agenda views
|
- **Drag-and-drop rescheduling**, click-drag creation, edge-resize (15-min snap)
|
||||||
- Event create, edit, delete with recurrence and reminders
|
- **Recurring events** with edit/delete scope (this / this and following / all)
|
||||||
- Participant scheduling with iTIP invitations and RSVP
|
- **Participant scheduling** — iTIP invitations, organizer/attendee UI, RSVP
|
||||||
- Inline calendar invitation banner in email viewer (.ics detection, RSVP, import)
|
- **Inline calendar invitations** in email viewer — auto-detect `.ics`, RSVP, import
|
||||||
- Multi-day events, column-based overlap layout
|
- **iCalendar import** with preview and bulk create
|
||||||
- Mini-calendar sidebar with calendar visibility toggles
|
- **Notifications** with configurable sound and alert persistence
|
||||||
- Locale-aware date formatting
|
- **Real-time sync** via JMAP push
|
||||||
- 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
|
### Contacts
|
||||||
|
|
||||||
- Reusable templates organized by category
|
- **Contact management** with JMAP sync (RFC 9553/9610) and local fallback
|
||||||
- Placeholder variables (`{{recipientName}}`, `{{date}}`, etc.) with auto-fill
|
- **Contact groups** with group expansion and member management
|
||||||
- Template picker in compose toolbar with search and filter
|
- **vCard import/export** (RFC 6350) with duplicate detection
|
||||||
- Template manager in settings
|
- **Autocomplete** in composer (To/Cc/Bcc)
|
||||||
|
- **Bulk operations** — multi-select, delete, group add, export
|
||||||
|
|
||||||
### Email filters
|
### Filters & Automation
|
||||||
|
|
||||||
- Server-side filtering with JMAP Sieve Scripts (RFC 9661)
|
- **Server-side email filters** via JMAP Sieve Scripts (RFC 9661)
|
||||||
- Visual rule builder: conditions (From, To, Subject, Size, Body...) and actions (Move, Forward, Mark read, Star, Discard, Reject...)
|
- **Visual rule builder** — conditions (From, To, Subject, Size, Body…) and actions (Move, Forward, Star, Discard…)
|
||||||
- Raw Sieve editor with syntax validation
|
- **Raw Sieve editor** with syntax validation
|
||||||
- Auto-save with rollback on failure
|
- **Vacation responder** with date range scheduling and sidebar indicator
|
||||||
- Drag-and-drop rule reordering
|
- **Email templates** — reusable, categorized, with placeholder auto-fill (`{{recipientName}}`, `{{date}}`, etc.)
|
||||||
- Only shown when the server supports Sieve
|
|
||||||
|
|
||||||
### Vacation responder
|
### Security & Privacy
|
||||||
|
|
||||||
- JMAP VacationResponse with date range scheduling
|
- **External content blocked** by default — trusted senders list for auto-load
|
||||||
- Settings tab for message configuration
|
- **HTML sanitization** via DOMPurify with XSS prevention
|
||||||
- Sidebar indicator when active
|
- **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)
|
||||||
|
|
||||||
### Security and privacy
|
### Interface
|
||||||
|
|
||||||
- External content blocked by default
|
- **Three-pane layout** — sidebar, email list, viewer
|
||||||
- Trusted senders list for automatic image loading
|
- **Dark and light themes** with intelligent email color transformation
|
||||||
- HTML sanitization (DOMPurify)
|
- **Responsive** — desktop sidebar + mobile bottom tab bar
|
||||||
- SPF/DKIM/DMARC status indicators
|
- **Keyboard shortcuts** — full navigation without a mouse
|
||||||
- Session-based auth, no password storage by default
|
- **Drag-and-drop** email organization between mailboxes
|
||||||
- TOTP two-factor authentication
|
- **Right-click context menus**, toast notifications with undo, form validation with shake feedback
|
||||||
- "Remember me" with AES-256-GCM encrypted httpOnly cookie (opt-in)
|
- **Accessibility** — WCAG AA contrast, reduced-motion support, focus trap, screen reader live regions
|
||||||
- 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
|
### Internationalization
|
||||||
|
|
||||||
- 8 languages: English, French, Japanese, Spanish, Italian, German, Dutch, Portuguese
|
8 languages: English · Français · 日本語 · Español · Italiano · Deutsch · Nederlands · Português
|
||||||
- Automatic browser language detection
|
|
||||||
- Persistent language preference
|
|
||||||
|
|
||||||
### Deployment
|
Automatic browser detection with persistent preference.
|
||||||
|
|
||||||
- Pre-built Docker images on [Docker Hub](https://hub.docker.com/r/bulwarkmail/webmail) and [GHCR](https://ghcr.io/bulwarkmail/webmail) (amd64/arm64)
|
### Identity Management
|
||||||
- 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
|
- **Multiple sender identities** with per-identity signatures
|
||||||
|
- **Sub-addressing** — `user+tag@domain.com` with contextual tag suggestions
|
||||||
|
- **Identity badges** in viewer and email list
|
||||||
|
|
||||||
- [Next.js 16](https://nextjs.org/) with App Router
|
---
|
||||||
- TypeScript
|
|
||||||
- [Tailwind CSS v4](https://tailwindcss.com/)
|
|
||||||
- [Zustand](https://zustand-demo.pmnd.rs/) for state management
|
|
||||||
- Custom JMAP client (RFC 8620)
|
|
||||||
- [next-intl](https://next-intl-docs.vercel.app/) for i18n
|
|
||||||
- [Lucide React](https://lucide.dev/) icons
|
|
||||||
|
|
||||||
## Getting started
|
## Quick Start
|
||||||
|
|
||||||
### Prerequisites
|
### Docker (recommended)
|
||||||
|
|
||||||
- Node.js 18+
|
```bash
|
||||||
- A JMAP-compatible mail server ([Stalwart](https://stalw.art/) recommended)
|
docker run -d -p 3000:3000 \
|
||||||
|
-e JMAP_SERVER_URL=https://mail.example.com \
|
||||||
|
ghcr.io/bulwarkmail/webmail:latest
|
||||||
|
```
|
||||||
|
|
||||||
### Installation
|
Or with Docker Compose:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env.local
|
||||||
|
# Edit .env.local — set JMAP_SERVER_URL
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### From Source
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/bulwarkmail/webmail.git
|
git clone https://github.com/bulwarkmail/webmail.git
|
||||||
cd webmail
|
cd webmail
|
||||||
npm install
|
npm install
|
||||||
cp .env.example .env.local
|
cp .env.example .env.local
|
||||||
|
# Edit .env.local — set JMAP_SERVER_URL
|
||||||
|
npm run build && npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
### Configuration
|
### Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev # Start dev server (mock JMAP server included)
|
||||||
|
npm run typecheck # Type checking
|
||||||
|
npm run lint # Linting
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
Edit `.env.local`:
|
Edit `.env.local`:
|
||||||
|
|
||||||
```env
|
```env
|
||||||
# App name displayed in the UI
|
# Required
|
||||||
APP_NAME=My Webmail
|
|
||||||
|
|
||||||
# Your JMAP server URL (required)
|
|
||||||
JMAP_SERVER_URL=https://mail.example.com
|
JMAP_SERVER_URL=https://mail.example.com
|
||||||
|
|
||||||
|
# Optional
|
||||||
|
APP_NAME=My Webmail
|
||||||
```
|
```
|
||||||
|
|
||||||
These are runtime environment variables, read at request time. Docker deployments can be configured without rebuilding. Legacy `NEXT_PUBLIC_*` variables still work as fallbacks.
|
All variables are **runtime** — Docker deployments can be configured without rebuilding.
|
||||||
|
|
||||||
#### OAuth2/OIDC (optional)
|
<details>
|
||||||
|
<summary>OAuth2/OIDC (SSO)</summary>
|
||||||
To enable SSO login alongside Basic Auth:
|
|
||||||
|
|
||||||
```env
|
```env
|
||||||
OAUTH_ENABLED=true
|
OAUTH_ENABLED=true
|
||||||
@@ -184,118 +199,50 @@ OAUTH_CLIENT_SECRET= # optional, for confidential clients
|
|||||||
OAUTH_ISSUER_URL= # optional, for external IdPs (Keycloak, Authentik)
|
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`).
|
Endpoints are auto-discovered via `.well-known/oauth-authorization-server` or `.well-known/openid-configuration`.
|
||||||
|
</details>
|
||||||
|
|
||||||
#### Remember me (optional)
|
<details>
|
||||||
|
<summary>Remember Me</summary>
|
||||||
To enable "Remember me" for Basic Auth login:
|
|
||||||
|
|
||||||
```env
|
```env
|
||||||
SESSION_SECRET=your-secret-key # Generate with: openssl rand -base64 32
|
SESSION_SECRET=your-secret-key # Generate with: openssl rand -base64 32
|
||||||
```
|
```
|
||||||
|
|
||||||
When 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).
|
Credentials encrypted with AES-256-GCM, stored in an httpOnly cookie (30-day expiry).
|
||||||
|
</details>
|
||||||
|
|
||||||
### Development
|
## Keyboard Shortcuts
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run dev # Start dev server
|
|
||||||
npm run typecheck # Type checking
|
|
||||||
npm run lint # Linting
|
|
||||||
```
|
|
||||||
|
|
||||||
### Production
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build
|
|
||||||
npm start
|
|
||||||
```
|
|
||||||
|
|
||||||
### Docker
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Pre-built image
|
|
||||||
docker run -p 3000:3000 -e JMAP_SERVER_URL=https://mail.example.com bulwarkmail/webmail:latest
|
|
||||||
|
|
||||||
# From GHCR
|
|
||||||
docker run -p 3000:3000 -e JMAP_SERVER_URL=https://mail.example.com ghcr.io/bulwarkmail/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 bulwark-webmail .
|
|
||||||
docker run -p 3000:3000 -e JMAP_SERVER_URL=https://mail.example.com bulwark-webmail
|
|
||||||
```
|
|
||||||
|
|
||||||
## Keyboard shortcuts
|
|
||||||
|
|
||||||
| Key | Action |
|
| Key | Action |
|
||||||
|-----|--------|
|
|-----|--------|
|
||||||
| `j` / `k` | Navigate between emails |
|
| `j` / `k` | Navigate between emails |
|
||||||
| `Enter` / `o` | Open selected email |
|
| `Enter` / `o` | Open email |
|
||||||
| `Esc` | Close viewer / deselect |
|
| `Esc` | Close / deselect |
|
||||||
| `c` | Compose new email |
|
| `c` | Compose |
|
||||||
| `r` | Reply |
|
| `r` / `R` | Reply / Reply all |
|
||||||
| `R` / `a` | Reply all |
|
|
||||||
| `f` | Forward |
|
| `f` | Forward |
|
||||||
| `s` | Toggle star |
|
| `s` | Star |
|
||||||
| `e` | Archive |
|
| `e` | Archive |
|
||||||
| `#` / `Delete` | Delete |
|
| `#` | Delete |
|
||||||
| `u` | Mark as unread |
|
| `/` | Search |
|
||||||
| `/` | Focus search |
|
| `?` | Show all shortcuts |
|
||||||
| `x` | Expand/collapse thread |
|
|
||||||
| `Ctrl+Shift+T` | Insert template |
|
|
||||||
| `?` | Show shortcuts help |
|
|
||||||
|
|
||||||
## Screenshots
|
## Tech Stack
|
||||||
|
|
||||||
<table>
|
| | |
|
||||||
<tr>
|
|---|---|
|
||||||
<td width="50%">
|
| **Framework** | [Next.js 16](https://nextjs.org/) with App Router |
|
||||||
|
| **Language** | TypeScript |
|
||||||
|
| **Styling** | [Tailwind CSS v4](https://tailwindcss.com/) |
|
||||||
|
| **State** | [Zustand](https://zustand-demo.pmnd.rs/) |
|
||||||
|
| **Protocol** | Custom JMAP client (RFC 8620) |
|
||||||
|
| **i18n** | [next-intl](https://next-intl-docs.vercel.app/) |
|
||||||
|
| **Icons** | [Lucide React](https://lucide.dev/) |
|
||||||
|
|
||||||
**Login**
|
## Why Stalwart?
|
||||||
<img src="screenshots/01-login.png" width="100%" alt="Login">
|
|
||||||
|
|
||||||
</td>
|
[Stalwart](https://github.com/stalwartlabs/mail-server) 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.
|
||||||
<td width="50%">
|
|
||||||
|
|
||||||
**Inbox**
|
|
||||||
<img src="screenshots/02-inbox.png" width="100%" alt="Inbox">
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td width="50%">
|
|
||||||
|
|
||||||
**Email Viewer**
|
|
||||||
<img src="screenshots/03-email-viewer.png" width="100%" alt="Email Viewer">
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td width="50%">
|
|
||||||
|
|
||||||
**Compose**
|
|
||||||
<img src="screenshots/04-compose.png" width="100%" alt="Compose">
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td width="50%">
|
|
||||||
|
|
||||||
**Dark Mode**
|
|
||||||
<img src="screenshots/05-dark-mode.png" width="100%" alt="Dark Mode">
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td width="50%">
|
|
||||||
|
|
||||||
**Settings**
|
|
||||||
<img src="screenshots/06-settings.png" width="100%" alt="Settings">
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
@@ -303,13 +250,8 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
See [ROADMAP.md](ROADMAP.md) for planned features.
|
See [ROADMAP.md](ROADMAP.md) for planned features and current status.
|
||||||
|
|
||||||
## Acknowledgments
|
|
||||||
|
|
||||||
- [Stalwart Labs](https://stalw.art/) for the mail server
|
|
||||||
- The [JMAP](https://jmap.io/) working group for the protocol spec
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT. See [LICENSE](LICENSE).
|
[MIT](LICENSE)
|
||||||
|
|||||||
Reference in New Issue
Block a user