Email authentication

SPF record parser

Paste a draft SPF record and read it term by term, with the faults named, before anything is published.

Free, no accountIn your browser

Paste the record exactly as you mean to publish it. Nothing is looked up or stored.

Try

Result

Nothing to read yet

Paste a draft SPF record and every term is read as you type.

What this is

This SPF record parser reads a record you paste rather than a domain you own, so a draft can be checked term by term before it is ever published - nothing here is looked up, which is also the one thing it cannot tell you.

An SPF record is a single line of terms read left to right, and the first one that matches the sending server decides the answer. Most of what goes wrong with one is visible in the text alone: a mechanism nobody spelled right, an address with an impossible prefix, a "redirect" sitting beside an "all" that cancels it, or terms added after the "all" where no receiver will ever reach them.

The rest only shows up once the record is live, because it depends on what other people publish: an "include" that costs four lookups instead of one, a target with nothing in it. That half needs a resolver and belongs to the SPF checker; this page is the half you can settle while the record is still in a text box.

How to read the result

The table lists every term in the order a receiver works through them, with what each one authorises and whether it spends a DNS lookup. The lookup column is where a record usually goes wrong later: ip4, ip6 and all cost nothing at all, while each include, a, mx, ptr and exists costs one before its own chain is even followed.

A red finding is a syntax error - the kind that makes a receiver return PermError and ignore the whole record, so it is worse for your mail than the one term you got wrong. An amber one is a term that is valid but does nothing useful where it sits.

This page checks a draft record’s syntax and lookup count, not how SPF relates to DKIM or DMARC. For how the three fit together, what order to fix them in, and what to do once one fails, see our guide How SPF, DKIM and DMARC work together, and what to do when one fails.

Common mistakes

What goes wrong most often, and how to tell.

  • Adding a second record instead of editing the one that existsEach new sender gets its own "v=spf1" line, and now the domain publishes two. A receiver finding more than one has to return PermError, which is a worse outcome than either of the two records would have given on its own.
  • Putting a new include after the "all" at the endThe "all" matches every sender, so evaluation stops there and nothing after it is ever read. The record looks like it lists the new sender, the new sender keeps failing SPF, and nothing in the text is obviously wrong.
  • Writing an address with a prefix the family does not haveAn "ip4" term with a /64 on it, or an octet written as 010 because a spreadsheet padded it, is a syntax error rather than a near miss: the receiver stops reading and the record authorises nobody.
  • Trusting a lookup count taken from the text aloneCounting the include terms in a draft gives the floor, not the total. One of them can point at a record holding three more includes of its own, and all four lookups come out of the same budget of ten.

Questions

What people ask about this tool.

  • No. It reads the text in the box and makes no DNS query at all, which is why it can check a record that does not exist yet. The trade-off is that it cannot tell you whether this record is the one actually published, or what its includes resolve to.
  • Because it assumes every "include", "a" and "mx" resolves exactly once. An include points at somebody else's record, which can hold includes of its own, and those count against the same limit of ten - so the number here is the floor and the real cost can only be higher.
  • A sender matching none of the terms gets the default result, Neutral, under RFC 7208 section 4.7 - which most receivers treat exactly as they treat a domain with no SPF record. The record then only helps the senders you listed and says nothing about anyone else.
  • No. An include asks whether the other record would pass this sender and carries on if it would not; a redirect hands the whole answer over to the other record, including its "all". A redirect is also ignored outright whenever the record has an "all" of its own.
  • RFC 7208 treats an unrecognised term as a syntax error in the record, so a receiver returns PermError and ignores every other mechanism in it. A single misspelled "include" leaves every correctly listed server unauthorised.

Sources

Every fixed value here comes from the provider’s own documentation. These pages change, so each says the day it was last read.

Use it from the API

The same check, as one request.

Request
curl -X POST https://resellerhq.com/api/tools/spf-record-parser \
  -H "Content-Type: application/json" \
  -d '{"record":"v=spf1 include:_spf.google.com ip4:203.0.113.0/24 ~all"}'
Response
{
  "tool": "spf-record-parser",
  "input": {
    "record": "v=spf1 include:_spf.google.com ip4:203.0.113.0/24 ~all"
  },
  "result": {
    "verdict": {
      "tone": "success",
      "headline": "This record reads cleanly, at about 1 of 10 lookups",
      "summary": "Every term is one RFC 7208 defines, and the record takes a position on unlisted senders."
    },
    "findings": [
      {
        "id": "lookup-count-is-an-estimate",
        "severity": "info",
        "title": "About 1 of the 10 DNS lookups, as written",
        "detail": "This counts one lookup for each \"include\", \"a\", \"mx\", \"ptr\" and \"exists\" term, and one for a \"redirect\" that is not cancelled by an \"all\" - it is an estimate, not a count. Nothing is published yet to resolve, so a nested include inside one of these can add lookups this page cannot see: 1 is the floor, never the total."
      },
      {
        "id": "record-read-term-by-term",
        "severity": "pass",
        "title": "3 terms read, in order",
        "detail": "Every term of this record is listed below with what it does, in the order a receiver works through them - the first one that matches the sending server decides the result, and the rest are never reached (RFC 7208 §4.6.2)."
      },
      {
        "id": "terminal-all-present",
        "severity": "pass",
        "title": "The record ends with \"~all\"",
        "detail": "A sender matching none of the terms in front of it gets SoftFail, the usual setting while a sender list is still being confirmed - the record takes a position on everyone, which is the point of having one."
      }
    ],
    "parts": [
      {
        "kind": "table",
        "title": "Every term, in the order a receiver reads them",
        "columns": [
          "#",
          "Term",
          "What it does",
          "Lookups"
        ],
        "mono": [
          1
        ],
        "rows": [
          [
            "1",
            "include:_spf.google.com",
            "Also passes whatever _spf.google.com passes, by reading that record too",
            "1"
          ],
          [
            "2",
            "ip4:203.0.113.0/24",
            "Passes 203.0.113.0/24",
            "-"
          ],
          [
            "3",
            "~all",
            "Soft-fails every sender not matched above",
            "-"
          ]
        ],
        "note": "Only the terms marked with a lookup count against the limit of 10; ip4, ip6 and all cost nothing."
      },
      {
        "kind": "facts",
        "title": "The record as a whole",
        "rows": [
          [
            "Terms",
            "3"
          ],
          [
            "DNS lookups, estimated",
            "1 of 10"
          ],
          [
            "Ends with",
            "~all"
          ],
          [
            "Characters",
            "54"
          ]
        ]
      },
      {
        "kind": "record",
        "title": "The record as read",
        "value": "v=spf1 include:_spf.google.com ip4:203.0.113.0/24 ~all",
        "note": "Exactly the first record pasted above, with nothing added or reordered."
      }
    ],
    "limits": [
      "This reads the text you pasted and nothing else. It cannot say whether this record is published anywhere, whether the domain already has another one beside it, or what any of its includes actually resolve to.",
      "The lookup count is an estimate that assumes each \"include\", \"a\" and \"mx\" resolves exactly once. An include whose own record includes two more costs three, not one - run the SPF checker against the domain once this record is published to count what it really spends.",
      "A \"ptr\" mechanism and any macro such as %{i} depend on the sending server's own IP address, which no amount of reading the record can supply."
    ]
  }
}

Resell mailbox licences for the domains you just checked, under your own brand.

Get started

Company names and logos are trademarks of their owners, shown only to identify each service.