Skip to the checker
ColdLint

Check SPF, DKIM, and DMARC before you send cold email

Look up four DNS records on the domain in the From address before a cold campaign goes out: SPF, DKIM, DMARC, and MX. This page says what each record is, what a working one looks like, how to query it, and the mistakes that make receivers ignore it.

Published November 4, 2026 by ColdLint.

Run these from a terminal. dig is usual on macOS and Linux. nslookup is on Windows too. example.com is a stand-in for the From domain. ColdLint's SPF, DKIM, and DMARC checker runs the same kind of lookup and explains the result. These commands cover cases it does not. Pasting the email and the domain on the home checker runs that DNS lookup beside the copy rules.

What you are proving

SPF and DKIM tell a receiver whether the message came from the domain it claims. DMARC adds a policy for failures, and it requires alignment with the domain in the visible From header. MX is not authentication. It is where mail for the domain is accepted, including replies.

SPF is judged on the envelope sender (the Return-Path), not the From line. DKIM aligns when d= matches the From domain. Relaxed alignment, the default, lets a subdomain match the registered domain. A sequencer that signs or bounces on its own domain can pass SPF or DKIM and still fail DMARC. Publish a key whose d= is your sending domain.

SPF

SPF is one TXT record on the sending domain. It is not a CNAME, and it starts with v=spf1. Mail that only leaves through Google Workspace can use:

v=spf1 include:_spf.google.com ~all

Mail that only leaves through Microsoft 365 can use:

v=spf1 include:spf.protection.outlook.com ~all

If both send, both includes belong on that same record. Add the cold-email tool's include or ip4 there too. Copy the mechanism from the tool's docs. Do not publish a second TXT to hold it.

v=spf1 include:_spf.google.com include:spf.protection.outlook.com ~all

~all soft-fails everyone else. -all is a hard fail once the list is complete, and Microsoft's wizard often prints it. +all lets any host send as you. Do not publish it.

Query it:

dig TXT example.com +short
nslookup -type=TXT example.com

You want a single answer that starts with v=spf1. Other TXT records, such as a site-verification token, are normal and are not a second SPF record. Non-existent domain means that hostname is not in DNS.

Two answers that both start with v=spf1 are a permanent error. Receivers do not choose between them. Merge every include and ip4 into one TXT and delete the other. Several quoted strings inside one TXT are still one record. That is normal for a long value. Two answers are two records.

SPF allows at most 10 DNS lookups per check. include, a, mx, ptr, exists, and redirect count, and so does every lookup inside an included record. ip4, ip6, and all do not. Google's include and Microsoft's include each expand further, so a short line can already be near the cap. Past 10, the check is a permanent error. Do not use ptr. A second SPF record does not add budget.

Publish it on the host that sends. For example.com, that is example.com, not www. ColdLint treats zero v=spf1 answers as missing and more than one as invalid. It shows the text and does not walk includes or count the 10 lookups. +all is still reported as present. Read the string.

DKIM

DKIM signs selected headers and the body with a private key. You publish the matching public key at selector._domainkey.example.com. The selector is the name in the signature, chosen by the system that signs. A TXT key has this shape. The p= value is the public key. Copy it from the provider.

v=DKIM1; k=rsa; p=BASE64_PUBLIC_KEY

An empty p= revokes the key. No p= tag means the record is not usable. A version tag, if present, must be v=DKIM1 and must be first. A long value split into quoted strings is still one TXT record.

In Google Workspace, open Apps, Google Workspace, Gmail, Authenticate email. Publish the TXT on the host shown there. The default selector is google, at google._domainkey.example.com.

dig TXT google._domainkey.example.com +short
nslookup -type=TXT google._domainkey.example.com

Microsoft 365 uses two CNAME records so it can rotate keys. You do not paste the public key yourself. For example.com in a tenant whose initial domain is example.onmicrosoft.com, publish:

selector1._domainkey.example.com.  CNAME  selector1-example-com._domainkey.example.onmicrosoft.com.
selector2._domainkey.example.com.  CNAME  selector2-example-com._domainkey.example.onmicrosoft.com.

The target host uses that tenant's initial domain, and dots in your domain become hyphens. After both CNAMEs answer, enable DKIM for the domain in the Microsoft Defender portal. Query both:

dig CNAME selector1._domainkey.example.com +short
dig CNAME selector2._domainkey.example.com +short
nslookup -type=CNAME selector1._domainkey.example.com

A cold-email tool gives you a third selector. Publish the CNAME or TXT it documents, on the sending domain. Messages that tool signs do not use the google selector. A key at google._domainkey does not mean the campaign is signed.

ColdLint probes google, selector1, selector2, s1, s2, k1, k2, k3, default, and a few vendor names such as mandrill and zoho. It follows CNAMEs, so Microsoft's default pair can show up. Anything else is not found on common selectors, which is not proof the key is missing. An empty p= does not count. There is no box for a selector you type.

DMARC

DMARC is one TXT record on _dmarc.example.com. It does not sit on the apex beside SPF. A sound first record is:

v=DMARC1; p=none; rua=mailto:dmarc@example.com

v=DMARC1 comes first, and p= is required. p=none only asks for aggregate reports. Read them before you move to p=quarantine. p=reject while a tool still sends unsigned will get that mail rejected where DMARC is enforced.

Query it:

dig TXT _dmarc.example.com +short
nslookup -type=TXT _dmarc.example.com

One answer, starting with v=DMARC1. A second record is invalid, and so is a record with no p=. Point rua at a mailbox you read. If that mailbox is on another organizational domain, the destination must publish an authorization TXT. For reports about example.com sent to dmarc@reports.example.net, that name is example.com._report._dmarc.reports.example.net and the value starts with v=DMARC1. Skip ruf at first. It asks for copies of failing messages, and many receivers do not send them.

ColdLint calls a missing record missing, a record with no p= invalid, and p=none monitor only rather than a failure. p=quarantine and p=reject are enforcing. A missing record gets a note recommending the p=none starter. The checker reads the first DMARC record only, so dig is how you notice a second one. It does not require rua. Add one or the reports have nowhere to go.

MX

MX records name the hosts that accept mail for the domain. Each has a priority. A lower number is tried first.

dig MX example.com +short
nslookup -type=MX example.com

You want at least one hostname, or replies and bounces have nowhere to go. Do not fall back to the domain's A record. ColdLint lists hosts and priorities and does not judge the provider. Missing MX is called out. A timeout is a lookup error. A cached answer can linger until the TTL expires.

A sensible order

  • Publish MX so a reply has a destination.
  • Publish one SPF record covering every sender you use.
  • Publish DKIM for each signer, including the cold-email tool, and confirm the selector with dig.
  • Publish DMARC at p=none with an rua mailbox you will open.
  • Mail yourself and confirm SPF, DKIM, and a d= that matches From.
  • Then raise volume. Move toward p=quarantine only after legitimate mail looks clean in the reports.

Google's sender guidelines require SPF or DKIM for mail to personal Gmail accounts, and both plus DMARC once you send about 5,000 messages a day to those accounts. p=none meets the requirement to publish a record. The From domain still has to align.

What the checker will not tell you

The SPF, DKIM, and DMARC checker explains the four records. The same lookup runs inside a full check. Either one counts toward 3 free checks a day. Pro is $19 a month and can recheck one saved domain daily when cron is set. It will not count SPF lookups, test an unlisted DKIM selector, notice a second DMARC record, or predict the inbox. Copy, warmup, and volume are on the pre-send checklist. DNS cannot answer them.

Questions

What if dig shows two answers that start with v=spf1?

You have two SPF records. That is a permanent error, and receivers do not choose one of them. Put every include and ip4 on a single TXT record and delete the other.

Does p=none stop someone from spoofing the domain?

No. p=none asks for reports. It does not ask receivers to quarantine or reject failures. Use it first so you can see who sends as you.

The checker says DKIM was not found, but I added a key. Why?

ColdLint only probes common selectors, including google, selector1, selector2, s1, and k1, and it follows CNAMEs. A selector your tool invented will not appear. Query that name with dig.

Does a passing DNS check mean the email will reach the inbox?

No. The records show that a receiver can authenticate the mail and that replies have a destination. They say nothing about the copy or complaints. ColdLint does not predict inbox placement.

Run it on ColdLint

Enter the domain that will send the campaign. The checker reads live DNS and names what it could not verify. It counts toward the free daily limit.

Related guide: Cold email pre-send checklist.