Verify and maintain email deliverability infrastructure: check SPF, DKIM, and DMARC DNS records with dig, ramp new domains through a warm-up cadence, enforce hard-bounce and complaint suppression hygiene, and screen subjects and content against spam-filter triggers. Use before any outbound email at volume, when a domain or inbox is new, or when mail starts landing in spam. Does not write email copy — pair with cold-email-drafting for subject lines, body copy, and personalization.
./install.sh email-deliverabilityname: "email_deliverability" description: "Verify and maintain email deliverability infrastructure: check SPF, DKIM, and DMARC DNS records with dig, ramp new domains through a warm-up cadence, enforce hard-bounce and complaint suppression hygiene, and screen subjects and content against spam-filter triggers. Use before any outbound email at volume, when a domain or inbox is new, or when mail starts landing in spam. Does not write email copy — pair with cold-email-drafting for subject lines, body copy, and personalization." metadata: includeInPrompt: true
Technical deliverability for agent-run email: authenticate the sending domain, warm up its reputation before real volume, and keep lists clean of bounces and complaints — so outbound mail reaches the inbox instead of spam or the reject pile. This skill is the plumbing, not the prose: it never drafts copy, picks subject lines, or personalizes. Run it before first send from any domain, and re-run it whenever placement drops.
dig +short TXT <domain> → look for a TXT record starting v=spf1. Multiple SPF records = broken (receivers treat it as a permerror).dig +short TXT <selector>._domainkey.<domain> → must return a key record (starts v=DKIM1;). DKIM signs every message; without it, forwarding breaks SPF and you lose authentication.dig +short TXT _dmarc.<domain> → must start v=DMARC1; and carry a p= policy (none = monitor, quarantine = spam-folder, reject = block). DMARC passes only when SPF or DKIM passes and aligns with the visible From domain — relaxed alignment (default) allows subdomains; strict requires an exact match.check_deliverability.py and run python3 check_deliverability.py <domain> [dkim-selector]. It parses the dig output and flags: missing records, duplicate SPF records, more than 10 DNS-lookup mechanisms in SPF (the RFC 7208 limit — every include, a, mx, ptr, exists, and redirect counts), missing DMARC policy, and missing rua= reporting address.python
#!/usr/bin/env python3
"""check_deliverability.py - SPF/DKIM/DMARC sanity check.
Save this file, then run: python3 check_deliverability.py example.com [dkim-selector]
Needs dig, or falls back to DNS-over-HTTPS via curl's path. stdlib only."""
import json, re, shutil, subprocess, sys, urllib.request
LOOKUP_MECHS = re.compile(r'\b(include|a|mx|ptr|exists)\b|redirect=')
def dig_txt(name):
recs = []
if shutil.which('dig'):
try:
out = subprocess.run(['dig', '+short', 'TXT', name],
capture_output=True, text=True, timeout=10).stdout
except Exception:
out = ''
for line in out.splitlines():
line = line.strip()
if line.startswith('"') and line.endswith('"'):
recs.append(line[1:-1])
if not recs: # DNS-over-HTTPS fallback for sandboxes where dig is blocked
try:
req = urllib.request.Request(
f'https://cloudflare-dns.com/dns-query?name={name}&type=TXT',
headers={'accept': 'application/dns-json'})
data = json.load(urllib.request.urlopen(req, timeout=10))
recs = [a['data'].strip('"') for a in data.get('Answer', [])]
except Exception:
pass
return recs
def check_spf(recs):
spf = [r for r in recs if re.match(r'^\s*v=spf1\b', r)]
if not spf:
return ['FAIL: no SPF record (TXT starting with v=spf1)']
issues, rec = [], spf[0]
if len(spf) > 1:
issues.append(f'FAIL: {len(spf)} SPF records published (multiple = permerror)')
n = len(LOOKUP_MECHS.findall(rec))
if n > 10:
issues.append(f'FAIL: {n} DNS-lookup mechanisms (RFC 7208 limit is 10)')
if 'redirect=' not in rec and not re.search(r'[~\-?]all\b', rec):
issues.append('WARN: no ending all mechanism (default to ~all, use -all only when sure)')
return issues or ['PASS: ' + rec[:90]]
def check_dmarc(recs):
dm = [r for r in recs if re.match(r'^\s*v=DMARC1\b', r)]
if not dm:
return ['FAIL: no DMARC record at _dmarc.<domain>']
issues, rec = [], dm[0]
m = re.search(r'\bp=(none|quarantine|reject)\b', rec)
if not m:
issues.append('FAIL: DMARC record has no p= policy')
elif m.group(1) == 'none':
issues.append('INFO: p=none (monitor only; move to quarantine/reject once reports are clean)')
if 'rua=' not in rec:
issues.append('WARN: no rua= address — you will not receive aggregate reports')
return issues or ['PASS: ' + rec[:90]]
def main():
if len(sys.argv) < 2:
print('usage: python3 check_deliverability.py <domain> [dkim-selector]')
sys.exit(2)
domain = sys.argv[1]
print(f'--- SPF: {domain} ---')
print('\n'.join(check_spf(dig_txt(domain))) or 'no TXT records returned')
print(f'--- DMARC: _dmarc.{domain} ---')
print('\n'.join(check_dmarc(dig_txt(f'_dmarc.{domain}'))) or 'no TXT records returned')
if len(sys.argv) > 2:
sel = sys.argv[2]
print(f'--- DKIM: {sel}._domainkey.{domain} ---')
recs = dig_txt(f'{sel}._domainkey.{domain}')
print(recs[0][:120] if recs else 'FAIL: no DKIM key record — confirm the selector with your provider')
else:
print('--- DKIM: skipped (pass your provider\'s selector as 2nd arg) ---')
main()
p=none minimum, From-domain alignment with SPF or DKIM, valid forward/reverse DNS and TLS, and one-click unsubscribe (RFC 8058 List-Unsubscribe header) on marketing mail, honored within ~2 days. All senders at any volume need SPF or DKIM. Keep user spam complaints under 0.3% per day in Google Postmaster Tools, targeting under 0.1%. These thresholds were current as of a 2026-09-21 check — verify before quoting them to anyone, and note Gmail now rejects non-compliant bulk mail outright (no more quiet spam-foldering).Re:/Fwd: prefixes. This blocks patterns; it doesn't write the copy.Return a Deliverability Report containing:
_dmarc with v=DMARC1; p=none; rua=mailto:<addr>").p=none on first DMARC publish (monitor via rua= reports); move to quarantine/reject only after reports show clean alignment — unless it's a high-spoof-risk brand, in which case start stricter.dig is blocked in your environment, verify records via the script's DNS-over-HTTPS fallback rather than declaring checks un-runnable.publisher key 2e43c913833850e6d80f939677e3de76e806d47785c0f1caaca90f9d18c98e1e
No ratings yet — be the first to rate it.