Multi-Process Scaling
The shard-based storage model is designed for concurrent access. Multiple rmail processes
can share the same mail_root directory on a POSIX filesystem without
coordination beyond the short-duration fcntl.flock used for UID allocation.
- Flag changes use
os.rename()within the same directory — POSIX-atomic. - Message writes are independent after UID allocation completes.
- JWT tokens are validated by any process that shares the same
exchange_api.token_secret.
exchange_api.token_secret explicitly to
the same value in each instance's config.json. If left null,
each process generates a different secret and tokens will not be valid across processes.
Known storage performance characteristics:
| Metric | Capacity / Latency |
|---|---|
| Messages per mailbox | Millions (10M+ UIDs supported) |
| Flag change latency | < 0.1 ms |
| Message delivery latency | ~2 ms |
| Concurrent flag changes | Unlimited (atomic rename, CAS retry) |
| SELECT 100K messages | ~50 ms |
TLS Configuration
Auto-Generated Self-Signed Certificates
By default, tls.cert_file is null. On startup, rmail calls
ensure_tls(config), which invokes openssl req to generate a
4096-bit RSA key and a self-signed certificate valid for 10 years. Files are written to
{mail_root}/tls/cert.pem and {mail_root}/tls/key.pem.
Subsequent startups reuse the existing files.
Using a CA-Signed Certificate
Set the paths in config.json before starting the server:
{
"tls": {
"cert_file": "/etc/letsencrypt/live/mail.example.com/fullchain.pem",
"key_file": "/etc/letsencrypt/live/mail.example.com/privkey.pem"
}
}
After setting these values, restart the server. TLS services (SMTPS 465, IMAPS 993, Exchange TLS 9003, STARTTLS on 25/587/143) activate automatically. AUTH capabilities are hidden from cleartext connections as a consequence.
Certificate Renewal
After renewing a certificate on disk, send SIGHUP to reload configuration
without restarting listeners:
kill -HUP $(systemctl show -p MainPID rmail | cut -d= -f2)
Signal Handling
| Signal | Effect |
|---|---|
SIGINT | Graceful shutdown: stops all listeners and exits. |
SIGTERM | Graceful shutdown: stops all listeners and exits. |
SIGHUP | Configuration reload: re-reads config.json and calls ensure_tls. Does not restart listeners. |
Diagnostics
The diagnostic tool exercises every subsystem in sequence. Run it against a live server:
sudo python3 run.py diagnose
The 8 diagnostic phases are:
- Configuration & Environment — loads config, checks TLS cert validity and expiry, verifies all listening ports and the mailstore directory.
- User Management — creates a temporary diagnostic user, verifies directory layout, password hashing, default mailboxes, disabled-user rejection, admin flag toggle, and quota field.
- Backend Storage — appends, reads, flags, copies, and deletes messages; verifies shard placement and quota enforcement.
- SMTP Protocol — rate limit config, STARTTLS, AUTH, delivery on ports 25 and 587, external delivery, relay blocking, spoofing rejection, RFC compliance, EHLO AUTH hiding on cleartext.
- Delivery Verification — polls INBOX for messages sent in phase 4, verifying end-to-end delivery within 5 seconds; tests IDLE push notification.
- IMAP Protocol — CAPABILITY, LOGIN, LIST (wildcard and partial patterns), SPECIAL-USE, SELECT, FETCH, STORE, EXPUNGE, IDLE, ID, ENABLE, CHECK, UNSELECT, MOVE, RENAME with spaces, SEARCH complex criteria, COPY COPYUID, CAPABILITY AUTH hiding on cleartext.
- Exchange API — autoconfig, autodiscover, login, folder listing, message retrieval, flag update, send, append, pagination, OPTIONS CORS, 400 on bad request, non-admin alias 403, admin alias POST/DELETE, token expiry, rate limiter lockout.
- Cleanup — removes the diagnostic user and any aliases created during the run.
A healthy server produces 0 failures and 0 warnings.
Systemd Service
sudo python3 run.py install # install and enable service
make start
make stop
make restart
make status
make logs # tail journald output
The install command writes a systemd unit file at /etc/systemd/system/rmail.service
targeting the current working directory and Python interpreter, with Restart=on-failure
and a 5-second restart delay. Review the unit file after installation to adjust
WorkingDirectory, User, or resource limits for your deployment.
Delivery Queue Tuning
The delivery worker count controls how many messages can be written concurrently.
Increase delivery.workers if throughput is bottlenecked by filesystem I/O
on high-volume inbound workloads:
{
"delivery": {
"workers": 16
}
}
Each worker is an asyncio coroutine; memory cost is minimal. Tune to match filesystem concurrency capacity.
Quota Management
Each user has a quota_mb field in user.json (default: 1024 MB).
Quota is enforced on all append paths:
- SMTP delivery: Messages exceeding recipient quota are rejected and logged as delivery failures.
- IMAP APPEND: Returns
NO [ALERT] Quota exceededto the client. - Exchange API send: Recipients over quota appear in the
failedarray. Sent-folder copies are skipped silently when sender is over quota. - Exchange API append: Returns HTTP 413
{"error": "Quota exceeded"}.
To update a user's quota, edit the quota_mb field in:
{mail_root}/domains/{domain}/users/{username}/user.json
Storage Backup
The mail_root directory is self-contained. A consistent backup can be taken
with any tool that preserves filenames (rsync, tar, cp -a). Because flag changes use
atomic rename within a single directory, a snapshot taken mid-operation will always
contain complete, valid .eml files — at worst a flag state may be one
operation behind.