Self-Host Stalwart Mail Server: Modern Rust Email 2026
TL;DR
Stalwart Mail Server (AGPL 3.0, ~6K GitHub stars, Rust) is the modern alternative to the classic Postfix+Dovecot combination. It's a single binary that handles SMTP, IMAP4, POP3, and JMAP — no separate processes, no complex configuration files scattered across /etc/postfix/, /etc/dovecot/, and /etc/opendkim/. Written in Rust for performance and memory safety, Stalwart includes advanced features like DANE, MTA-STS, BIMI, ARC, and an AI-powered spam filter built in. If you're building a new mail server in 2026, Stalwart is worth serious consideration over the traditional stack.
Key Takeaways
- Stalwart: AGPL 3.0, ~6K stars, Rust — single binary replaces Postfix+Dovecot+OpenDKIM
- JMAP support: Modern email protocol (RFC 8620) alongside IMAP — faster mobile sync
- Built-in spam filter: Bayes + DNS blocklists + scoring — no Rspamd required
- Web admin: Full management UI at
/admin— no config files to edit - DANE/MTA-STS: Modern TLS verification for outbound email — better security than just STARTTLS
- vs Mailcow: Stalwart is simpler to set up; Mailcow has more mature ecosystem and webmail
Stalwart vs Mailcow vs iRedMail
| Feature | Stalwart | Mailcow | iRedMail |
|---|---|---|---|
| Language | Rust | Multi (Docker stack) | Shell + Python |
| Setup complexity | Low | Medium | Medium |
| Config style | Web UI / TOML | Web UI | Config files |
| SMTP | Built-in | Postfix | Postfix |
| IMAP | Built-in | Dovecot | Dovecot |
| JMAP | Yes | No | No |
| Webmail | No (external) | SOGo | Roundcube |
| Spam filter | Built-in | Rspamd | SpamAssassin |
| DANE | Yes | Manual | No |
| MTA-STS | Yes | No | No |
| BIMI | Yes | No | No |
| RAM usage | ~150MB | ~2-4GB | ~1-2GB |
| Maturity | Growing (~2022) | Mature (~2015) | Mature (~2010) |
Part 1: Docker Setup
# docker-compose.yml
services:
stalwart:
image: stalwartlabs/mail-server:latest
container_name: stalwart
restart: unless-stopped
ports:
- "25:25" # SMTP
- "465:465" # SMTP over TLS (SMTPS)
- "587:587" # SMTP submission (STARTTLS)
- "143:143" # IMAP
- "993:993" # IMAP over TLS
- "4190:4190" # ManageSieve
- "8080:8080" # HTTP admin UI (proxy behind Caddy)
volumes:
- stalwart_data:/opt/stalwart-mail
environment:
- TZ=America/Los_Angeles
volumes:
stalwart_data:
docker compose up -d
# Get the admin password from logs:
docker logs stalwart 2>&1 | grep "administrator"
Part 2: HTTPS with Caddy
mail.yourdomain.com {
reverse_proxy localhost:8080
}
Access the admin UI at https://mail.yourdomain.com/admin
Part 3: Initial Configuration via Web UI
1. Configure your domain
- Directory → Domains → Add domain
- Domain:
yourdomain.com - Enable DKIM: Stalwart generates key automatically
2. Create administrator account
- Directory → Accounts → Add account
- Username:
admin@yourdomain.com - Type: Administrator
- Set password
3. Create mailboxes
- Directory → Accounts → Add account
- Username:
hello@yourdomain.com - Type: Individual
- Quota: 10GB (0 = unlimited)
Part 4: DNS Configuration
Same requirements as any mail server:
; A record:
mail.yourdomain.com. IN A YOUR.SERVER.IP
; MX record:
yourdomain.com. IN MX 10 mail.yourdomain.com.
; SPF:
yourdomain.com. IN TXT "v=spf1 mx ~all"
; DKIM (get key from Stalwart admin → Directory → Domains → yourdomain.com → DKIM):
dkim._domainkey.yourdomain.com. IN TXT "v=DKIM1; k=rsa; p=..."
; DMARC:
_dmarc.yourdomain.com. IN TXT "v=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com"
DANE (DNS-Based Authentication of Named Entities)
Stalwart supports DANE — a stronger alternative to CA-based TLS verification:
; TLSA record — ties your TLS cert to DNS:
; Get your cert fingerprint:
openssl x509 -in /path/to/cert.pem -noout -fingerprint -sha256 | sed 's/://g'
; TLSA record:
_25._tcp.mail.yourdomain.com. IN TLSA 3 1 1 <sha256-fingerprint>
Your domain registrar/DNS provider must support DNSSEC for DANE to be useful.
MTA-STS (strict TLS policy)
# Create MTA-STS policy file:
mkdir -p /var/www/.well-known
cat > /var/www/.well-known/mta-sts.txt << 'EOF'
version: STSv1
mode: enforce
mx: mail.yourdomain.com
max_age: 86400
EOF
; MTA-STS DNS record:
_mta-sts.yourdomain.com. IN TXT "v=STSv1; id=20260309"
; TLS-RPT (TLS reporting):
_smtp._tls.yourdomain.com. IN TXT "v=TLSRPTv1; rua=mailto:tls-reports@yourdomain.com"
Part 5: Configuration via TOML (Advanced)
Stalwart stores config in /opt/stalwart-mail/etc/config.toml — you can edit directly or use the web UI:
# /opt/stalwart-mail/etc/config.toml
[server]
hostname = "mail.yourdomain.com"
[server.listener.smtp]
bind = ["0.0.0.0:25"]
protocol = "smtp"
[server.listener.submission]
bind = ["0.0.0.0:587"]
protocol = "smtp"
tls.implicit = false
[server.listener.smtps]
bind = ["0.0.0.0:465"]
protocol = "smtp"
tls.implicit = true
[server.listener.imap]
bind = ["0.0.0.0:143"]
protocol = "imap"
[server.listener.imaps]
bind = ["0.0.0.0:993"]
protocol = "imap"
tls.implicit = true
# Spam filtering settings:
[spam]
enable = true
learn-ham = "ham"
learn-spam = "spam"
bayes-store = "db"
# Rate limiting:
[session.throttle]
ip-concurrency = 5
ip-rate = "100/1h"
Part 6: JMAP (Modern Email Protocol)
JMAP (RFC 8620) is a modern replacement for IMAP with a REST/JSON API:
# JMAP endpoint:
https://mail.yourdomain.com/jmap
# Discover capabilities:
curl https://mail.yourdomain.com/.well-known/jmap
# JMAP session (basic auth):
curl -u hello@yourdomain.com:password \
https://mail.yourdomain.com/jmap
# List mailboxes:
curl -u hello@yourdomain.com:password \
-X POST https://mail.yourdomain.com/jmap \
-H "Content-Type: application/json" \
-d '{
"using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
"methodCalls": [["Mailbox/get", {"accountId": "ACCOUNT_ID"}, "0"]]
}'
JMAP clients: Mujmail, Ltt.rs, or build your own.
Part 7: Spam Filter Configuration
Stalwart's built-in spam filter uses multiple mechanisms:
# Spam filter configuration:
[spam.lists]
# DNS blocklists (checked on inbound):
dnsbl = [
"zen.spamhaus.org",
"bl.spamcop.net",
"dnsbl.sorbs.net"
]
[spam.scores]
# Adjust scoring:
dnsbl-match = 5.0
spf-fail = 2.0
dkim-fail = 2.0
dmarc-fail = 2.0
bayes-spam = 6.0
Train the Bayes filter:
# Via Sieve script (automatic from Junk folder):
# When user moves to Junk, Stalwart auto-learns spam
# Via CLI:
docker exec stalwart stalwart-mail spam-train \
--spam /path/to/spam.eml
docker exec stalwart stalwart-mail spam-train \
--ham /path/to/legitimate.eml
Part 8: Webmail Options
Stalwart doesn't include webmail — pair it with:
# Add Roundcube webmail:
services:
roundcube:
image: roundcube/roundcubemail:latest
container_name: roundcube
restart: unless-stopped
ports:
- "9090:80"
environment:
ROUNDCUBEMAIL_DEFAULT_HOST: tls://mail.yourdomain.com
ROUNDCUBEMAIL_SMTP_SERVER: tls://mail.yourdomain.com
ROUNDCUBEMAIL_DB_TYPE: sqlite
ROUNDCUBEMAIL_SKIN: elastic
webmail.yourdomain.com {
reverse_proxy localhost:9090
}
Maintenance
# Update Stalwart:
docker compose pull
docker compose up -d
# Backup:
tar -czf stalwart-backup-$(date +%Y%m%d).tar.gz \
$(docker volume inspect stalwart_stalwart_data --format '{{.Mountpoint}}')
# View logs:
docker compose logs -f stalwart
# Check mail queue:
docker exec stalwart stalwart-mail queue list
# Flush queue:
docker exec stalwart stalwart-mail queue retry --all
# View spam stats:
docker exec stalwart stalwart-mail spam stats
See all open source email tools at OSSAlt.com/categories/email.