Can you self-host email at all

Before any install: deliverability is the hard part. Big mail providers (Gmail, Outlook, Yahoo) accept inbound from any properly-DNS'd server, but flag outbound from residential IP ranges and from new domains. Three pre-checks:

  • Port 25 outbound must work. Most residential ISPs block it; cloud VPSes mostly allow it but some require unblocking via a ticket.
  • The IP can't be on a major spam blocklist. Check Spamhaus, Barracuda, SORBS before claiming it.
  • Reverse DNS (PTR) must match your hostname. Most VPS providers expose a control-panel setting; some require a ticket.

If any of those is no, plan to use a transactional relay (Postmark, Mailgun, AWS SES) as the outbound smarthost. Stalwart handles that case cleanly.

Install

# Debian / Ubuntu
SV=0.10.0   # check stalw.art for current
curl -L -o /tmp/stalwart.deb \
    "https://github.com/stalwartlabs/mail-server/releases/download/v${SV}/stalwart-mail_${SV}_amd64.deb"
sudo apt install /tmp/stalwart.deb

# Or container
docker pull stalwartlabs/mail-server:latest
docker volume create stalwart-data
docker run -d \
    --name stalwart-mail \
    --restart unless-stopped \
    -p 25:25 -p 465:465 -p 587:587 -p 143:143 -p 993:993 \
    -p 4190:4190 -p 8080:443 \
    -v stalwart-data:/opt/stalwart-mail \
    stalwartlabs/mail-server:latest

First run prompts for the install wizard at http://<host>:8080/admin — pick storage backend (default: embedded RocksDB, fine up to thousands of users), set admin credentials, choose domains.

DNS records

Email DNS is the unforgiving part. For domain example.com with mail server mail.example.com:

; MX record — "who handles mail for example.com"
example.com.        IN  MX 10  mail.example.com.

; A / AAAA for the mail server
mail.example.com.   IN  A      203.0.113.10
mail.example.com.   IN  AAAA   2001:db8::10

; SPF — "only this server can send mail for example.com"
example.com.        IN  TXT    "v=spf1 ip4:203.0.113.10 ip6:2001:db8::10 -all"

; DKIM — Stalwart generates the key; copy its public part here
;   The "selector" is anything you like; "stalwart" is conventional
stalwart._domainkey.example.com.  IN  TXT  "v=DKIM1; k=rsa; p=<long-public-key>"

; DMARC — how receivers should treat failures
_dmarc.example.com. IN  TXT    "v=DMARC1; p=quarantine; rua=mailto:postmaster@example.com; aspf=s; adkim=s"

; (Optional but increasingly important) MTA-STS — TLS enforcement
_mta-sts.example.com. IN  TXT  "v=STSv1; id=2025090100"
mta-sts.example.com.  IN  CNAME  mail.example.com.

; Reverse DNS (in the IP provider's interface, NOT yours)
;   203.0.113.10  PTR  mail.example.com.

Stalwart's admin UI under Domains → example.com → DNS gives the exact records to copy/paste, with a "validate" button that re-resolves and reports red/green per record.

TLS

Mail TLS terminates inside Stalwart on ports 465/587/993. Two options:

  1. Let Stalwart get its own cert via ACME. Admin → Settings → TLS → enable ACME, set the contact email. Stalwart talks to Let's Encrypt directly — no certbot needed.
  2. Reuse an existing cert (e.g. one Caddy or nginx already manages). Drop the cert + key paths into the TLS config; reload on rotation.

Add a user mailbox

# Via CLI
sudo stalwart-cli account create amir@example.com \
    --display-name "Amir" \
    --password <password> \
    --quota 5G

# Or via the web admin (Accounts → Add)

Configure a mail client (Thunderbird, Apple Mail, K-9 Mail) with:

  • Incoming: IMAP on mail.example.com:993 (TLS) OR JMAP at https://mail.example.com/jmap
  • Outgoing: SMTP submission on mail.example.com:465 (TLS) or 587 (STARTTLS)
  • Username: amir@example.com
  • Sieve filtering on mail.example.com:4190

Anti-spam

Stalwart's built-in anti-spam combines:

  • SPF / DKIM / DMARC verification on inbound
  • Bayesian classifier per user, learning from "mark as spam" / "not spam"
  • DNS block lists (Spamhaus, SpamCop, etc.)
  • Greylisting (optional) — reject the first delivery attempt, accept the retry
  • SpamAssassin-style rules with the bundled rule set

For virus scanning, enable the ClamAV plugin (Stalwart talks to a local clamd socket).

Sieve filtering

Sieve scripts (per-user) auto-file incoming mail. Stalwart includes the standard Sieve interpreter plus the ManageSieve protocol on port 4190 for client editing:

require ["fileinto", "envelope"];

if address :is "from" "github@notifications.github.com" {
    fileinto "INBOX/GitHub";
    stop;
}

if anyof (header :contains "List-ID" "discuss.example.com",
          header :contains "Subject" "[announcements]") {
    fileinto "INBOX/Mailing Lists";
}

Apple Mail and Thunderbird have ManageSieve plugins (sieve.client / sieve add-on); Roundcube has one built in. Stalwart can also serve a webmail (it does not bundle one in-tree; pair with Roundcube or Snappymail).

Outbound relaying through a smarthost

If port 25 outbound is blocked (residential IP, some VPS providers), route outbound through a transactional service:

Admin → SMTP → Outbound → Relay
  Host: smtp.postmarkapp.com  (or smtp.mailgun.org, etc.)
  Port: 587
  Auth: STARTTLS, username/password

Inbound still arrives on your server directly via port 25; outbound traffic carries the relay's reputation and the relay does the DKIM-signing of its own headers.

Backups

One directory holds everything: configuration, the user database, all mailboxes (Maildir-like Stalwart format), DKIM keys, ACME certs. Snapshot it (filesystem snapshot or restic) on a nightly schedule. The bundled stalwart-cli backup command writes a portable archive too.

DKIM keys are the one piece worth a special note: lose them, every recipient flags your email as forged for whatever cache lifetime the DKIM records have. Keep an out-of-band copy of data/etc/dkim/.

Migrating from another server

Stalwart's import tool can read Maildir, mbox, or IMAP source servers and copy mailboxes into the local storage:

stalwart-cli import imap \
    --source imap.old.example.com \
    --user-source-creds amir@example.com:<oldpass> \
    --target-user amir@example.com

Combined with overlapping MX records during the transition, this is the standard migration path. Run with both inboxes active for a week, switch the MX, retire the old server.

When self-hosting email is the wrong call

For a small business or family where any-loss-of-email is a real-world problem, paying $5–10 per mailbox per month (Fastmail, Migadu, ProtonMail, Microsoft 365) buys vastly more deliverability than self-hosting. Self-hosting makes sense for technical curiosity, fine-grained control, jurisdictional / privacy concerns, or zero-cost-per-mailbox at scale — not because "it must be cheaper." It rarely is, once an outage costs you a real reply.