Server

Postfix as a Relay, with Milters

By Prabath Thalangama· October 24, 2026· 4 min read
#postfix#email#smtp

Introduction

A relay (or smarthost) Postfix accepts mail from trusted sources and forwards it toward the internet — signing it, applying policy, and optionally sending through an upstream provider. This is the box your applications, MFPs, and monitoring point their SMTP at.

Relay for internal networks

# main.cf
myhostname = mail.example.com
mydomain = example.com
myorigin = $mydomain

# who may relay THROUGH us
mynetworks = 127.0.0.0/8 10.0.0.0/8 [::1]/128
# do NOT set mynetworks too wide; an open relay gets abused within hours

# we relay outbound for our own domains only (no local delivery here)
mydestination =
relay_domains =

Relay through a smarthost (with auth)

relayhost = [smtp.provider.example]:587
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = static:apikey:SECRET      # or hash:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous
smtp_tls_security_level = encrypt
smtp_tls_wrappermode = no        # STARTTLS on 587
postmap /etc/postfix/sasl_passwd   # if using a hash: map; chmod 600 the source

The [brackets] disable MX lookup for the smarthost name.

Restriction stages

Postfix evaluates restrictions per SMTP phase; each returns OK/REJECT/DUNNO and you stop at the first decisive result:

smtpd_client_restrictions =
    permit_mynetworks
    permit_sasl_authenticated
    reject_unknown_client_hostname

smtpd_helo_required = yes
smtpd_helo_restrictions =
    permit_mynetworks
    permit_sasl_authenticated
    reject_invalid_helo_hostname
    reject_non_fqdn_helo_hostname

smtpd_sender_restrictions =
    permit_mynetworks
    permit_sasl_authenticated
    reject_non_fqdn_sender
    reject_unknown_sender_domain

smtpd_recipient_restrictions =
    permit_mynetworks
    permit_sasl_authenticated
    reject_unauth_destination        # THE anti-open-relay rule — keep it
    reject_non_fqdn_recipient
    reject_unknown_recipient_domain

smtpd_relay_restrictions =
    permit_mynetworks
    permit_sasl_authenticated
    reject_unauth_destination

reject_unauth_destination (in smtpd_relay_restrictions, enabled by default in modern Postfix) is what stops you being an open relay. Never remove it.

postscreen handles pre-queue connection filtering (zombies, pipelining) — see the postscreen post.

Milters — the filter chain

Milters (mail filters) inspect/modify mail during the SMTP session. Order matters: sign after content filters, verify before.

# main.cf
smtpd_milters =
    unix:/run/opendkim/opendkim.sock,
    unix:/run/opendmarc/opendmarc.sock
non_smtpd_milters = unix:/run/opendkim/opendkim.sock    # sign locally-submitted mail too
milter_default_action = accept        # if a milter is down, don't lose mail
milter_protocol = 6

OpenDKIM (sign outbound, verify inbound)

# /etc/opendkim.conf
Mode                    sv               # sign + verify
Socket                  local:/run/opendkim/opendkim.sock
KeyTable                refile:/etc/opendkim/KeyTable
SigningTable            refile:/etc/opendkim/SigningTable
InternalHosts           /etc/opendkim/TrustedHosts   # which senders get signed
Selector                mail

Publish the public key at mail._domainkey.example.com (TXT). See SPF, DKIM and DMARC end to end.

OpenDMARC (verify inbound alignment)

# /etc/opendmarc.conf
Socket                  local:/run/opendmarc/opendmarc.sock
AuthservID              mail.example.com
TrustedAuthservIDs      mail.example.com
RejectFailures          false          # add an Authentication-Results header; don't reject at MTA
HistoryFile             /var/run/opendmarc/opendmarc.dat

SpamAssassin / rspamd

spamass-milter or rspamd's milter mode — add before OpenDMARC in the chain for inbound scanning. For a pure outbound relay you usually don't scan.

Verification and troubleshooting

postconf -n                              # non-default settings (the useful view)
postfix check
postqueue -p; mailq
echo test | sendmail -v [email protected]    # verbose delivery trace
tail -f /var/log/mail.log
opendkim-testkey -d example.com -s mail -vvv
  • "Relay access denied" for a legitimate sender — their IP isn't in mynetworks and they're not SASL-authenticated. Add the network or require auth.
  • You became an open relay — smtpd_relay_restrictions / smtpd_recipient_restrictions missing reject_unauth_destination, or mynetworks is 0.0.0.0/0. Fix immediately; you'll be blacklisted fast.
  • DKIM signature not added — the milter socket path/permissions (postfix must read it — UserID opendkim:postfix or socket group), the sender isn't in InternalHosts/SigningTable, or non_smtpd_milters missing for local submissions.
  • Mail deferred to a smarthost: "SASL authentication failed" — wrong credentials in sasl_passwd, forgot postmap, or smtp_sasl_security_options disallows the mechanism the provider offers (noplaintext blocks LOGIN/PLAIN which most providers need over TLS — remove noplaintext).
  • Milter timeout / mail piling up — a milter process is slow or dead; milter_default_action = accept prevents mail loss but you lose filtering. Check the milter's own logs and socket.
  • reject_unknown_client_hostname blocking valid senders — strict rDNS requirement; many legit small senders fail it. Use reject_unknown_reverse_client_hostname (looser) or drop it and rely on DNSBLs/postscreen.
  • TLS to smarthost fails — smtp_tls_security_level = encrypt + the smarthost's cert chain; may downgrades silently, verify requires a valid cert.
PrabathStuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.