How to Check SPF, DKIM, and DMARC Records in Python

How to Check SPF, DKIM, and DMARC Records in Python

如何使用 Python 检查 SPF、DKIM 和 DMARC 记录

If your app sends email — transactional or marketing — three DNS records decide whether it lands in the inbox or the spam folder: SPF, DKIM, and DMARC. Here’s how to look them up and sanity-check them in Python, no third-party API required. Install the one dependency: pip install dnspython

如果你的应用程序需要发送电子邮件(无论是事务性邮件还是营销邮件),有三项 DNS 记录决定了邮件是进入收件箱还是被丢进垃圾邮件文件夹:SPF、DKIM 和 DMARC。以下是如何在 Python 中查询并验证这些记录的方法,无需任何第三方 API。首先安装依赖库:pip install dnspython

SPF: who is allowed to send

SPF:谁被允许发送邮件

SPF lives in a TXT record on the domain itself and starts with v=spf1.

SPF 存在于域名本身的 TXT 记录中,并以 v=spf1 开头。

import dns.resolver

def get_spf(domain):
    for rec in dns.resolver.resolve(domain, "TXT"):
        txt = b"".join(rec.strings).decode()
        if txt.startswith("v=spf1"):
            return txt
    return None

print(get_spf("github.com")) 
# v=spf1 ip4:... include:_spf.google.com ~all

A quick gotcha worth checking: SPF allows at most 10 DNS-querying mechanisms (include, a, mx, ptr, exists, redirect). Go over and receivers return permerror, which quietly breaks authentication:

一个值得注意的陷阱:SPF 最多允许 10 个 DNS 查询机制(include、a、mx、ptr、exists、redirect)。如果超过这个限制,接收方会返回 permerror,这会悄无声息地导致身份验证失败:

def spf_lookup_count(spf):
    return sum(spf.count(m) for m in ("include:", "a:", "mx:", "ptr", "exists:", "redirect="))

spf = get_spf("example.com")
if spf and spf_lookup_count(spf) > 10:
    print("⚠️ SPF exceeds the 10-lookup limit")

DMARC: the policy that ties it together

DMARC:将一切串联起来的策略

DMARC is a TXT record on the _dmarc. subdomain and starts with v=DMARC1.

DMARC 是位于 _dmarc. 子域名下的 TXT 记录,并以 v=DMARC1 开头。

def get_dmarc(domain):
    try:
        for rec in dns.resolver.resolve(f"_dmarc.{domain}", "TXT"):
            txt = b"".join(rec.strings).decode()
            if txt.startswith("v=DMARC1"):
                return dict(
                    kv.strip().split("=", 1) for kv in txt.split(";") if "=" in kv
                )
    except dns.resolver.NXDOMAIN:
        return None

print(get_dmarc("github.com")) 
# {'v': 'DMARC1', 'p': 'reject', 'rua': 'mailto:...'}

The key field is p: none (monitor only), quarantine (spam folder), or reject (bounce). If a domain sends real mail but has p=none, it’s not protected against spoofing yet.

关键字段是 pnone(仅监控)、quarantine(进入垃圾邮件文件夹)或 reject(拒收)。如果一个域名发送真实邮件但配置为 p=none,则说明它尚未受到防欺诈保护。

DKIM: the signature key

DKIM:签名密钥

DKIM is trickier because you need the selector — a label chosen by the sender that lives at SELECTOR._domainkey.DOMAIN. There’s no way to enumerate selectors from DNS, so you try common ones:

DKIM 比较棘手,因为你需要“选择器”(selector)——这是由发送方选择的标签,位于 SELECTOR._domainkey.DOMAIN。由于无法从 DNS 枚举选择器,因此通常尝试一些常见的名称:

COMMON_SELECTORS = ["google", "default", "s1", "s2", "k1", "selector1", "selector2", "dkim"]

def find_dkim(domain):
    found = {}
    for sel in COMMON_SELECTORS:
        try:
            for rec in dns.resolver.resolve(f"{sel}._domainkey.{domain}", "TXT"):
                txt = b"".join(rec.strings).decode()
                if "v=DKIM1" in txt or "p=" in txt:
                    found[sel] = txt
        except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
            continue
    return found

print(find_dkim("github.com").keys())

If none of the common selectors resolve, the domain may use a custom one — you’ll need to read it from an actual signed message’s DKIM-Signature: header (s= tag).

如果所有常见选择器都无法解析,说明该域名可能使用了自定义选择器——你需要从实际已签名邮件的 DKIM-Signature: 邮件头(s= 标签)中读取它。

Putting it together

综合应用

def audit(domain):
    spf = get_spf(domain)
    dmarc = get_dmarc(domain)
    dkim = find_dkim(domain)
    
    print(f"{domain}")
    print(f" SPF : {'✓' if spf else '✗ missing'}")
    print(f" DMARC : {dmarc.get('p') if dmarc else '✗ missing'}")
    print(f" DKIM : {', '.join(dkim) if dkim else '✗ none of the common selectors'}")

audit("your-domain.com")

Run that against your sending domain before you ship a campaign. Missing DKIM or a p=none DMARC are the two most common reasons legitimate mail gets filtered after Gmail and Yahoo tightened bulk-sender rules in 2024.

在发送营销活动之前,请针对你的发件域名运行此脚本。自 2024 年 Gmail 和 Yahoo 收紧批量发件人规则以来,缺少 DKIM 或配置为 p=none 的 DMARC 是导致合法邮件被过滤的两个最常见原因。

When you need this at scale

当你需要大规模检查时

The snippets above are perfect for one domain in a script. If you’re validating whole lists, warming a new sending domain, or want the checks plus blacklist and deliverability scoring in one pass, I built these into Bulko’s free email tools — same logic, no code to run. But for a quick programmatic check, dnspython is all you need.

上述代码片段非常适合在脚本中检查单个域名。如果你需要验证整个列表、预热新的发件域名,或者希望一次性完成检查、黑名单查询和送达率评分,我已将这些功能集成到 Bulko 的免费电子邮件工具中——逻辑相同,无需编写代码。但对于快速的程序化检查,dnspython 就足够了。