Stalwart is a mail server written in Rust that ships as a single binary in a single container, covering SMTP, IMAP, POP3, JMAP, CalDAV, and CardDAV. It runs comfortably where heavier stacks would swap.
1. Before you start
Port 25 must be open, reverse DNS must point at your mail hostname, and your domain's DNS must be yours to edit. Our guide on what running your own mail server actually takes covers all of it.
You also need Docker installed.
2. Start the container
$ docker volume create stalwart-etc $ docker volume create stalwart-data $ docker run -d --name stalwart \ --restart unless-stopped \ -p 443:443 -p 8080:8080 \ -p 25:25 -p 587:587 -p 465:465 \ -p 143:143 -p 993:993 \ -p 110:110 -p 995:995 \ -p 4190:4190 \ -v stalwart-etc:/etc/stalwart \ -v stalwart-data:/var/lib/stalwart \ stalwartlabs/stalwart:latest
The latest tag tracks the newest release. For a production server, pin to the current minor series instead (a tag like v0.16, whatever their releases page shows today), so a future version with breaking changes never arrives unannounced.
You do not need every port. A server that only handles submission and IMAPS can publish just 587 and 993, plus 25 for incoming mail and 8080 for setup.
3. Get the temporary password
On first start Stalwart runs in bootstrap mode and prints a temporary administrator account to the logs:
$ docker logs stalwart 2>&1 | grep -A8 'bootstrap mode'Copy the 16 character password. It appears once and is not recoverable from the logs later.
4. Run the setup wizard
Open http://YOUR.SERVER.IP:8080/admin and sign in as admin with that password. Five screens follow, and the defaults are sensible. Two answers matter:
- Step 1, server identity: enter your mail hostname (
mail.example.com) and your mail domain (example.com). Leave the TLS certificate and DKIM key options enabled. - Step 4, logging: choose Console, not Log File. A file logger inside a container writes to storage that disappears on restart, while Console output is captured by Docker.
For storage in step 2 and the account directory in step 3, the defaults are right for a single server.
The final screen prints your permanent administrator email and password. Write both down now; that screen is the only place the password appears.
5. Restart, then sign in properly
$ docker restart stalwartThen open https://mail.example.com/admin with the permanent credentials.
A certificate warning on first sign-in is expected
Stalwart requests a Let's Encrypt certificate after the restart, and that takes a minute or two. Until it arrives, the server presents a self-signed certificate and your browser complains. Accept it once, reload after a couple of minutes, and the warning goes away. If HTTPS is not reachable at all yet, keep using http://YOUR.SERVER.IP:8080/admin.
6. Get your DNS records
Stalwart generates every record it needs and hands them to you as a zone file. In the admin interface, open Management, Domains, Domains, click the three-dot menu on your domain, and choose View DNS Zone file.
That output covers MX, SPF, DKIM, DMARC, MTA-STS, and autoconfig. Paste it into your DNS provider, or import it where the provider supports zone files.
7. Close the setup door
The HTTP admin listener on port 8080 exists for setup. Once https://mail.example.com/admin works, remove the STALWART_RECOVERY_ADMIN variable if you set one, disable the HTTP listener in the web interface, and restart. An unencrypted admin login reachable from the internet is a real risk, and you no longer need it.
8. Spam filtering
Stalwart has its own built-in filter, with most of its rules ported from Rspamd and a few from SpamAssassin. There is nothing extra to install.
Everything is under Settings, Spam Filter in the web interface:
- General: the master switch and the score thresholds that decide when a message is junked or rejected.
- Scores: every rule that can fire has a tag and a score. Positive scores push toward spam, negative toward legitimate. You can change any of them, or set a tag to discard or reject outright.
- DNSBL: Stalwart ships definitions for Spamhaus, Spamcop, Barracuda, and dozens more, and checks sending IPs, domains, URLs in the message body, and content hashes. Core definitions update themselves, and you can toggle any of them off.
- Greylisting: temporarily refuses mail from unknown senders. Real servers retry, most spam software does not.
- Spamtrap: decoy addresses. Anything sent to them is treated as spam and used to train the filter.
- Trusted senders: overrides the score so mail from known correspondents always reaches the inbox.
The classifier learns
Stalwart includes a statistical classifier that trains on what your users mark. Every message moved into or out of Junk teaches it. This improves accuracy more than adjusting scores by hand, so leave it on and tell users to move mail into and out of Junk instead of deleting it.
Reading why a message was scored
Stalwart adds two headers to delivered mail: X-Spam-Status gives the verdict and the final score, and X-Spam-Result lists every tag that fired with its contribution.
When a legitimate message lands in Junk, read X-Spam-Result in the raw source. It names the rule that did it, and you can lower that specific score rather than raising the global threshold.
9. Add accounts
Under the Account Manager in the web interface, create your mailboxes. Client settings are standard: IMAP on 993, submission on 587, both with TLS.
Quick reference
| Task | Command or place |
|---|---|
| Start | docker run ... stalwartlabs/stalwart:latest |
| Bootstrap password | docker logs stalwart 2>&1 | grep -A8 'bootstrap mode' |
| Setup wizard | http://SERVER:8080/admin |
| After restart | https://mail.example.com/admin |
| DNS records | Management, Domains, three-dot menu, View DNS Zone file |
| Logging in Docker | Console, never Log File |
Where to go next
Publish the zone file, send a test message in each direction, then disable the HTTP listener. If you bind-mount host directories instead of using named volumes, the directories must be owned by UID 2000, which is the unprivileged user inside the container.