Have a question?
Message sent Close

SPF Troubleshooting Guide

Common SPF Mistakes and Fixes

Intro to the SPF Troubleshooting Guide

What is SPF

The Sender Policy Framework (SPF) is an email-authentication technique which is used to prevent spammers from sending messages on behalf of your domain. With SPF an organisation can publish authorized mail servers.

TopDeliverability SPF Field Manual

This SPF Troubleshooting Guide wants to provide an easy way to solve common issues and mistakes while dealing with SPF records. It’s meant to be read and used by everyone, regardless their level of knowledge and understanding of Email Authentication.

In order to make this SPF Guide a true “Field Manual”, the main focus is on Common Mistakes (and Solutions) with regards to SPF Records.

SPF Syntax For Dummies

At the end of this guide you will find a detailed description of SPF Syntax and elements. If you are interested in learning SPF and Email Authentication in more detail, Top Deliverability provides Advanced Deliverability Courses.

For the purpose of this SPF guide, this is all that you need to know:

SPF is published as a TXT record on the DNS of a sending domain.

An SPF policy consists of multiple terms separated by whitespace. A term can be a modifier (such as v or redirect), or a matching mechanism (such as amxinclude, etc.).

A matching term has the following format:
[prefix][mechanism][:value]
The prefix determines the SPF validation outcome that the receiver should apply to the message if the sender matches the term. Allowed values are + (pass), ? (neutral), ~ (soft fail) or - (fail). The prefix is optional, if no prefix is defined, it defaults to pass (+).

The mechanism determines how to match an IP address against the term, supported values are aipv4ipv6mxptrincludeexists and all.

The value portion of a term is optional, and depends on the used mechanism. For most mechanisms the value allows you to point to other domains, and if omitted it defaults to the current domain.

Table of Contents

Common SPF Mistakes

Multiple SPF records

A very common issue. A domain can have only 1 SPF record. Having more than 1 SPF record will inevitably cause issues and authentication errors.

HOW TO FIX IT: combine them in one and removed the other records.

EXAMPLE: 
dig example.com TXT
> example.com. 21599 IN TXT "v=spf1 include:secureserver.net ~all" 
> example.com. 21599 IN TXT "v=spf1 include:_spf.google.com ~all"
 <– wrong!

dig example.com TXT
> example.com. 21599 IN TXT "v=spf1 include:secureserver.net include:_spf.google.com ~all" <– correct

Too many DNS lookups

The evaluation of an SPF policy may not exceed 10 additional lookups. The DNS query for the SPF policy record itself does not count towards this limit.

This means your record cannot generate more than 10 references to other domains.

Every instance of parameters “include”, “a”, “mx”, “ptr”, “exists”, “redirect” will generate one lookup. Additionally, if any domain that is referenced in an “include” contains another instance of those parameters it is also counted towards the 10 lookup limit.

A validator (the receiving email system) must not proceed after 10 lookups, and reject the SPF validation with a permerror error.

HOW TO FIX IT: remember that certain mechanisms, such as ip4, ip6 and all don’t count against the limit of 10 lookups. The “exp” modifier does not count against this limit because the DNS lookup to fetch the explanation string occurs after the SPF record has been evaluated. Converting “includes” to their respectives ip4 or ip6 (so called SPF flattening) it’s one of the potential solutions.

EXAMPLE:
v=spf1 a mx include:bluehost.com include:_spf.google.com ~all <– wrong!

In fact, in this example

  1. the mailserver is Google (mx is not needed because it’s part of include:_spf.google.com)
  2. include:bluehost.com contains many other includes, many might not be necessary (when resolved it shows v=spf1 include:spf2.bluehost.com include:_spf.qualtrics.com include:_spf.google.com include:_spf.salesforce.com include:sparkpostmail.com include:spf.mailjet.com include:spf.protection.outlook.com -all). Each of those includes have even more additional includes.
  3. the website is hosted on bluehost (the a mechanism is enough here)

You will need to remove all the redundant information

v=spf1 a include:_spf.google.com ~all <– correct

Syntax errors

SPF records should start with “v=spf1“.

HOW TO FIX IT: Make sure the txt records starts with v=spf1.

EXAMPLE: 
include:secureserver.net ~all <– wrong!
v=spf1 include:secureserver.net ~all <– correct

Deprecated mechanisms

The PTR mechanism has been deprecated in 2014 (check RFC7208 Section 5.5 for more info). It should not be used.
Some large receivers will skip the mechanism – or worse they’ll skip the entire SPF record – because such mechanisms cannot be easily cached. Imagine a large receiver doing a PTR lookup for millions of different connections… the size of the local cache explodes.

HOW TO FIX IT: just remove ptr mechanisms from your SPF records. In many cases a ptr mechanism can be replaced with the a mechanism.

EXAMPLE:
v=spf1 ptr include:secureserver.net ~all <– wrong!
v=spf1 a include:secureserver.net ~all <– correct

 

Double quoted TXT record

The content of a DNS TXT type record is always displayed with double quotes, but those quotes are not part of the record content. They are there for display purposes only, so you can distinguish the content start and end, since a TXT type record is allowed to contain whitespace characters.

Therefore, it is common for email services which instruct you how to set up your SPF to show those quotes in the example. You should not include those quotes in the record. If your record starts with "v=spf1 and not with v=spf1 , it won’t be recognized.

HOW TO FIX IT: just make sure to not include the quotes on your record! you can check your record with the dig tool.

EXAMPLE:
dig example.com TXT
> example.com. 21599 IN TXT "v=spf1 mx -all" <– correct
> example.com. 21599 IN TXT "\"v=spf1 mx -all\"" <– wrong!

 

Record length

SPF records have a 255 character string limit. Any record longer than 255 characters will fail the authentication check.

HOW TO FIX IT: It’s theoretically possible by combining multiple strings 255 characters long (up to a total of 4,000 characters) but this is not recommended.

you need to optimize (“minimize”) the SPF record by removing any extra mechanism or legacy ESP records and making good use of includes. The ptr mechanism has been deprecated years ago, so make sure to get rid of it – if you still have one in your record.

EXAMPLE: 

v=spf1 ip4:172.217.0.0/19 ip4:172.217.32.0/20 ip4:172.217.128.0/19 ip4:172.217.160.0/20 ip4:172.217.192.0/19 ip4:172.253.56.0/21 ip4:172.253.112.0/20 ip4:108.177.96.0/19 ip4:35.191.0.0/16 ip4:130.211.0.0/22 ip4:173.236.20.0/24 ip4:192.92.97.0/24 ip4:52.128.40.0/21 ~all <– wrong! (269 chars)
v=spf1 include:_spf1.mydomain.com include:spf2.mydomain.com ~all <– correct (64 chars)

 

Permissive all mechanism

An SPF record is interpreted from left to right, the all mechanism will match all senders that did not match the preceding mechanisms.

HOW TO FIX IT: You should place the all mechanism at the end of the SPF record, and use it with the ~ (softfail) or - (fail) prefix. Do note that if no prefix is set, the + (pass) is used by default.

EXAMPLE OF PERMISSIVE ALL MECHANISMS:
v=spf1 mx +all <– this allows everyone to send email with your domain
v=spf1 mx ?all <– this is a neutral prefix, which does the same as +all
v=spf1 mx all <– no prefix, so +all is used
v=spf1 mx <– no all mechanism, +all is assumed

 

Typos

Mistyping domains, mechanisms or adding unneeded blank spaces in SPF records is very common.

HOW TO FIX IT: always double check your SPF record and make sure to test/validate it using an online tool.

EXAMPLE:
v=spf1 inculde:secureserver.net ~all <– wrong!
v=spf1 include:secureserver.net ~all <– correct

Using the SPF record type

When SPF was first standardized in RFC4408, a new DNS record type named SPF was also specified. To accommodate rapid adoption of SPF, RFC4408 also allowed the use of the common TXT type record in case the SPF type record wasn’t supported by the involved systems. The official recommendation (back then) was to publish both types.

But this dual record solution caused more issues than it was intended to solve, most notably situations where an administrator would accidentally update one type but not the other. Adoption of the SPF type record didn’t pick up as hoped. So, when the SPF standard was refreshed in RFC7208, it was decided that the use of the SPF record type would be dropped in future versions.

HOW TO FIX IT: Although the SPF type DNS record is not deprecated just yet, you shouldn’t rely on it. And since it is always required to publish SPF as a TXT type anyway, we recommend only using the TXT type record to prevent accidental mismatches between the two.

EXAMPLE: 

dig example.com SPF
> example.com. 21599 IN SPF "v=spf1 mx -all"
 <– wrong!

dig example.com TXT
> example.com. 21599 IN TXT "v=spf1 mx -all" <– correct

APPENDIX: SPF Syntax

SPF Qualifiers

Qualifier Result Description
+ Pass the directive defines authorized transmitters;
this is the standard, i.e. if no qualifier is specified, + is assumed
Fail the directive defines unauthorized channels
~ SoftFail the directive defines unauthorized transmitters, but the receiver should treat this failure generously;
this qualifier is intended for testing purposes
? Neutral the directive defines channels about whose legitimacy nothing should be said; the channel must be accepted

SPF Mechanisms

Mechanism Set of host the directive will apply to Examples of valid mechanisms
all This mechanism always matches. It usually goes at the end of the SPF record. -all
Prohibits everything that isn’t specified.
ip4 The argument to the “ip4:” mechanism is an IPv4 network range. If no prefix-length is given, /32 is assumed (singling out an individual host address). ip4:192.168.0.1/16
Allow any IP address between 192.168.0.1 and 192.168.255.255.
ip6 The argument to the “ip6:” mechanism is an IPv6 network range. If no prefix-length is given, /128 is assumed (singling out an individual host address). ip6:1080::8:800:200C:417A/96
Allow any IPv6 address between 1080::8:800:0000:0000 and 1080::8:800:FFFF:FFFF.
a All the A records for domain are tested. If the client IP is found among them, this mechanism matches. If the connection is made over IPv6, then an AAAA lookup is performed instead.

If domain is not specified, the current-domain is used.

The A records have to match the client IP exactly, unless a prefix-length is provided, in which case each IP address returned by the A lookup will be expanded to its corresponding CIDR prefix, and the client IP will be sought within that subnet.

a
The current-domain is used.

a:example.com
Equivalent if the current-domain is example.com.

a:mailers.example.com
Perhaps example.com has chosen to explicitly list all the outbound mailers in a special A record under mailers.example.com.

a/24
If example.com resolves to 192.0.2.1, the entire class C of 192.0.2.0/24 would be searched for the client IP.

a:offsite.example.com/24
Similarly for offsite.example.com. If more than one A record were returned, each one would be expanded to a CIDR subnet.

mx All the A records for all the MX records for domain are tested in order of MX priority. If the client IP is found among them, this mechanism matches.

If domain is not specified, the current-domain is used.

The A records have to match the client IP exactly, unless a prefix-length is provided, in which case each IP address returned by the A lookup will be expanded to its corresponding CIDR prefix, and the client IP will be sought within that subnet.

mx
If a domain sends mail through its MX servers.

mx:deferrals.domain.com
If a domain sends mail through another set of servers whose job is to retry mail for deferring domains.

mx/24
mx:offsite.domain.com/24
Perhaps a domain’s MX servers receive mail on one IP address, but send mail on a different but nearby IP address.

ptr
(DEPRECATED)
The hostname or hostnames for the client IP are looked up using PTR queries. The hostnames are then validated: at least one of the A records for a PTR hostname must match the original client IP. Invalid hostnames are discarded. If a valid hostname ends in domain, this mechanism matches.

If domain is not specified, the current-domain is used.

(DEPRECATED – DO NOT USE)

ptr
A domain which directly controls all its machines (unlike a dialup or broadband ISP) allows all its servers to send mail. For example, hotmail.com or paypal.com might do this.

ptr:otherdomain.com
Any server whose hostname ends in otherdomain.com is designated.

exists Perform an A query on the provided domain. If a result is found, this constitutes a match. It doesn’t matter what the lookup result is – it could be 127.0.0.2.

When you use SPF macros with this mechanism, you can perform RBL-style reversed-IP lookups, or set up per-user exceptions.

exists:example.com
If example.com does not resolve, the result is fail. If it does resolve, this mechanism results in a match.
include The specified domain is searched for a match. If the lookup does not return a match or an error, processing proceeds to the next directive.

Warning: If the domain does not have a valid SPF record, the result is a permanent error. Some mail receivers will reject based on a PermError.

In the following example, the client IP is 1.2.3.4 and the current-domain is example.com.
include:example.com
If example.com has no SPF record, the result is PermError.
Suppose example.com’s SPF record were “v=spf1 a -all“.
Look up the A record for example.com. If it matches 1.2.3.4, return Pass.
If there is no match, other than the included domain’s “-all“, the include as a whole fails to match; the eventual result is still Fail from the outer directive set in this example.

SPF Modifiers

Modifier Description Example
redirect The SPF record for domain replace the current record. The macro-expanded domain is also substituted for the current-domain in those look-ups. In the following example, the client IP is 1.2.3.4 and the current-domain is example.com.

v=spf1 redirect=example.com

If example.com has no SPF record, that is an error; the result is unknown.
Suppose example.com’s SPF record was “v=spf1 a -all”.
Look up the A record for example.com. If it matches 1.2.3.4, return Pass.
If there is no match, the exec fails to match, and the -all value is used.

exp If an SMTP receiver rejects a message, it can include an explanation. An SPF publisher can specify the explanation string that senders see. This way, an ISP can direct nonconforming users to a web page that provides further instructions about how to configure SASL.

The domain is expanded; a TXT lookup is performed. The result of the TXT query is then macro-expanded and shown to the sender. Other macros can be used to provide an customized explanation.

SPF Macros

It’s an advanced (and smart) way of implementing SPF. You can learn more taking one of our Advanced Deliverability Courses.

If you liked this SPF Troubleshooting Guide,
you will love the DKIM Troubleshooting Guide
and the DMARC Troubleshooting Guide!

This website uses cookies and asks your personal data to enhance your browsing experience.