Contents

Stalwart: Self-Hosting Your Own Email Server (and Why You Probably Shouldn't)

An all-in-one mail server is finally pleasant — deliverability still isn't

Contents

Every couple of years I get the itch to run my own mail server. I quash it, because every couple of years I remember what running a mail server is actually like: a Postfix config you don’t dare touch, a Dovecot setup nobody documented, Rspamd bolted on the side, and a Sunday afternoon vanished into SASL authentication errors. And then, the first time you email someone at a big provider, you land in spam anyway.

So when I tell you that the technical experience of self-hosting email has genuinely become pleasant, hold two thoughts at once. It has. And you still probably shouldn’t.

What Stalwart actually is

Advertisement

Stalwart is a modern, all-in-one mail server written in Rust. The pitch is that it collapses the traditional “mail zoo” — separate daemons for SMTP, IMAP/POP3, and spam filtering — into a single binary you can run as one container.

Out of the box it gives you:

  • An SMTP server for sending and receiving.
  • IMAP4 and POP3 for clients, plus JMAP, the modern JSON-over-HTTP protocol that the better mail apps now speak in place of IMAP’s line-based wire format.
  • Built-in spam and phishing filtering, so no separate Rspamd.
  • Native handling of DKIM, SPF, DMARC, ARC and MTA-STS rather than three more packages glued together.
  • A web admin interface, so you’re not editing five config files by hand to add a user.

Recent versions have grown well beyond mail, adding CalDAV, CardDAV and WebDAV — calendars, contacts and file storage — so it’s edging toward being a full self-hosted collaboration server rather than just an MTA. For our purposes the mail side is what matters, but it’s worth knowing the project’s ambitions have widened.

The JMAP support is more than a checkbox, and it’s the part I’d nudge you to try. IMAP is a stateful, chatty protocol designed for an era of always-connected desktops; every folder sync is a conversation. JMAP is JSON over HTTPS, batches operations, and pushes changes rather than making the client poll — which on a phone means fewer wakeups, less battery, and faster sync. The catch is client support: most mainstream mail apps still speak only IMAP, so JMAP is a “nice if your client supports it” feature rather than a reason to switch by itself. Stalwart speaking all of them at once means you don’t have to choose — old clients get IMAP, modern ones get JMAP, from the same mailbox.

The contrast with the old stack is the whole point. Instead of wiring Postfix to Dovecot to Rspamd and praying the sockets line up, you run one thing, you configure it in one place, and the parts are designed to talk to each other. For a tinkerer, that alone is reason enough to spin it up on a weekend.

One architectural note that matters for planning: Stalwart is storage-pluggable. Out of the box it uses an embedded key-value store (RocksDB) that needs no external database, which is exactly what you want for a single-box homelab deployment — one container, one volume, done. For larger or clustered setups it can put its data and blobs in PostgreSQL, an external key-value store, or S3-compatible object storage instead. You almost certainly don’t need that for a personal server, but it’s worth knowing the escape hatch exists before you’ve built your whole setup around the embedded store and later wish you’d separated the blob storage.

Getting it running

The container route is the path of least resistance. A minimal Compose file looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
services:
  stalwart:
    image: stalwartlabs/stalwart:latest
    container_name: stalwart
    restart: unless-stopped
    ports:
      - "25:25"     # SMTP (inbound mail from the world)
      - "465:465"   # SMTP submission (implicit TLS)
      - "587:587"   # SMTP submission (STARTTLS)
      - "143:143"   # IMAP
      - "993:993"   # IMAP over TLS
      - "4190:4190" # ManageSieve (mail filters)
      - "443:443"   # JMAP + web admin (HTTPS)
    volumes:
      - ./stalwart:/opt/stalwart

Note the image is now stalwartlabs/stalwart — the older stalwartlabs/mail-server name still exists but the project has consolidated on the shorter one, and the data path moved to /opt/stalwart. If you’re following an older guide that mounts /opt/stalwart-mail, that’s the tell it predates the rename.

First boot prints an admin password to the logs; you finish setup in the web UI — adding your domain, creating accounts, and letting it generate a DKIM key. That part really is a few minutes of clicking. If only the rest of email were as kind.

The bit nobody can make easy

Advertisement

Here is where the honesty starts. The software is the simple part; the internet’s distrust of new mail senders is the hard part, and no binary fixes it.

To stand any chance of being delivered, you need all of the following correct, and any one of them being wrong will sink you:

  • A static IP with clean reputation. Mail providers judge you by your IP before they read a byte of your message. If your IP was previously used by a spammer, you inherit their sins.
  • Reverse DNS (PTR). The PTR record for your IP must resolve to your mail hostname, and that hostname must resolve back. No PTR, no delivery — many servers reject you outright.
  • Port 25 open and outbound. Most residential ISPs and a lot of cloud providers block port 25 by default to fight botnets. No port 25, no sending. You often have to request it be unblocked, and the answer is frequently no.
  • SPF, DKIM, DMARC and MTA-STS published correctly in DNS.

The DNS side looks roughly like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
; Mail routing
mail.example.com.    IN  A     203.0.113.10
example.com.         IN  MX 10 mail.example.com.

; SPF — only this host may send for the domain
example.com.         IN  TXT   "v=spf1 mx -all"

; DKIM — public key generated by Stalwart (selector "default", Ed25519)
default._domainkey.example.com. IN TXT "v=DKIM1; k=ed25519; p=11qYAY...base64key"

; DMARC — quarantine failures, send reports
_dmarc.example.com.  IN  TXT   "v=DMARC1; p=quarantine; rua=mailto:[email protected]"

I’ve written the DKIM record with an Ed25519 key rather than the old 2048-bit RSA out of habit — Stalwart can generate either, and modern DKIM supports Ed25519, which is shorter and cleaner in DNS. The one caveat: a handful of ancient verifiers only understand RSA, so the belt-and-braces move is to publish both an Ed25519 and an RSA key under different selectors and let Stalwart sign with both. For a new domain in 2025, RSA-2048 is the safe default and Ed25519 the nice-to-have.

And the PTR record, set on the IP side by whoever controls the reverse zone (usually your provider):

1
10.113.0.203.in-addr.arpa. IN PTR mail.example.com.

Get all of that perfect and you have earned the right to be treated as a suspicious unknown. New sending domains and IPs start with no reputation, which the big filters treat as guilty until proven innocent. You warm up slowly, you watch your DMARC reports, and you accept that for the first weeks your mail to large providers may sit in spam through no fault of your own.

Troubleshooting: the failures that actually happen

“My mail lands in spam at Gmail and Outlook.” Assuming SPF/DKIM/DMARC all pass (check the message headers at the receiving end — Gmail shows the authentication results explicitly), this is almost always reputation, not configuration. A brand-new IP has none, and there’s no config change that manufactures it; you warm up by sending low, legitimate volume over weeks. Verify alignment first, though: DMARC requires that the domain in SPF or DKIM aligns with the From domain, and a subtle mismatch there fails DMARC even when SPF and DKIM individually pass.

“Outbound mail just times out.” Nine times out of ten, port 25 outbound is blocked by your host. Test it with a raw connection to a known mail server on port 25 from the box itself; if it hangs, the port is filtered upstream and no amount of Stalwart config helps. This is the single most common reason a self-hosted server can receive mail but can’t send it.

“Inbound mail never arrives.” Check the MX record actually points at a hostname with a valid A record and open port 25 inbound, and that no firewall (or the container’s own port mapping) is swallowing it. A missing or wrong PTR won’t stop inbound, but a closed inbound 25 will.

“TLS certificate errors on the submission ports.” Stalwart can obtain certificates automatically, but it needs a reachable ACME challenge path. If it can’t complete the challenge (port 80/443 blocked, DNS not yet propagated), clients get certificate warnings on 465/587/993. Check the logs for the ACME failure rather than fiddling with the client.

“The admin UI locked me out after the first-boot password scrolled off.” The bootstrap admin password is printed once to the container logs. If you missed it, the recovery path is documented but tedious; grab it from the logs on first boot and store it in your password manager immediately.

Why you probably shouldn’t

I run a lot of things I shouldn’t, so understand the spirit in which I say this: for most people, self-hosting outbound mail is the wrong call.

The deliverability problem never fully goes away. You can do everything right and still be collateral damage on a shared blocklist, or get throttled because a neighbouring IP in your provider’s range misbehaved. Reputation is maintained forever, not set up once.

Then there’s the uptime burden. Mail is not a service you can let fall over for a day. If your server is down when someone emails you, the sender’s server retries for a while and then gives up — and you never learn the message existed. Backups, certificate renewal, security patching, monitoring: this is now your job, indefinitely. This is exactly the hidden, recurring cost I try to make people count honestly in self-hosting is not free — the setup is a weekend, the operation is forever, and email is the least forgiving service to operate.

For most people the pragmatic answer is one of two things. Pay for a mailbox from a provider whose entire business is keeping their IPs trusted. Or, if you want to host your own inbox for control and privacy, send through an authenticated SMTP relay — let a reputable relay carry the outbound reputation while Stalwart handles your storage, filtering and clients. You keep the fun parts and outsource the unwinnable fight. If your motivation is privacy rather than tinkering, it’s worth weighing that path against a managed private provider first; I go through the trade-offs in Proton vs Tuta vs self-hosted email, and the honest conclusion for many people is “don’t self-host the sending”.

Where it does make sense

Self-hosting mail earns its keep when control is the actual requirement, not the hobby. If you have a static IP with a genuinely clean, established reputation, your volume is low and predictable, and you treat the relay route as a perfectly respectable option, Stalwart is a delight to run. It’s also a brilliant lab: standing it up taught me more about how email really works than a decade of using it.

Just go in clear-eyed. The server is no longer the hard part. Convincing the rest of the internet to trust you still is — and it always will be.

Advertisement
Advertisement
Smarc
Written by Smarc

Founder and editor of vo.rs. A lifelong tinkerer who self-hosts far more than is sensible, hardens Linux boxes for fun, and prods the latest AI tools to see what they can really do. The how-to guides here are the notes Smarc wishes had existed the first time round.