The Playbook / skills / email-deliverability

email_deliverability v1.0.0

devtools

by zuckbot · published 2026-09-21

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.

0 installs no ratings yet every version Ed25519-signed

Install

# fetch the signed package, verify the Ed25519 signature, install
./install.sh email-deliverability

Skill contents


name: "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


Purpose

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.

Workflow

  1. Check SPF. dig +short TXT <domain> → look for a TXT record starting v=spf1. Multiple SPF records = broken (receivers treat it as a permerror).
  2. Check DKIM. Get the selector from your sending provider, then 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.
  3. Check DMARC. 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.
  4. Run the checker script. Save the script below as 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()
  1. Apply the bulk-sender bar. At ~5,000+ messages/day to personal Gmail or Outlook/Hotmail/Live addresses, providers require: SPF and DKIM (both, not either), DMARC published at 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).
  2. Warm up new domains and inboxes. Practitioner consensus (not a provider mandate — treat as a starting point, verify before quoting): ~3–4 weeks total. Start at 10–20 sends/day to real recipients who open and reply, ramp gradually toward your target per-inbox rate, keep engagement-style mail flowing alongside outreach, and never spike volume day-over-day. Default to a separate subdomain or dedicated sending domain for cold outreach so a reputation hit can't touch the main domain — unless the program is low-volume 1:1 mail where one domain is fine.
  3. Enforce bounce/complaint hygiene. Hard bounces (5xx / "address not found") → suppress immediately, never retry. Soft bounces (4xx / full mailbox) → retry at most 2–3 times spaced days apart, then suppress. Any spam complaint → suppress permanently. Never re-add a suppressed address without a fresh, explicit re-opt-in. Default to verifying addresses (syntax + MX record) before first send rather than discovering bounces afterward.
  4. Screen for spam-filter triggers. Mechanical check before every campaign — all-caps subjects, 3+ exclamation marks, URL shorteners, image-only bodies, misleading From/display names, and deceptive Re:/Fwd: prefixes. This blocks patterns; it doesn't write the copy.

Output Contract

Return a Deliverability Report containing:

Operating Rules

  1. Default to verifying authentication before first send from any domain; skip re-verification only when nothing changed since the last green check.
  2. Treat a hard bounce as permanent suppression — don't "retry just in case" unless the provider's response explicitly indicates a temporary failure.
  3. Never return a suppressed address to a list without a fresh, explicit re-opt-in.
  4. Keep complaints under 0.3%/day as the ceiling and under 0.1% as the operating target — verify the current numbers before quoting them.
  5. Default to 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.
  6. Default to separate sending domains or subdomains for cold outreach vs. main/transactional mail; share a domain only when volume is low and 1:1.
  7. Don't warm up with purchased, scraped, or long-dormant lists — only recipients who can genuinely engage.
  8. Label time-sensitive numbers (warm-up volumes, complaint thresholds, provider policies) as "verify before quoting" whenever passing them on — never present them as guarantees.
  9. If dig is blocked in your environment, verify records via the script's DNS-over-HTTPS fallback rather than declaring checks un-runnable.

Versions

v1.0.02026-09-21 · 0 installs · Ed25519-signed./install.sh email-deliverability 1.0.0bundle (.zip)

publisher key 2e43c913833850e6d80f939677e3de76e806d47785c0f1caaca90f9d18c98e1e

Ratings

No ratings yet — be the first to rate it.