281 lines
15 KiB
Markdown
281 lines
15 KiB
Markdown
# RustBox 0.2.28
|
|
|
|
**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.
|
|
|
|

|
|
|
|
## 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)**.
|
|
|
|
| | |
|
|
|---|---|
|
|
|  |  |
|
|
|  |  |
|
|
|
|
## 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).
|