Skip to main content

Self-Host Stalwart Mail Server: Modern Rust Email 2026

·OSSAlt Team
stalwartemail-serverrustself-hostingdockerjmap2026

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

FeatureStalwartMailcowiRedMail
LanguageRustMulti (Docker stack)Shell + Python
Setup complexityLowMediumMedium
Config styleWeb UI / TOMLWeb UIConfig files
SMTPBuilt-inPostfixPostfix
IMAPBuilt-inDovecotDovecot
JMAPYesNoNo
WebmailNo (external)SOGoRoundcube
Spam filterBuilt-inRspamdSpamAssassin
DANEYesManualNo
MTA-STSYesNoNo
BIMIYesNoNo
RAM usage~150MB~2-4GB~1-2GB
MaturityGrowing (~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

  1. Directory → Domains → Add domain
  2. Domain: yourdomain.com
  3. Enable DKIM: Stalwart generates key automatically

2. Create administrator account

  1. Directory → Accounts → Add account
  2. Username: admin@yourdomain.com
  3. Type: Administrator
  4. Set password

3. Create mailboxes

  1. Directory → Accounts → Add account
  2. Username: hello@yourdomain.com
  3. Type: Individual
  4. 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.

Comments