Files
RustBox/README.md
T
Mateusz Gruszczyński 2932578424
RustBox CI / rustbox (push) Waiting to run
v0.2.31
2026-11-08 16:42:41 +01:00

281 lines
15 KiB
Markdown

# RustBox 0.2.31
**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; optional CalDAV/CardDAV server to sync both with phones and desktop clients.
- 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 = "<shared-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).