Guide
SPF, DKIM, DMARC: Debug Email Deliverability
According to Validity's 2024 Email Deliverability Benchmark, 15.8% of legitimate email never reaches the inbox. The three authentication protocols (SPF, DKIM, DMARC) are the first line of defense, but sender reputation, bounce rates, and content signals matter too. This guide shows how to inspect and debug every layer from the command line using Nylas CLI, dig, and openssl. Works across all major email providers.
Written by Prem Keshari Senior SRE
Reviewed by Caleb Geene
SPF: who is allowed to send from your domain
Sender Policy Framework (SPF, defined in RFC 7208) is a DNS record that tells receiving mail servers which IP addresses are authorized to send email on behalf of your domain. When a server receives an email from your domain, it checks the SPF record to see if the sending server's IP is on the list. According to Valimail's 2024 Email Authentication Report, SPF adoption has reached 93% among the top 1 million domains, but misconfigured records still cause 15-20% of legitimate emails to fail authentication.
The dig command queries a domain's TXT records and filters for the SPF entry. The output shows which services (e.g., Google Workspace, Mailchimp) are authorized and what policy applies to unauthorized senders.
# Check SPF record for any domain
dig +short TXT example.com | grep spf
# Example output:
# "v=spf1 include:_spf.google.com include:servers.mcsv.net ~all"
# Break down what this means:
# v=spf1 → SPF version 1
# include:_spf.google.com → Google Workspace can send for this domain
# include:servers.mcsv.net → Mailchimp can send for this domain
# ~all → Soft-fail everything else (mark as suspicious but deliver)The policy at the end matters:
-all— hard fail. Reject emails from unauthorized servers. Strongest protection.~all— soft fail. Accept but mark as suspicious. Most common during setup.?all— neutral. No policy. Effectively useless.+all— allow all. Dangerous — anyone can spoof your domain.
Inspect SPF includes recursively
SPF records chain through include: directives that reference other domains, and each directive triggers a separate DNS lookup. A single domain using Google Workspace, Mailchimp, and Salesforce can consume 7 of the 10 allowed lookups before adding any custom entries. Following the full include chain reveals every authorized IP range and shows how close you are to the lookup ceiling.
Google Workspace alone uses 3 nested includes (_spf.google.com pointing to 3 netblock records), each resolving to a different set of IP ranges. The following commands trace that chain and count your total lookups.
# Follow the SPF include chain for Google Workspace
dig +short TXT _spf.google.com
# "v=spf1 include:_netblocks.google.com include:_netblocks2.google.com include:_netblocks3.google.com ~all"
# See the actual IP ranges
dig +short TXT _netblocks.google.com
# "v=spf1 ip4:35.190.247.0/24 ip4:64.233.160.0/19 ip4:66.102.0.0/20 ..."
# Check if you are close to the DNS lookup limit (max 10 lookups)
# Count the number of include/redirect/a/mx mechanisms
dig +short TXT yourdomain.com | grep -oE 'include:|redirect=|a:|mx:' | wc -lSPF has a hard ceiling of 10 DNS lookups per evaluation, as specified in RFC 7208, Section 4.6.4. Exceeding it causes a permanent error (permerror), which means SPF effectively fails for every email. If you are using multiple email services (Google Workspace + Mailchimp + Salesforce + a transactional sender), you can hit this limit quickly. DNS changes for SPF records typically propagate within 5-60 minutes, depending on TTL values.
DKIM: cryptographic email authentication
DomainKeys Identified Mail (DKIM, defined in RFC 6376) adds a cryptographic signature to outgoing emails. The sending server signs the message with a private key, and the receiving server verifies the signature using a public key published in DNS. This proves the email was actually sent by an authorized server and was not modified in transit. DKIM signatures use RSA keys of at least 1,024 bits; Google recommends 2,048-bit keys for stronger protection.
Each email provider uses a different DKIM selector name. Google Workspace uses google, Microsoft 365 uses selector1 and selector2, Mailchimp uses k1. The following commands check DNS for the DKIM public key using known selectors, or loop through all common selectors to find which one is active.
# Check DKIM record for a domain
# You need to know the selector — common ones:
# google / default → Google Workspace
# s1 / s2 → Microsoft 365
# k1 → Mailchimp
# sm1 / sm2 → Salesforce
# Google Workspace DKIM
dig +short TXT google._domainkey.example.com
# Microsoft 365 DKIM
dig +short TXT selector1._domainkey.example.com
# Generic check (try common selectors)
for selector in google default s1 s2 selector1 selector2 k1 sm1; do
RESULT=$(dig +short TXT "$selector._domainkey.example.com" 2>/dev/null)
if [ -n "$RESULT" ]; then
echo "Found DKIM for selector: $selector"
echo "$RESULT"
echo "---"
fi
doneA valid DKIM record contains a public key (the p= field). If the record is empty or missing, DKIM signing is not configured for that selector.
DMARC: the policy that ties SPF and DKIM together
Domain-based Message Authentication, Reporting, and Conformance (DMARC, defined in RFC 7489) tells receiving servers what to do when an email fails SPF and DKIM checks. It also specifies where to send aggregate and forensic reports about authentication failures. DMARC adoption among Fortune 500 companies reached 91% in 2024 (Agari/Fortra report).
As of February 2024, Google requires SPF, DKIM, and DMARC for bulk senders (5,000+ messages/day to Gmail). Yahoo implemented similar requirements the same month. If you send any meaningful volume of email, all three protocols are mandatory.
# Check DMARC record
dig +short TXT _dmarc.example.com
# Example output:
# "v=DMARC1; p=reject; rua=mailto:dmarc-reports@example.com; ruf=mailto:forensics@example.com; pct=100"
# Break down what this means:
# v=DMARC1 → DMARC version 1
# p=reject → Reject emails that fail both SPF and DKIM
# rua=mailto:dmarc-reports@example.com → Send aggregate reports here
# ruf=mailto:forensics@example.com → Send forensic (failure) reports here
# pct=100 → Apply to 100% of emailsDMARC policies in order of strictness:
p=none— monitor only. Receive reports but take no action. Start here.p=quarantine— send failing emails to spam. Intermediate step.p=reject— reject failing emails entirely. Full protection against spoofing.
Inspect authentication headers on received emails
Inspecting authentication headers on received emails confirms whether SPF, DKIM, and DMARC passed or failed for a specific message. Every email that reaches an inbox carries an Authentication-Results header added by the receiving mail server. This single header contains the pass/fail verdict for all three protocols, making it the fastest way to diagnose deliverability issues without touching DNS tools.
The Nylas CLI outputs message headers as structured JSON, which means you can pipe the result through jq to extract only authentication-related fields. The following command reads a message and filters for the 4 headers that matter: Authentication-Results, Received-SPF, DKIM-Signature, and ARC.
# Read a message with full headers
nylas email read msg_abc123 --json \
| jq '.headers'
# Look for specific authentication headers
nylas email read msg_abc123 --json \
| jq '.headers | to_entries[] | select(.key | test("Authentication-Results|Received-SPF|DKIM-Signature|ARC"; "i"))'
# Example Authentication-Results header:
# Authentication-Results: mx.google.com;
# dkim=pass header.i=@example.com header.s=google;
# spf=pass (google.com: domain of sender@example.com designates 198.51.100.27 as permitted sender);
# dmarc=pass (p=REJECT) header.from=example.comHere is what the full JSON output looks like when you inspect authentication headers with Nylas CLI:
{
"id": "a1b2c3d4e5f6g7h8",
"subject": "Your invoice #1042",
"from": [{"name": "Jane Cooper", "email": "jane@example.com"}],
"to": [{"name": "Alex Morgan", "email": "alex@example.com"}],
"headers": {
"authentication-results": "mx.google.com; dkim=pass header.d=example.com header.s=google; spf=pass (google.com: domain of jane@example.com designates 192.0.2.41 as permitted sender); dmarc=pass (p=REJECT sp=REJECT) header.from=example.com",
"received-spf": "pass (google.com: domain of jane@example.com designates 192.0.2.41 as permitted sender)",
"dkim-signature": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com; s=google; h=from:to:subject:date; bh=abc123def456...; b=xyz789ghi012..."
}
}The Authentication-Results header is the single most useful header for debugging deliverability. It shows the pass/fail status of SPF, DKIM, and DMARC in one place.
Send test emails and verify authentication
Sending a test email and inspecting its headers on the receiving end is the most reliable way to verify that SPF, DKIM, and DMARC are all passing in production. DNS records alone confirm that authentication is published, but only a live send proves the entire chain works end to end -- from the sending server signing the message to the receiving server validating it. Google's Postmaster Tools data shows that domains with all three protocols passing see inbox placement rates above 95%.
The Nylas CLI sends email through the authenticated provider (Gmail, Outlook, or any connected account), so the message carries real SPF and DKIM signatures. After sending, check the authentication headers on the receiving account to confirm all three protocols passed.
# Send a test email
nylas email send \
--to "test@gmail.com" \
--subject "Deliverability test" \
--body "Testing SPF, DKIM, and DMARC authentication."
# Then on the receiving end, check the authentication headers
# In Gmail: open the email → three dots → "Show original"
# Or if you have CLI access to the receiving account:
nylas email search "deliverability test" --limit 1 --json \
| jq '.[0].headers | to_entries[] | select(.key | test("Authentication|SPF|DKIM|DMARC"; "i"))'Diagnose common delivery failures
Most email delivery failures trace back to one of three authentication misconfigurations: an SPF record that does not include the sending server's IP, a DKIM public key that is missing or does not match the signing selector, or a DMARC alignment mismatch between the header From domain and the authenticated domain. According to DMARC.org's 2024 adoption report, 38% of domains with a published DMARC record still use p=none, meaning authentication failures go unreported and unblocked.
SPF failure: sending from an unauthorized server
An SPF failure means the IP address of the sending server is not listed in the domain's SPF record. This is the most common cause of emails landing in spam when using a new email service or transactional sender. Extract the sending server's IP from the email headers and compare it against the SPF include chain.
# Symptom: emails going to spam or being rejected
# Check: is the sending server in the SPF record?
# Find your sending server's IP (from the email headers)
nylas email read msg_id --json \
| jq -r '.headers["Received"]' \
| grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+'
# Check if that IP is authorized by SPF
dig +short TXT yourdomain.com | grep spf
# If the sending IP is not in the SPF include chain, add itDKIM failure: signature mismatch or missing key
A DKIM failure occurs when the receiving server cannot verify the cryptographic signature, either because the public key is missing from DNS or because the message was modified in transit. The DKIM-Signature header in each email identifies which selector was used for signing. Verify that the corresponding public key record exists in DNS.
# Check if the DKIM key exists for the selector in the email
# Find the selector from the DKIM-Signature header:
nylas email read msg_id --json \
| jq -r '.headers["DKIM-Signature"]'
# Look for s=selector; in the output
# Then verify the public key exists in DNS
dig +short TXT selector._domainkey.yourdomain.com
# If empty: DKIM key not published. Add it to your DNS.DMARC failure: alignment issues
DMARC alignment requires that the domain in the From header matches the domain that passed SPF or DKIM. Strict alignment (aspf=s or adkim=s) demands an exact domain match, while relaxed alignment allows subdomains. Organizations sending from marketing.example.com with SPF/DKIM configured only on example.com will fail strict alignment checks.
# DMARC requires alignment: the domain in From must match
# the domain that passed SPF or DKIM
# Check DMARC alignment mode
dig +short TXT _dmarc.yourdomain.com
# aspf=r → relaxed SPF alignment (subdomains OK)
# aspf=s → strict SPF alignment (exact match only)
# adkim=r → relaxed DKIM alignment
# adkim=s → strict DKIM alignment
# If you send from marketing.example.com but SPF/DKIM
# is set up for example.com, strict alignment will failCheck MX records and mail routing
MX (Mail Exchanger) records tell the internet where to deliver incoming email for a domain. Each MX record has a priority value -- lower numbers mean higher priority -- and receiving servers try them in order. If MX records are missing or point to unreachable servers, inbound email fails silently: senders get no bounce, and recipients never know a message was attempted. Google Workspace typically publishes 5 MX records with priorities ranging from 1 to 30 for redundancy.
The dig command returns all MX records along with their priority values. Verifying that each MX hostname resolves to a valid IP address confirms the mail routing path is functional.
# Check MX records
dig +short MX example.com
# 10 alt1.aspmx.l.google.com.
# 5 aspmx.l.google.com.
# 20 alt2.aspmx.l.google.com.
# The number is the priority (lower = higher priority)
# Verify the MX servers are reachable
for mx in $(dig +short MX example.com | awk '{print $2}'); do
echo -n "$mx: "
dig +short A "$mx"
doneVerify TLS encryption
Email sent without TLS encryption travels in plaintext across every network hop between sender and receiver. Google's Transparency Report shows that 92% of inbound Gmail traffic uses TLS as of 2024, up from 33% in 2013. Verifying that a mail server supports STARTTLS confirms that the connection between your sending server and the recipient's MX server is encrypted in transit.
The openssl s_client command initiates a STARTTLS handshake on port 25 and displays the server's TLS certificate, including its subject and expiration dates. An expired or self-signed certificate can cause receiving servers to reject the connection.
# Check if a mail server supports STARTTLS
# Connect to the MX server on port 25 and check for STARTTLS
echo "EHLO test.example.com" | ncat -w 5 aspmx.l.google.com 25
# Check the TLS certificate
openssl s_client -connect aspmx.l.google.com:25 -starttls smtp < /dev/null 2>/dev/null \
| openssl x509 -noout -subject -datesDeliverability checklist
A complete deliverability audit covers 7 checks: SPF record existence, SPF lookup count, DKIM key publication, DMARC policy, MX resolution, authentication header verification, and TLS support. Running all 7 takes under 2 minutes from the command line. Any single failure can cause emails to land in spam or be rejected outright, so treat this as a pass/fail gate before launching a new sending domain or email service.
| Check | Command | Expected result |
|---|---|---|
| SPF record exists | dig +short TXT example.com | Contains v=spf1 |
| SPF not too many lookups | Count includes | 10 or fewer |
| DKIM key published | dig +short TXT selector._domainkey.example.com | Contains p= public key |
| DMARC policy set | dig +short TXT _dmarc.example.com | Contains v=DMARC1 |
| MX records resolve | dig +short MX example.com | Returns mail servers |
| Auth headers pass | nylas email read --json | SPF=pass, DKIM=pass, DMARC=pass |
| TLS supported | openssl s_client -starttls smtp | Valid certificate |
Automate deliverability monitoring
Automated monitoring catches DNS record changes, accidental deletions, and TTL expirations before they affect deliverability. SPF, DKIM, and DMARC records are just DNS TXT entries -- any infrastructure change, domain transfer, or DNS provider migration can silently remove them. A cron job running every 6 hours that checks all 4 record types (SPF, DKIM, DMARC, MX) and exits with a non-zero status on failure integrates directly with alerting systems like PagerDuty, Datadog, or a simple email notification.
The following script checks SPF, DMARC, DKIM (trying 4 common selectors: google, default, s1, selector1), and MX records. It prints a pass/fail summary and exits with code 1 if any check fails, making it suitable for CI pipelines or cron-based alerting.
#!/bin/bash
# deliverability-check.sh — run with cron or in CI
DOMAIN="yourdomain.com"
ERRORS=0
# Check SPF
SPF=$(dig +short TXT "$DOMAIN" | grep "v=spf1")
if [ -z "$SPF" ]; then
echo "FAIL: No SPF record found for $DOMAIN"
ERRORS=$((ERRORS + 1))
else
echo "PASS: SPF record found"
fi
# Check DMARC
DMARC=$(dig +short TXT "_dmarc.$DOMAIN")
if [ -z "$DMARC" ]; then
echo "FAIL: No DMARC record found for $DOMAIN"
ERRORS=$((ERRORS + 1))
else
echo "PASS: DMARC record found"
# Warn if policy is none
if echo "$DMARC" | grep -q "p=none"; then
echo "WARN: DMARC policy is 'none' — no enforcement"
fi
fi
# Check DKIM (try common selectors)
DKIM_FOUND=false
for selector in google default s1 selector1; do
DKIM=$(dig +short TXT "$selector._domainkey.$DOMAIN" 2>/dev/null)
if [ -n "$DKIM" ]; then
echo "PASS: DKIM record found for selector '$selector'"
DKIM_FOUND=true
break
fi
done
if [ "$DKIM_FOUND" = false ]; then
echo "WARN: No DKIM record found for common selectors"
fi
# Check MX
MX=$(dig +short MX "$DOMAIN")
if [ -z "$MX" ]; then
echo "FAIL: No MX records found for $DOMAIN"
ERRORS=$((ERRORS + 1))
else
echo "PASS: MX records found ($(echo "$MX" | wc -l | tr -d ' ') servers)"
fi
# Summary
echo "---"
if [ $ERRORS -gt 0 ]; then
echo "RESULT: $ERRORS failures found. Fix before sending email."
exit 1
else
echo "RESULT: All checks passed."
fiMonitor and handle bounces
Bounce monitoring protects sender reputation by identifying invalid addresses before they accumulate. According to Validity's 2024 Email Deliverability Benchmark, maintaining a bounce rate below 2% is required to preserve sender reputation with major providers. Hard bounces (invalid addresses) damage reputation faster than soft bounces (full mailbox, temporary failures). Gmail and Outlook start throttling senders whose hard bounce rate exceeds 5% over a 7-day window.
The Nylas CLI can search for delivery failure notifications and extract bounced addresses into a suppression list. The following commands search for common bounce notification subjects, extract the bounced email addresses, and calculate the bounce rate for a given send volume.
# Search for bounce notifications
nylas email search "delivery failure undeliverable returned mail" --json | jq '
[.[] | {
subject: .subject,
from: .from[0].email,
date: .date,
snippet: .snippet
}]'
# Extract bounced addresses from delivery failure emails
nylas email search "delivery status notification" --json | jq -r '
[.[] | .body] | .[]' | grep -oE '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}' | sort -u
# Count bounce rate for your last campaign
TOTAL_SENT=50
BOUNCES=$(nylas email search "delivery failure" --json | jq 'length')
echo "Bounce rate: $(echo "scale=1; $BOUNCES * 100 / $TOTAL_SENT" | bc)%"Build a suppression list from bounced addresses. Never send to an address that hard-bounced. Continuing to send to invalid addresses signals to mail servers that you don’t maintain your list, which degrades sender reputation for all your email.
Sending best practices from the CLI
When sending email through the Nylas CLI, SPF and DKIM authentication is handled by the connected provider (Gmail, Outlook, or any other supported account). Content and sending patterns are where most deliverability problems originate after authentication is correctly configured. Litmus's 2024 State of Email report found that 45% of emails flagged as spam passed all three authentication checks but were filtered based on content signals, sender reputation, or engagement patterns.
- Use a real reply-to address — do not send from no-reply addresses. Receiving servers score emails higher when replies are possible.
- Keep subject lines honest — avoid all-caps, excessive punctuation, and spam trigger words in the subject.
- Include plain text — when sending HTML email, always include a plain text alternative. Nylas CLI sends plain text by default.
- Warm up new accounts — if using a new email account, start with low volume and increase gradually over 2-4 weeks.
- Monitor bounces — check for bounce notifications and remove invalid addresses promptly.
The Nylas CLI sends plain text by default, which avoids the HTML-only penalty that some spam filters apply. After sending, search for delivery failure notifications to catch bounces early.
# Send a well-formed email through authenticated channels
nylas email send \
--to "recipient@company.com" \
--subject "Q2 report summary" \
--body "Hi Team, please find the Q2 summary below. Let me know if you have questions."
# Check for bounce notifications
nylas email search "delivery failure undeliverable returned mail" --jsonFrequently asked questions
These are the most common questions developers ask when debugging email deliverability from the command line. Each answer references specific DNS records, tools, and thresholds from the authentication protocols (SPF, DKIM, DMARC). According to Google's Postmaster Tools documentation, over 80% of deliverability issues reported by senders trace back to one of these authentication or configuration problems.
My SPF record is correct but emails still go to spam. Why?
SPF is necessary but not sufficient. Check DKIM and DMARC as well. Also check sender reputation — new domains or domains with a history of spam need time to build reputation. Content analysis (spam-like language, excessive links) can also trigger filters regardless of authentication.
How long does it take for DNS changes to propagate?
DNS changes for SPF, DKIM, and DMARC records typically propagate within 5-60 minutes, depending on TTL values. Records with a 300-second TTL (common default) update within 5 minutes; records with a 3,600-second TTL can take up to an hour. Use dig +trace TXT _dmarc.example.com to check propagation status from authoritative nameservers.
Do I need all three (SPF, DKIM, DMARC)?
Yes. As of February 2024, Google's Email Sender Guidelines require all three for bulk senders (5,000+ messages/day). Yahoo Sender Best Practices impose similar requirements. Microsoft's Basic Auth deprecation further tightened authentication enforcement across Outlook and Exchange Online. Missing any one of them significantly increases the chance of your emails being filtered or rejected.
How do I check deliverability for emails sent by AI agents?
The same way — check authentication headers on the receiving end. If your agent sends email through nylas email send, the email goes through the authenticated provider (Gmail, Outlook, etc.) with proper SPF, DKIM, and DMARC. The provider handles the authentication; you handle the content quality.
Next steps
- Send email from the terminal — complete guide to CLI email
- Why Gmail API breaks AI agents — authentication complexity compared
- GPG encrypted email from the CLI — add end-to-end encryption
- Full command reference — every flag and option