Andrew Mercer
on this page

SMTP was designed for a friendly network and has no built-in sender authentication. Anyone can send MAIL FROM:<[email protected]> and nothing in the base protocol stops them. Everything in this doc exists to bolt trust back on. None of it is Postfix-specific. The MTA just needs to publish it (DNS records, signatures) and check it (filters, milters).

SPF (Sender Policy Framework)

An SPF record is a DNS TXT record listing which hosts may send mail using the domain in the envelope sender (MAIL FROM):

domain.tld.  TXT  "v=spf1 mx ip4:203.0.113.10 include:mailgun.org -all"

The receiving server takes the envelope sender's domain, fetches its SPF record, and checks whether the connecting IP is authorized.

Mechanism / qualifier Meaning
ip4: / ip6: A literal address or CIDR range
a / mx The domain's own A or MX hosts
include: Also accept whatever another domain's SPF allows (how you authorize a provider)
-all Hard fail everything else
~all Soft fail: accept but mark suspicious. Common while rolling out
?all Neutral. Effectively no policy

Things that break SPF:

  • Missing include: for a relay provider. If you send through Mailgun, Gmail or Proton, their include must be in your record. Postfix being configured perfectly doesn't help if SPF fails at the receiver.
  • More than 10 DNS lookups (include, a, mx, redirect, nested includes all count) produces a permanent error. Flatten or remove unused includes.
  • Two SPF records on one name is also a permanent error. Merge them into one.
  • Forwarding: when a message is forwarded, the forwarder's IP isn't in the original domain's SPF. This is why DKIM matters.

DKIM (DomainKeys Identified Mail)

DKIM adds a cryptographic signature header (DKIM-Signature:) covering the body and selected headers, made with a private key held by the sending system. The public key is published in DNS under a selector:

sel2026._domainkey.domain.tld.  TXT  "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOC..."

The receiver finds the selector and domain in the signature (s=sel2026; d=domain.tld), fetches the key, and verifies that the signed parts weren't modified in transit. Unlike SPF, a DKIM signature survives forwarding as long as the message isn't altered.

Postfix doesn't sign by itself. Signing is added with a milter:

  • OpenDKIM: does one job (signing and verifying), with a simple config.
  • rspamd: a full spam filter that also does DKIM signing, ARC and DMARC. It's the better choice if you need filtering anyway.

The Postfix side is the same for either:

# /etc/postfix/main.cf
smtpd_milters     = inet:127.0.0.1:8891      # OpenDKIM's usual port (rspamd proxy: 11332)
non_smtpd_milters = $smtpd_milters           # also sign locally submitted mail (sendmail/cron)
milter_default_action = accept               # if the milter is down, deliver unsigned rather than fail

Postfix 3.11 changed the default milter_default_action to shutdown, which disconnects clients when a milter is unavailable. Set it explicitly to whichever behaviour you want.

Use 2048-bit RSA keys (Ed25519 DKIM is standardized but not yet universally verified, so publish it only alongside RSA). Rotate selectors periodically by publishing a new selector, switching signing to it, then removing the old one.

DMARC

DMARC ties SPF and DKIM to the domain a human actually sees, the header From:, and tells receivers what to do when that fails:

_dmarc.domain.tld.  TXT  "v=DMARC1; p=quarantine; rua=mailto:[email protected]; adkim=r; aspf=r"

A message passes DMARC if either:

  • SPF passes and the envelope-sender domain aligns with the From: domain, or
  • DKIM passes and the signing domain (d=) aligns with the From: domain.

"Relaxed" alignment (adkim=r, aspf=r, the default) accepts subdomains. "Strict" requires an exact match. Alignment closes the loophole where SPF and DKIM pass for some unrelated domain while the visible From: is forged.

p= is the policy: none (monitor and report only), quarantine (spam folder) or reject. Roll out in that order. Start at p=none with rua= reports, fix every legitimate source the reports reveal (forgotten newsletters, ticketing systems, scanners), then tighten.

What large receivers require now

Since 2024, Gmail and Yahoo (and Microsoft for Outlook.com since 2025) enforce baseline requirements:

  • All senders: SPF or DKIM passing, valid forward and reverse DNS (FCrDNS) for sending IPs, TLS for transmission, and low spam-complaint rates.
  • Bulk senders (roughly 5,000+ messages a day to these providers): SPF and DKIM, a DMARC record (at least p=none) with alignment, and one-click unsubscribe (List-Unsubscribe + List-Unsubscribe-Post) on marketing mail.

Even a tiny self-hosted server should meet the bulk-sender bar. It's the difference between "delivered" and "spam folder".

Transport security: MTA-STS, TLS-RPT and DANE

Server-to-server STARTTLS on port 25 is opportunistic, so an attacker on the path can strip the STARTTLS advertisement and read mail in plaintext. Two standards let a receiving domain demand authenticated TLS:

  • MTA-STS (RFC 8461): publish _mta-sts.domain.tld TXT "v=STSv1; id=20261009" and serve a policy file at https://mta-sts.domain.tld/.well-known/mta-sts.txt listing your MX hosts and mode: enforce. Senders that support it (Gmail, Microsoft, Postfix 3.11+ with an STS resolver) will only deliver over verified TLS to those hosts.
  • DANE for SMTP (RFC 7672): publish TLSA records for your MX hosts in a DNSSEC-signed zone. Postfix supports it natively as a sender (smtp_dns_support_level = dnssec, smtp_tls_security_level = dane).
  • TLS-RPT (RFC 8460): _smtp._tls.domain.tld TXT "v=TLSRPTv1; rua=mailto:[email protected]" gets you daily reports of senders' TLS failures to your domain.

DNS blocklists (DNSBLs / RBLs)

DNS-based blocklists answer "has this IP been seen sending spam?" via a specially formed DNS lookup. Postfix supports them natively (reject_rbl_client zen.spamhaus.org) and, better, through postscreen, which scores several lists before a connection ever reaches smtpd.

Spamhaus and public resolvers: Spamhaus refuses queries arriving via large public resolvers (8.8.8.8, 1.1.1.1, 9.9.9.9…) and returns a special error code instead. A server using one of those resolvers silently gets no protection, or with careless configs, rejects everything. Run a local caching resolver (unbound) on the mail server, and use a free Spamhaus DQS key if your volume requires it.

Common lists: zen.spamhaus.org (combined), bl.spamcop.net, b.barracudacentral.org (requires registration), psbl.surriel.com. Check your own sending IP against them too, since being listed is a frequent cause of rejected outbound mail.

Greylisting

Greylisting isn't a blocklist at all; it's a behavioural test. On first contact from an unseen (client IP, envelope sender, envelope recipient) triplet, the server answers with a 4xx temporary failure and remembers the triplet. A well-behaved MTA retries a few minutes later and gets through. Much spamware never retries.

It trades a delivery delay (minutes, occasionally longer for providers that retry from different IPs) for a large cut in spam. Its effectiveness has declined as spam has moved to real MTAs and compromised accounts, and it hurts time-sensitive mail (2FA codes, password resets). Modern setups often use postscreen's pregreet and protocol tests, or rspamd's selective greylisting of only suspicious mail, instead of greylisting everyone. See Security and Hardening for Postfix's postgrey.

Content filtering

  • Header/body regex (Postfix header_checks, body_checks) is fast and blunt. It's good for stamping out one specific campaign, but useless as a general strategy.
  • Scoring filters (rspamd, SpamAssassin) combine hundreds of signals: Bayesian classification, DNSBLs, URL blocklists, SPF/DKIM/DMARC results, fuzzy hashes. They add a score and headers, and reject or quarantine above thresholds. They're hooked into Postfix as a milter (rspamd) or content filter (amavisd + SpamAssassin).
  • Antivirus (ClamAV) is usually run through the same filter.

Open relay testing

An open relay is both a self-inflicted deliverability disaster and a way to unknowingly distribute spam. Always test a new server from outside your trusted networks. Testing from inside mynetworks will always succeed and proves nothing. See Security and Hardening for the test.

Checking your work

dig +short TXT domain.tld | grep spf
dig +short TXT sel2026._domainkey.domain.tld
dig +short TXT _dmarc.domain.tld
dig +short TXT _mta-sts.domain.tld

Then send a message to an external mailbox you control (Gmail works well) and read the receiver's verdict in the full headers:

Authentication-Results: mx.google.com;
       dkim=pass [email protected] header.s=sel2026;
       spf=pass (google.com: domain of [email protected] designates 203.0.113.10 as permitted sender) [email protected];
       dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=domain.tld

All three should read pass. Third-party testers (for example mail-tester-style services that give you a throwaway address and score what arrives) also report blocklisting, PTR problems and content issues in one go.