note · networking

SPF, Postfix and rspamd: common delivery problems

SPF fails more often because of small details in the record or in the recipient’s setup than because of an actually forged sender. An overview of what comes up most often.

  • Only one SPF record per domain. Two TXT records starting with v=spf1 mean permerror, and the check fails for all mail.
  • The ten DNS lookup limit. The include, a, mx, ptr and exists mechanisms and the redirect modifier count towards a limit of ten lookups; long chains of provider includes easily exceed it. The result is again permerror.
  • Empty answers. The standard also limits the number of lookups that return no record (void lookups); an include of a domain without SPF is a common source of errors.
  • Do not use ptr – it is slow, unreliable and discouraged by the standard.
  • ~all versus -all: softfail mail usually gets through with a penalty, fail rejects it. Switch to -all only once all sending servers are known.
  • Macros (%{i}, %{s} and others) are supported differently by different implementations. Verify a record with macros against the real recipient, not just a syntax validator, and use macros only where they are genuinely necessary.

SPF verifies the envelope address (MAIL FROM) and HELO, not the From header the user sees. Alignment between them is handled by DMARC.

Forwarded mail arrives from the forwarding server’s address, which the original domain’s SPF does not know, so the check fails. The fix is SRS (Sender Rewriting Scheme) on the forwarding server, in Postfix for example via postsrsd.

  • The check_policy_service check belongs in smtpd_recipient_restrictions after reject_unauth_destination, so that the SPF result cannot open a relay.
  • Authenticated users (permit_sasl_authenticated) should pass earlier, otherwise your own outgoing mail sent via submission is checked too.
  • The policy daemon is started via spawn in master.cf; its time limit (policyd-spf_time_limit) has to be set in main.cf, otherwise Postfix kills it before it answers.
  • The SPF result is carried by the symbols R_SPF_ALLOW, R_SPF_FAIL, R_SPF_SOFTFAIL, R_SPF_NA, R_SPF_PERMFAIL and R_SPF_DNSFAIL – the message history shows at a glance why the check ended the way it did.
  • Mail arriving through your own relay or filter must be evaluated against the last external address; your own relays and networks therefore belong among the local ones, otherwise SPF is checked against the relay’s address.
  • R_SPF_DNSFAIL usually does not mean a sender error, but an unreliable resolver on the recipient’s side.
  • When both rspamd and policyd-spf check SPF, the results can diverge. The check should live in one place.

Verify the record by sending a real message to a test address (e.g. mail-tester) and reading the result in the headers and in the rspamd history, not just with a syntax validator.

published 05/2026updated 09/2026 tags: postfix · mail · spf · rspamd networking →

← back to explore