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=spf1meanpermerror, and the check fails for all mail. - The ten DNS lookup limit. The
include,a,mx,ptrandexistsmechanisms and theredirectmodifier count towards a limit of ten lookups; long chains of providerincludes easily exceed it. The result is againpermerror. - Empty answers. The standard also limits the number of lookups that return no record (void lookups); an
includeof a domain without SPF is a common source of errors. - Do not use
ptr– it is slow, unreliable and discouraged by the standard. -
~allversus-all: softfail mail usually gets through with a penalty, fail rejects it. Switch to-allonly 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_servicecheck belongs insmtpd_recipient_restrictionsafterreject_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
spawninmaster.cf; its time limit (policyd-spf_time_limit) has to be set inmain.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_PERMFAILandR_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_DNSFAILusually 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.