How to check SPF and DMARC records (command line)

SPF lists who may send mail for a domain; DMARC says what receivers should do when a message fails. Both are TXT records you can read in one command.

1. Read the records

dig +short TXT example.com | grep -i spf1
dig +short TXT _dmarc.example.com

On Windows (or anywhere without dig):

nslookup -type=TXT example.com
nslookup -type=TXT _dmarc.example.com

Results on 2026-10-01: google.com has v=spf1 include:_spf.google.com ~all and v=DMARC1; p=reject; rua=mailto:mailauth-reports@google.com. example.com, which sends no mail, publishes v=spf1 -all (nobody may send as it) and a strict p=reject DMARC policy.

Long records come in pieces. github.com's SPF record arrives as two quoted strings, because a single TXT string holds at most 255 characters. Receivers join them without a space, so read them as one line.

2. What the parts mean

3. The 10-lookup limit

An SPF check may cause at most 10 DNS lookups: every include, a, mx, ptr, exists and redirect counts, including those inside included records. Above 10, SPF fails with a permanent error for every message. This script follows the includes and counts (needs dig):

import re, subprocess, sys

def spf(domain):
    out = subprocess.run(["dig", "+short", "TXT", domain], capture_output=True, text=True).stdout
    for line in out.splitlines():
        record = "".join(re.findall(r'"([^"]*)"', line))  # long records come in 255-char chunks
        if record.lower().startswith("v=spf1"):
            return record
    return None

def count(domain, depth=0):
    record = spf(domain)
    if record is None:
        print("  " * depth + domain + ": no SPF record")
        return 0
    print("  " * depth + domain + ": " + record)
    n = 0
    for term in record.split()[1:]:
        mech = term.lstrip("+-~?").lower()
        if mech.startswith(("include:", "redirect=")):
            n += 1 + count(re.split("[:=]", term, maxsplit=1)[1], depth + 1)
        elif mech in ("a", "mx", "ptr") or mech.startswith(("a:", "mx:", "ptr:", "exists:", "a/", "mx/")):
            n += 1
    return n

total = count(sys.argv[1])
print("DNS lookups:", total, "(limit 10)" + (" - TOO MANY: SPF fails with permerror" if total > 10 else ""))

python3 spf_count.py github.com prints the include tree and 10: eight includes, a Salesforce exists and one nested SendGrid include. That is exactly at the limit, so one more email service would break it. google.com needs 1 lookup, example.com 0.

Without the command line

Our free DNS lookup tool checks SPF, DMARC and MX in your browser. For many domains at once, the Domain WHOIS, DNS & SSL checker on Apify returns SPF, DMARC policy, MX, SSL expiry and registration dates per domain, and the email checker tests address lists. Both export CSV, Excel or JSON.

Frequently asked questions

How do I check if SPF is set up correctly?

Read the record with dig or nslookup, make sure there is exactly one record starting with v=spf1, that it ends with -all or ~all, and that it needs at most 10 DNS lookups.

Where is the DMARC record?

In a TXT record at _dmarc.yourdomain.com, not at the domain itself.

What happens if SPF has more than 10 lookups?

Receivers treat SPF as a permanent error (permerror), so SPF fails for all your mail, which can also make DMARC fail.

Should I start DMARC with p=reject?

Start with p=none and a rua= address, read the reports until all your real senders pass, then move to quarantine and reject.

Written with AI assistance. The dig and nslookup commands and the Python lookup counter were run on 2026-10-01 for google.com, github.com and example.com. The Apify tool is ours; the APIs are free to call directly.