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.
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.
![]() |
![]() |
![]() |
![]() |
Quick start
# development server with config.dev.toml
./dev.sh
# then open http://127.0.0.1:3001
Production build and systemd installation:
./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/.
Documentation
| Document | Contents |
|---|---|
| Features | Everything RustBox does, with screenshots |
| Storage and search | Backends, shared storage, migrations, failure modes |
| System diagnostics | Administrator diagnostics endpoint |
| IMAP connection pool | How IMAP sessions are reused |
| Docker | Compose deployments |
| Releasing · Validation | Release process and checks |
| Implementation status | Detailed technical status |
| Changelog | Changes per version |
| Database migrations | 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:
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; setstorage.shared=trueonly 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.
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
filesystemwithstorage.shared=trueon 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:
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:
[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:
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:
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 unlesssieve_allow_insecure_auth = trueis 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
./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.
./dev.sh
Then open:
http://127.0.0.1:3001
To use another configuration:
RUSTBOX_CONFIG=/path/to/rustbox.toml ./dev.sh
Normal Cargo commands work from the repository root:
cargo run
cargo test
cargo build --release
Project layout
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).
RustBox is dual-licensed:
- GNU Affero General Public License v3 (
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. Contacting the author is voluntary and never required for use that follows the AGPL.
Details: LICENSING.md. The first version of RustBox was created as a fork of Oxi (MIT License); see NOTICE.md.




