SPF, DMARC, and DKIM Setup Guide: Verify and Fix Mail Auth
Stop Discovering Broken Mail Auth Through Client Complaints
SPF and DMARC feel like a one-time setup, but DNS changes break them silently and repeatedly. Agencies managing multiple client domains are especially exposed: a new email SaaS added to an SPF record, a nameserver migration that drops authentication records, or a DKIM key rotation that misses the publish step. The first sign of failure is almost always a client saying "emails aren't arriving."
This guide covers the correct configuration and verification procedure for SPF, DMARC, DKIM, and MX records — with commands you can run immediately during initial setup, domain handoffs, or routine audits.
Understanding the Four Authentication Types
| Type | Purpose | Where it lives |
|---|---|---|
| MX | Declares the servers that accept inbound email | TXT record at the apex domain |
| SPF | Declares which servers are authorized to send | TXT record at the apex domain |
| DMARC | Specifies what happens when SPF or DKIM fails | TXT record at _dmarc.<domain> |
| DKIM | Cryptographically signs outbound messages | TXT record at <selector>._domainkey.<domain> |
Each type is independent. A valid SPF record does not protect you if the DMARC policy is malformed — email may still land in spam.
SPF: Correct Configuration
Basic syntax
example.com. IN TXT "v=spf1 include:_spf.google.com ~all"
v=spf1— Version tag. Must appear first.include:— Authorizes an external set of sending servers.~all— Soft fail for unauthorized senders (recommended starting point).-all— Hard fail: reject unauthorized senders entirely (stricter enforcement).
Common SPF mistakes
| Mistake | Consequence |
|---|---|
Missing v=spf1 prefix |
Record is not recognized as SPF — effectively disabled |
Two v=spf1 records on the same domain |
RFC 7208 violation — receiving MTAs treat this as permanent failure |
| SPF lookup chain exceeds 10 DNS queries | RFC 7208 limit exceeded — evaluation fails permanently |
No all terminal |
Policy is incomplete — sender evaluation is undefined |
Verify SPF with dig
# Extract TXT records starting with v=spf1
dig +short TXT example.com | grep 'v=spf1'
# Check that exactly one SPF record is returned
# Two or more records require immediate remediation
If dig isn't available, nslookup works too (it can't filter, so scan the output for the right line).
nslookup -type=TXT example.com
DMARC: Correct Configuration
Basic syntax
_dmarc.example.com. IN TXT "v=DMARC1; p=quarantine; rua=mailto:[email protected]; pct=100"
v=DMARC1— Version tag.p=— Policy:none(monitor only) →quarantine(spam folder) →reject(block). Escalate gradually.rua=— Aggregate report destination as amailto:URI.pct=— Percentage of messages the policy applies to (0–100).
Common DMARC mistakes
| Mistake | Consequence |
|---|---|
Missing p= tag |
Required tag absent — the entire record is invalid |
pct=120 or other out-of-range value |
RFC 7489 violation — some parsers silently ignore the record |
rua=example.com without mailto: |
Malformed URI — reports never arrive |
| Record placed at wrong subdomain | Not queried by receiving MTAs |
Verify DMARC with dig
# Retrieve the DMARC record
dig +short TXT _dmarc.example.com
# Confirm p= tag is present
dig +short TXT _dmarc.example.com | grep 'p='
With nslookup:
nslookup -type=TXT _dmarc.example.com
If p=none, the policy is monitoring-only and no messages are filtered. Once your authentication is stable, step up to p=quarantine, then eventually to p=reject.
DKIM: Correct Configuration
Public key record syntax
selector1._domainkey.example.com. IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSq..."
v=DKIM1— Version tag.k=rsa— Key algorithm (can usually be omitted; defaults to RSA).p=— Base64-encoded public key. An emptyp=means the key has been revoked.
Finding your selector name
The selector name depends on which email sending service you use:
| Service | Common selector |
|---|---|
| Google Workspace | google |
| SendGrid | s1, s2 |
| Amazon SES | amazonses |
| Mailchimp | k1 |
# Verify the DKIM public key for selector "google"
dig +short TXT google._domainkey.example.com
# A valid response contains v=DKIM1 and a non-empty p= value
With nslookup:
nslookup -type=TXT google._domainkey.example.com
Common DKIM mistakes
| Mistake | Consequence |
|---|---|
| Publishing new selector key but forgetting DNS | All signatures fail for that selector |
| Not removing old selector after rotation completes | Stale records cause confusion; some parsers may match the wrong key |
p= value is empty |
Key is treated as revoked — all signature verifications fail |
Verifying MX Records
# Check MX records and priority values
dig +short MX example.com
# Example output:
# 10 aspmx.l.google.com.
# 20 alt1.aspmx.l.google.com.
With nslookup:
nslookup -type=MX example.com
If no MX record is returned, or the record still points to a temporary migration host, inbound email will fail. After any domain or DNS migration, confirm the MX records point to the intended mail servers.
Fixing Changes That Don't Seem to Take Effect (DNS Propagation and Caching)
Right after editing a DNS record, TTL (Time To Live) and resolver caching can delay when the new value becomes visible. If dig still shows the old record, work through these checks before assuming the configuration itself is wrong.
- Query a public resolver (Google Public DNS) -- this rules out a stale cache on your own network or ISP resolver.
dig +short TXT example.com @8.8.8.8
nslookup -type=TXT example.com 8.8.8.8
- Query the authoritative nameserver directly -- bypassing every cache shows you the true, current state of the record.
# Find the authoritative nameservers
dig +short NS example.com
# Query one of them directly for the TXT record
dig +short TXT example.com @ns1.example-dns.com
- Check the TTL -- a longer TTL means a longer wait before the change propagates everywhere. If you need the cutover to happen fast, lower the TTL (e.g. to 300 seconds) before making the actual change.
dig example.com TXT
# Look at the TTL value (in seconds) in the ANSWER SECTION
In most cases a record that "isn't showing up yet" simply hasn't propagated. Only suspect the configuration itself if the record still doesn't match after waiting out the TTL.
Combined Spot-Check Script
Use this during new client onboarding or as part of a periodic audit:
#!/bin/bash
# Usage: ./check-mail-auth.sh example.com [selector]
DOMAIN="${1:?Provide a domain name}"
SELECTOR="${2:-google}"
echo "=== MX ==="
dig +short MX "$DOMAIN"
echo "=== SPF ==="
dig +short TXT "$DOMAIN" | grep 'v=spf1'
echo "=== DMARC ==="
dig +short TXT "_dmarc.$DOMAIN"
echo "=== DKIM (selector: $SELECTOR) ==="
dig +short TXT "${SELECTOR}._domainkey.$DOMAIN"
Once you have confirmed the output looks correct, register each type in Miterl so future changes are caught automatically.
Move From Manual Checks to Continuous Monitoring
Even a correct setup breaks again at predictable moments:
- Adding a new email SaaS causes the SPF lookup count to exceed 10.
- A DNS zone migration drops authentication records that were not in the export.
- An automatic DKIM key rotation at the ESP does not update the published TXT record.
Manual verification catches the state at one point in time. Continuous monitoring catches changes the moment they happen.
Miterl monitors MX, SPF, DMARC, and DKIM records using the same API you use for HTTP and SSL monitors:
# Register a DMARC monitor (validates p= tag, pct range, and rua URI format)
curl -X POST https://miterl.com/api/v1/monitors \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "example.com DMARC",
"type": "dmarc",
"url": "example.com",
"interval_seconds": 300
}'
Syntax validation is built in. An SPF record that exceeds 10 lookups, a DMARC record missing p=, or a DKIM key with an empty p= field all trigger a Down alert — not just a missing record. For the full setup walkthrough, see "Monitor SPF, DMARC, DKIM, and MX Records Continuously."
Summary
- SPF: One
v=spf1TXT record at the apex domain, with a confirmedallterminal and a lookup chain under 10 hops. - DMARC: Required
p=tag at_dmarc.<domain>. Start withnone, escalate toquarantinethenrejectas confidence grows. - DKIM: Before rotating a selector, verify the new public key is live in DNS. Confirm the selector at the ESP matches what your DNS serves.
- MX: After any migration, verify MX records point to the intended mail servers — not a temporary hostname from the migration.
After initial setup, shift from manual checks to continuous monitoring so you catch silent breakage before clients do. For domain-level checks at launch, the "Pre-Launch Monitoring Checklist" covers the full set. To understand how mail authentication fits alongside HTTP, DNS, and SSL monitoring, see "Server Monitoring Basics: HTTP, Ping, DNS, and SSL." Beyond mail authentication, tracking your site's response time gives clients another concrete measure of maintenance quality — "Response Time Monitoring Guide" covers setup and thresholds. When a downtime incident does occur, having a notification template ready saves time: "Downtime Notification Email Templates for Agencies" provides copy for common outage scenarios. For the site itself, see "What Is Synthetic Monitoring? A Plain-Language Guide" for outside-in checks and "Heartbeat Monitoring: Detect Failed Cron and Batch Jobs" for cron and batch jobs. Feature documentation is at /en/docs, pricing questions are answered in the FAQ, and you can start testing from the free signup.