# RustBox 0.2.29 **Fast, self-hosted webmail written in Rust.** RustBox connects to any IMAP/SMTP server and ships as one executable with the whole interface built in, so there is no Node.js, PHP or separate frontend to deploy. ![RustBox](docs/screenshots/mail-light.png) ## Highlights - Three-pane mail client with folder tree, threads, bulk actions, drag and drop, snooze, undo send and undo move. - HTML, plain-text and Markdown composer with inline images, drafts, identities, signatures and OpenPGP. - Server-side filters (Sieve) with a visual editor, exceptions, templates and **Apply now** for mail that is already in a folder. - Calendar with reminders and ICS import/export, contacts with groups and vCard. - Realtime new-mail updates and desktop notifications. - 43 interface styles, from Gmail- and Outlook-inspired to Dracula and Windows XP, plus a fully adjustable custom style, light/dark/time-of-day themes and a mobile layout. - SQLite, MySQL or PostgreSQL; local disk, shared filesystem or S3 storage; built-in search or OpenSearch; optional cluster mode. - English and Polish interface. The complete list, with screenshots, is in **[docs/FEATURES.md](docs/FEATURES.md)**. | | | |---|---| | ![Filters](docs/screenshots/filters-editor.png) | ![Dracula style](docs/screenshots/style-dracula.png) | | ![Calendar](docs/screenshots/calendar.png) | ![Windows XP style](docs/screenshots/style-winxp.png) | ## Quick start ```bash # development server with config.dev.toml ./dev.sh # then open http://127.0.0.1:3001 ``` Production build and systemd installation: ```bash ./scripts/build-release.sh sudo ./scripts/install-systemd.sh sudo systemctl enable --now rustbox ``` Docker Compose examples (SQLite; PostgreSQL + S3 + OpenSearch; MySQL + NFS + OpenSearch; HA with Garage) are in [`docker/`](docker/README.md). ## Documentation | Document | Contents | |---|---| | [Features](docs/FEATURES.md) | Everything RustBox does, with screenshots | | [Storage and search](docs/STORAGE_SEARCH_OPERATIONS.md) | Backends, shared storage, migrations, failure modes | | [System diagnostics](docs/SYSTEM_DIAGNOSTICS.md) | Administrator diagnostics endpoint | | [IMAP connection pool](docs/imap-pool.md) | How IMAP sessions are reused | | [Docker](docker/README.md) | Compose deployments | | [Releasing](docs/RELEASING.md) · [Validation](docs/VALIDATION.md) | Release process and checks | | [Implementation status](docs/IMPLEMENTATION_STATUS.md) | Detailed technical status | | [Changelog](CHANGELOG.md) | Changes per version | | [Database migrations](migrations/README.md) | Schema baseline and upgrades | ## Configure mail servers Mail endpoints are configured by the administrator, not on the login screen. Users enter only email and password. Login brute-force protection is on by default and configured in the `[login_rate_limit]` section (or `LOGIN_RATE_LIMIT__*` environment variables): a per-IP token bucket on the login endpoint plus failed-attempt lockouts per mailbox and per client IP with exponential back-off. Locked attempts are answered with `429` and `Retry-After` without contacting the IMAP server. Counters are in memory per node. See `config.example.toml` for every option. Session cookies get the `Secure` attribute according to `cookie_secure` (default `auto`): only when the browser reached RustBox over HTTPS, detected from a trusted proxy's `X-Forwarded-Proto`/`Forwarded`/`X-Forwarded-Ssl` header or the browser's `Origin`/`Referer`. Plain-HTTP installations keep working; set `cookie_secure = "always"` behind an HTTPS proxy that does not forward the scheme. Edit `config.dev.toml` before the first run: ```toml default_theme = "system" # system | dark | light default_interface_skin = "rustbox" # rustbox | gmail | outlook | apple-mail | github | slack | dracula | catppuccin | tokyo-night | paper | terminal | aqua | material | retro | winxp | osx | ios6 | win7 | frutiger-aero | beos | ... (full list in config.example.toml) log_level = "info" # RustBox app level; dependencies default to WARN. RUST_LOG can override the full filter. # Recommended dev filter: warn,rustbox=debug,tower_http=info # Avoid a global RUST_LOG=debug/info unless diagnosing a dependency: third-party # crates may emit internal filesystem/source paths in verbose records. # Mail hosts may be omitted: IMAP defaults to localhost:993 and SMTP to localhost:587. # imap_host = "localhost" # smtp_host = "localhost" tls_enabled = true # tls_verify = true # omitted: false only for localhost/loopback, true for remote hosts # Upload limit for one raw attachment. max_attachment_size_mb = 10 # Hard limit for the complete rendered RFC822/MIME message submitted to the MTA. max_message_size_mb = 50 # Memory guard for messages with attachments. Sending a message or saving an IMAP # draft snapshot loads every attachment into RAM and the MIME builder keeps several # copies (about 3-4x the message size). Attachment sets that cannot fit # max_message_size_mb are rejected with 413 before any file is read, and at most # max_concurrent_message_builds requests may hold attachments in memory at once; # the others wait up to message_build_wait_secs and then get 503. # Rough peak RAM = max_concurrent_message_builds x 4 x max_message_size_mb. max_concurrent_message_builds = 4 message_build_wait_secs = 30 # Optional features exposed by the server/UI. sieve_enabled = true diagnostics_enabled = true about_enabled = true # Adds content-type-aware browser security headers when enabled: full document policy on HTML, private no-store/CORP/anti-framing policy on /api, Referrer-Policy on HTML/CSS, and nosniff broadly. security_headers_enabled = false # Optional; defaults to imap_host. Ignored when sieve_enabled=false. # sieve_host = "mail.example.com" sieve_port = 4190 sieve_tls_mode = "starttls" sieve_allow_insecure_auth = false # Optional administrator-owned headers added to every outgoing message. # Structural/MIME headers such as From, To, Subject and Content-Type are rejected. outgoing_headers = [ "X-Company-Mailer: RustBox", ] ``` If the mail host settings are omitted, RustBox uses `localhost` with IMAP `993` and SMTP `587`. TLS is enabled by default. Certificate and hostname verification are skipped automatically only for localhost/loopback unless `tls_verify` is set explicitly. Mail-size limits are intentionally separate. `max_attachment_size_mb` applies to each raw uploaded file, while `max_message_size_mb` is checked against the final RFC822/MIME byte stream immediately before SMTP submission. The latter therefore includes message bodies, headers, MIME boundaries, base64 expansion, inline CID parts and OpenPGP transformations. The composer displays only an estimate; the server-side final-MIME check is authoritative. Attachments are streamed to blob storage on upload, but sending a message or saving an IMAP draft snapshot has to hold them in memory while the MIME message is built. RustBox therefore (1) rejects an attachment set whose base64-encoded size cannot fit `max_message_size_mb` with `413` before any file is read into memory, and also while an upload is still streaming, and (2) allows only `max_concurrent_message_builds` such operations at the same time; further requests wait up to `message_build_wait_secs` and then receive `503`. Peak RAM for these operations is roughly `max_concurrent_message_builds` x 4 x `max_message_size_mb`. Fully streamed MIME assembly is not implemented. ## Storage and search RustBox separates three concerns: SQL metadata, blob storage and search. The SQL engine does **not** decide whether `data_dir` may be used. A single-node PostgreSQL/MySQL deployment may still use filesystem blobs and local Tantivy. In cluster mode, local node state is rejected unless the configured backend is explicitly shared. Blob backends: - `storage.backend="filesystem"` — local disk in single-node mode; set `storage.shared=true` only for a real shared POSIX mount such as NFS, CephFS or GlusterFS. - `storage.backend="s3"` — AWS S3 or compatible APIs such as MinIO/Ceph RGW. Physical blob deletion is durable across all blob backends. RustBox records deletion work in the per-account SQL `blob_delete_outbox` in the same transaction that removes attachment metadata, attempts the delete immediately, and retries temporary failures in the background with leases/backoff. This covers local disk, shared POSIX/NFS and S3-compatible storage without permanently dead-lettering failed cleanup. Search backends: - `search.backend="local_tantivy"` — single-node. - `search.backend="opensearch"` — cluster-capable. Index mutations are written first to a durable per-tenant SQL outbox and processed with retry/backoff/dead-letter handling. - `search.backend="disabled"` — no full-text index. In a cluster using shared SQL + S3/shared POSIX + OpenSearch/disabled search + `pgp_enabled=false`, `data_dir` is not required as persistent node state. See `docker/README.md` for ready Compose variants. Administrator-only deep runtime diagnostics can be enabled separately under `[diagnostics_endpoint]`. It is disabled and loopback-only by default, supports a network allowlist plus token or Basic Auth, redacts secrets, and probes SQL/Redis/storage/search/session/runtime/security state. See [`docs/SYSTEM_DIAGNOSTICS.md`](docs/SYSTEM_DIAGNOSTICS.md). ## Database, storage, search and cluster traffic `database_backend = "sqlite" | "mysql" | "pgsql"` selects only the authoritative SQL database. It does not force or forbid local blob/search storage. In particular, PostgreSQL/MySQL are valid in a single-node installation together with `storage.backend="filesystem"` and `search.backend="local_tantivy"`. `session_backend = "database"` stores sessions in the selected SQL database. `session_backend = "redis"` moves only session persistence to Redis/Redis Sentinel. Mailbox metadata and cached message bodies remain in SQL. `data_dir` is persistent node state only when a selected local feature actually needs it: default SQLite, implicit filesystem storage, implicit local Tantivy, or local OpenPGP keys. In cluster mode it is not required when those features are replaced with cluster-safe backends. Cluster validation is capability-based: - SQL: shared MySQL/PostgreSQL; SQLite is rejected; - blobs: S3-compatible storage, or `filesystem` with `storage.shared=true` on a real NFS/CephFS/GlusterFS mount; - search: OpenSearch or `disabled`; local Tantivy is rejected; - OpenPGP: remains disabled until a cluster-safe key backend exists; - all nodes use the same `session_encryption_key`. Example stateless cluster: ```toml database_backend = "pgsql" database_url = "postgres://rustbox:secret@db/rustbox" session_backend = "database" session_encryption_key = "" cluster_mode = true pgp_enabled = false [storage] backend = "s3" bucket = "rustbox" region = "eu-central-1" # endpoint = "https://s3.example.com" # MinIO/Ceph RGW/etc. [sync] interval_secs = 30 deep_index_batch_size = 10 [search] backend = "opensearch" urls = ["https://opensearch.example.com:9200"] required = true # Production-safe default: do not proactively download every full message body. deep_index = false ``` Shared POSIX is also supported without S3: ```toml [storage] backend = "filesystem" path = "/mnt/rustbox-shared" shared = true ``` Existing attachment files can be migrated to the configured storage backend without deleting the source files: ```bash rustbox storage migrate --from filesystem:/old/data/root --to configured --dry-run rustbox storage migrate --from filesystem:/old/data/root --to configured --verify ``` The SQL row is switched to the logical object key only after upload and size/checksum verification. Search state is rebuildable from SQL: ```bash rustbox search reindex ``` Ready Docker examples are in `docker/`: standalone SQLite, PostgreSQL + SILO (S3-compatible) + OpenSearch, and MySQL + NFS + OpenSearch. Database schema upgrades run automatically at startup. Databases older than the consolidated baseline (V052) must first be opened once with RustBox 0.2.22. See `migrations/README.md` for the schema history; V055 is the next migration number. ## ManageSieve Supported modes: - `starttls` — recommended; - `implicit` — TLS immediately after connect; - `none` — plaintext transport; reusable credential authentication remains blocked unless `sieve_allow_insecure_auth = true` is explicitly enabled. When `sieve_host` is not configured, RustBox uses the authenticated session's IMAP host. Sieve uses the same session credentials; passwords are not exposed to the browser after login. ## Native installation ```bash ./scripts/build-release.sh sudo ./scripts/install-systemd.sh sudo systemctl enable --now rustbox sudo journalctl -u rustbox -f ``` For public access put an HTTPS reverse proxy in front of RustBox. The production example binds to `127.0.0.1:3001`. ## Development Requirements: Rust toolchain with Cargo. OpenPGP key generation/sign/encrypt/decrypt is implemented with rPGP. Changing the passphrase of an existing private key additionally requires GnuPG (`gpg`) on the server; override the executable with `RUSTBOX_PGP_GPG_BINARY` if needed. ```bash ./dev.sh ``` Then open: ```text http://127.0.0.1:3001 ``` To use another configuration: ```bash RUSTBOX_CONFIG=/path/to/rustbox.toml ./dev.sh ``` Normal Cargo commands work from the repository root: ```bash cargo run cargo test cargo build --release ``` ## Project layout ```text src/ Rust application (Axum server, IMAP/SMTP, Sieve, storage, search) src/web/ Browser interface: index.html, css/, js/, i18n/, icons.svg migrations/ SQL schemas for SQLite, MySQL and PostgreSQL docker/ Compose examples packaging/ systemd unit, logrotate and production config scripts/ Build, install, release and Roundcube import helpers docs/ Documentation and screenshots ``` `build.rs` bundles the CSS and JavaScript modules listed in `src/web/css/manifest.txt` and `src/web/js/manifest.txt`; the bundles, HTML, translations and icons are embedded in the executable. ## License Copyright © 2026 Mateusz Gruszczyński ([linuxiarz.pl](https://linuxiarz.pl)). RustBox is dual-licensed: - **GNU Affero General Public License v3** ([`LICENSE`](LICENSE)) — free to use, modify and share, also in companies. If you run a modified version as a network service, offer its source code to its users. - **Commercial license** — for organisations that prefer different terms, for example to keep their changes private. Paid; terms by agreement. Contact: [linuxiarz@linuxiarz.pl](mailto:linuxiarz@linuxiarz.pl). Contacting the author is voluntary and never required for use that follows the AGPL. Details: [`LICENSING.md`](LICENSING.md). The first version of RustBox was created as a fork of [Oxi](https://github.com/c0h1b4/oxi) (MIT License); see [`NOTICE.md`](NOTICE.md).