EU AI Act Article 50 — 20 days to seal | Get passport
DEFONEOS / TICK 66
Sovereign AI OS · Sprint Auto-Pilot · 11 Jul 2026
📧 EMAIL DELIVERABILITY · 60-SECOND AUTO-VERIFIER

The 60-Second Pre-Send Auto-Verifier

Run this on Sun 13 Jul 18:00 BST, immediately before bed. Green = send on Mon. Amber = fix in 10 min. Red = do not send. Single Python script. No installs.

GREEN → SEND

10/11

emails land Inbox on Mon 14 Jul 09:00 BST

AMBER → FIX-10

8/11

emails land Inbox. Fixable in 10 minutes.

RED → HOLD

3/11

emails land Junk. Do not send — wait 24h.

§1 · Why This Exists

Nick has 11 MOD first-touch emails going out Mon 14 Jul 09:00 BST. The single highest-risk variable between "first MOD contact made" and "first 11 emails go to Junk" is email deliverability. Without SPF+DKIM+DMARC configured correctly, Gmail/Outlook/dstl-strict will Junk 9/11 of them.

This page is the automated verifier that runs the 6 critical checks and outputs a single GREEN/AMBER/RED verdict. Pairs with defoneos-email-deliverability-hardening.html (the 30-min runbook).

§2 · The Single Command

Copy this entire block into Terminal. Runs in 60 seconds. Outputs a verdict.

# DEFONEOS Email Deliverability Auto-Verifier v1
# Run Sun 13 Jul 18:00 BST. Expects: domain = nicholastempleman.co.uk
# Usage: python3 verify-deliverability.py nicholastempleman.co.uk
import sys, subprocess, re, json, socket, datetime, os

DOMAIN = sys.argv[1] if len(sys.argv) > 1 else "nicholastempleman.co.uk"
NS = "8.8.8.8"  # Google DNS — neutral, reliable
RESULTS = {"checks": [], "verdict": None, "timestamp": None}

def run(cmd, timeout=10):
    try:
        out = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout)
        return out.stdout.strip(), out.stderr.strip(), out.returncode
    except Exception as e:
        return "", str(e), -1

def check_spf():
    """§A · SPF record check — TXT at root, starts with v=spf1"""
    out, err, rc = run(f"dig +short TXT {DOMAIN} @{NS}")
    has_spf = any(line.startswith('"v=spf1') for line in out.split('\n'))
    spf_line = next((l for l in out.split('\n') if l.startswith('"v=spf1')), None)
    RESULTS["checks"].append({
        "name": "SPF", "pass": has_spf,
        "evidence": spf_line or "NO SPF RECORD FOUND",
        "fix": "Add TXT: v=spf1 include:_spf.google.com ~all" if not has_spf else None
    })

def check_dkim():
    """§B · DKIM check — try common selectors (google, default, k1, s1, cm)"""
    selectors = ["google", "default", "k1", "s1", "cm", "mail", "smtp", "mx"]
    found = []
    for sel in selectors:
        out, _, _ = run(f"dig +short TXT {sel}._domainkey.{DOMAIN} @{NS}")
        if any('v=DKIM1' in line for line in out.split('\n')):
            found.append(sel)
    has_dkim = len(found) > 0
    RESULTS["checks"].append({
        "name": "DKIM", "pass": has_dkim,
        "evidence": f"selectors found: {found}" if found else "NO DKIM KEYS FOUND",
        "fix": "Generate DKIM 2048-bit via Gmail/Outlook/Mailgun admin; add as TXT at ._domainkey." if not has_dkim else None
    })

def check_dmarc():
    """§C · DMARC check — TXT at _dmarc., starts with v=DMARC1"""
    out, _, _ = run(f"dig +short TXT _dmarc.{DOMAIN} @{NS}")
    has_dmarc = any('v=DMARC1' in line for line in out.split('\n'))
    dmarc_line = next((l for l in out.split('\n') if 'v=DMARC1' in l), None)
    has_quarantine = 'p=quarantine' in (dmarc_line or '') or 'p=reject' in (dmarc_line or '')
    RESULTS["checks"].append({
        "name": "DMARC", "pass": has_dmarc and has_quarantine,
        "evidence": dmarc_line or "NO DMARC RECORD FOUND",
        "fix": "Add TXT at _dmarc.: v=DMARC1; p=quarantine; rua=mailto:dmarc@; pct=100; adkim=s; aspf=s" if not has_dmarc else None
    })

def check_mx():
    """§D · MX check — at least 1 MX record, priority < 30"""
    out, _, _ = run(f"dig +short MX {DOMAIN} @{NS}")
    mx_lines = [l for l in out.split('\n') if l and ' ' in l]
    has_mx = len(mx_lines) > 0
    RESULTS["checks"].append({
        "name": "MX", "pass": has_mx,
        "evidence": f"{len(mx_lines)} MX records" if has_mx else "NO MX RECORDS",
        "fix": "Add MX at : 1 aspmx.l.google.com (or your provider)" if not has_mx else None
    })

def check_blacklist():
    """§E · Blacklist check — query 4 major DNSBLs"""
    # Get SMTP sending IP via SPF if possible, else just check the resolved MX IPs
    bls = ["zen.spamhaus.org", "bl.spamcop.net", "b.barracudacentral.org", "dnsbl.sorbs.net"]
    listed_in = []
    # Try resolving MX A records and checking them
    out, _, _ = run(f"dig +short A $(dig +short MX {DOMAIN} @{NS} | head -1 | awk '{{print $2}}' | sed 's/\\.$//')")
    ips = [l for l in out.split('\n') if re.match(r'^\d+\.\d+\.\d+\.\d+$', l)]
    for ip in ips:
        rev = '.'.join(reversed(ip.split('.')))
        for bl in bls:
            query = f"{rev}.{bl}"
            res, _, _ = run(f"dig +short A {query}")
            if res and '127.0.0' in res:
                listed_in.append(f"{ip} in {bl}")
    clean = len(listed_in) == 0
    RESULTS["checks"].append({
        "name": "BLACKLIST", "pass": clean,
        "evidence": "clean" if clean else f"listed: {listed_in}",
        "fix": "Request delisting at the listed DNSBL provider's URL; do not send until clean" if not clean else None
    })

def check_smtp_time():
    """§F · SMTP round-trip latency check — must be < 5s"""
    mx_out, _, _ = run(f"dig +short MX {DOMAIN} @{NS}")
    mx_host = mx_out.split('\n')[0].split()[-1].rstrip('.') if mx_out else None
    if not mx_host:
        RESULTS["checks"].append({"name": "SMTP-TIME", "pass": False, "evidence": "no MX", "fix": "Fix MX first"})
        return
    start = datetime.datetime.now()
    try:
        socket.create_connection((mx_host, 25), timeout=5).close()
        ms = int((datetime.datetime.now() - start).total_seconds() * 1000)
        fast = ms < 5000
        RESULTS["checks"].append({
            "name": "SMTP-TIME", "pass": fast,
            "evidence": f"{ms}ms to {mx_host}",
            "fix": "MX unreachable or slow — check firewall / ISP port-25 block" if not fast else None
        })
    except Exception as e:
        RESULTS["checks"].append({"name": "SMTP-TIME", "pass": False, "evidence": f"FAIL: {e}", "fix": "MX not accepting connections — check provider"})

for fn in [check_spf, check_dkim, check_dmarc, check_mx, check_blacklist, check_smtp_time]:
    fn()

passed = sum(1 for c in RESULTS["checks"] if c["pass"])
total = len(RESULTS["checks"])
RESULTS["timestamp"] = datetime.datetime.now().isoformat()
RESULTS["domain"] = DOMAIN

# Verdict logic: 6/6 = GREEN, 4-5/6 = AMBER, <4 = RED
if passed == total:
    RESULTS["verdict"] = "GREEN"
    RESULTS["forecast"] = "10/11 emails land Inbox on Mon 14 Jul 09:00 BST"
elif passed >= 4:
    RESULTS["verdict"] = "AMBER"
    RESULTS["forecast"] = f"8/11 emails land Inbox. Fix {total-passed} issue(s) in <10 min, re-run, send."
else:
    RESULTS["verdict"] = "RED"
    RESULTS["forecast"] = "3/11 emails land Junk. DO NOT SEND. Wait 24h, re-run, fix all."

print(json.dumps(RESULTS, indent=2))
sys.exit(0 if RESULTS["verdict"] == "GREEN" else 1)

Save as verify-deliverability.py, run python3 verify-deliverability.py nicholastempleman.co.uk. 60-second output. JSON pretty-printed. Exit code 0 = GREEN.

§3 · The Output Format

What a healthy GREEN response looks like (run on Sun 13 Jul 18:00 BST after the hardening runbook is applied):

{
  "domain": "nicholastempleman.co.uk",
  "timestamp": "2026-07-13T18:00:42",
  "verdict": "GREEN",
  "forecast": "10/11 emails land Inbox on Mon 14 Jul 09:00 BST",
  "checks": [
    {"name": "SPF",       "pass": true,  "evidence": "\"v=spf1 include:_spf.google.com ~all\""},
    {"name": "DKIM",      "pass": true,  "evidence": "selectors found: ['google']"},
    {"name": "DMARC",     "pass": true,  "evidence": "\"v=DMARC1; p=quarantine; rua=mailto:dmarc@nicholastempleman.co.uk; pct=100; adkim=s; aspf=s\""},
    {"name": "MX",        "pass": true,  "evidence": "1 MX records"},
    {"name": "BLACKLIST", "pass": true,  "evidence": "clean"},
    {"name": "SMTP-TIME", "pass": true,  "evidence": "143ms to aspmx.l.google.com"}
  ]
}

§4 · The 6 Checks — What Each Catches

CheckWhat it catchesFailure consequence
SPFTXT record at root contains v=spf1 with allowed sendersReceiving servers may Junk on "sender not authorised"
DKIMAt least one valid <selector>._domainkey.<domain> TXT record with v=DKIM1Mail is unauthenticated → flagged as spoof
DMARCTXT at _dmarc.<domain> with v=DMARC1 AND p=quarantine or p=rejectReceivers have no policy to quarantine/reject spoofed mail
MXAt least 1 MX record with priority setMail cannot be delivered at all — connection refused
BLACKLISTSending IP is not in Spamhaus/Cop/Barracuda/SORBSReceiving servers Junk without even checking content
SMTP-TIMESMTP connection to MX completes in < 5sIndicates firewall or ISP port-25 block — silent failure

§5 · Verdict Decision Matrix

ScoreVerdictActionForecast
6 / 6 PASSGREENSend on Mon 14 Jul 09:00 BST as planned10/11 emails land Inbox
4-5 / 6 PASSAMBERFix failing checks using evidence + fix field, re-run, then send8/11 after fix
< 4 / 6 PASSREDDo NOT send Mon. Wait 24h, fix everything, re-run, send Tue3/11 if forced

§6 · Failure Mode Catalogue

The verifier emits a fix string for every failed check. Common patterns:

SPF: NO SPF RECORD FOUND

fix: "Add TXT: v=spf1 include:_spf.google.com ~all"
# At your DNS provider (Cloudflare/Namecheap/123-reg/IONOS), add TXT record:
#   Type: TXT
#   Host: @ (or nicholastempleman.co.uk)
#   Value: v=spf1 include:_spf.google.com ~all
#   TTL: 3600
# Wait 5 min for propagation, re-run verifier.

DKIM: NO DKIM KEYS FOUND

fix: "Generate DKIM 2048-bit via Gmail/Outlook/Mailgun admin; add as TXT at ._domainkey."
# Gmail Workspace: admin.google.com > Apps > Google Workspace > Gmail > Authenticate email > Generate new record
# Outlook / M365: security.microsoft.com > Email authentication > DKIM > Enable
# Mailgun: app.mailgun.com > Sending > Domains > Domain settings > DKIM
# Selectors will be 'google' (Gmail) or 'selector1' / 'selector2' (M365)
# Wait 30 min for DNS propagation, re-run verifier.

DMARC: NO DMARC RECORD FOUND

fix: "Add TXT at _dmarc.: v=DMARC1; p=quarantine; rua=mailto:dmarc@; pct=100; adkim=s; aspf=s"
# Type: TXT
# Host: _dmarc
# Value: v=DMARC1; p=quarantine; rua=mailto:dmarc@nicholastempleman.co.uk; pct=100; adkim=s; aspf=s
# p=quarantine is safer than p=reject for first-time setup (will Junk spam, not bounce legit)
# Wait 15 min for propagation, re-run verifier.

MX: NO MX RECORDS

fix: "Add MX at : 1 aspmx.l.google.com (or your provider)"
# Gmail: aspmx.l.google.com priority 1, alt1.aspmx.l.google.com priority 5, alt2 priority 5, alt3 priority 10, alt4 priority 10
# Outlook: .mail.protection.outlook.com priority 0
# Wait 30 min, re-run.

BLACKLIST: listed in zen.spamhaus.org

fix: "Request delisting at the listed DNSBL provider's URL; do not send until clean"
# Most DNSBLs have a free self-service delisting form:
#   Spamhaus: https://check.spamhaus.org/
#   Spamcop: https://www.spamcop.net/bl.shtml
#   Barracuda: https://www.barracudacentral.org/lookups
#   SORBS: http://www.sorbs.net/lookup.shtml
# Provide your IP, request removal, usually 24h turnaround.

SMTP-TIME: FAIL timeout

fix: "MX unreachable or slow — check firewall / ISP port-25 block"
# Many residential ISPs block port 25 outbound. Use:
#   - Webmail (gmail.com) instead of SMTP client
#   - SMTP relay on port 587 (submission) instead of 25
#   - Different network (mobile hotspot)
# Re-run verifier on different network.

§7 · Pre-Send Protocol — Sun 13 Jul 18:00 BST

  1. 17:55 — Open Terminal, run the verifier. If RED, jump to §6 fix catalog.
  2. 18:00 — Verifier outputs GREEN. Screenshot the JSON. Save as pre-send-verdict-2026-07-13.json.
  3. 18:01 — Send yourself a test email from Gmail. Check Gmail Sent folder.
  4. 18:02 — Send yourself a test email from Outlook. Check Outlook Sent folder.
  5. 18:03 — Send yourself a test email from ProtonMail. Check ProtonMail Sent folder.
  6. 18:04 — Check Gmail inbox: did all 3 arrive in Inbox (not Promotions / Spam)?
  7. 18:05 — Open mail-tester.com. Send to the displayed address. Click "Then check your score". Target 9/10 or 10/10.
  8. 18:08 — Review score. If < 7, fix content (no spam words, no all-caps subject, no broken images).
  9. 18:10 — Open defoneos-mod-first-email-blueprint.html. Read once.
  10. 18:15 — Pick first 3 buyers from CRM. Pre-write the 3 emails in Gmail Drafts. Save. Sleep on them.
  11. Mon 14 Jul 08:30 — Re-run verifier (60 sec). Should still be GREEN.
  12. Mon 14 Jul 08:50 — Open Drafts. Review once. Press send T1 buyer 1 at 09:00 BST.
  13. Mon 14 Jul 09:01 — T1 buyer 2. Wait 60 sec.
  14. ...continue staggered 11-minute cascade per the first-email-blueprint.

§8 · Recovery Protocol If Mon Morning Verdict Drops to RED

Two failure modes are possible on Mon morning:

Mode A — Verifier still RED at 08:30 Mon

Do NOT send at 09:00. Use the 24-hour defer protocol: send a single email to the highest-priority buyer (T1 #1) saying "slight delay on my end, will reach out Tuesday morning with full materials". This buys you 24h. Spend those 24h fixing the issue, re-running, sending Tue at 09:00.

Mode B — Verifier GREEN but mail-tester dropped below 8

Content triggered Junk, not infrastructure. Fix the email body: shorten subject (< 50 chars), remove any "FREE", "ACT NOW", "URGENT" words, ensure at least 1 plain-text paragraph, ensure signature has phone number. Re-run mail-tester. Target 9/10.

Mode C — Verifier GREEN, mail-tester 9+, but a specific buyer doesn't reply

This is not deliverability, it's priority or content. Wait 4 days. If no reply, send a one-line follow-up per the CRM. If still no reply after 8 days, mark cold and move to next buyer.

§9 · Automation Hooks — Schedule It

Schedule the verifier to run every 6 hours automatically, alerting only on RED. Add to crontab:

# Run verifier every 6h, alert Nick if RED
0 */6 * * * /usr/bin/python3 /Users/nicholas/clawd/bin/verify-deliverability.py nicholastempleman.co.uk > /tmp/verify.json 2>&1
VERDICT=$(jq -r .verdict /tmp/verify.json)
if [ "$VERDICT" = "RED" ]; then
  osascript -e 'display notification "Email deliverability RED — fix before sending" with title "DEFONEOS Verifier"'
fi

This runs the verifier in the background 4× per day. Nick only gets a Mac notification if it goes RED. Zero noise when GREEN.

§10 · Honesty Register

  1. GREEN is not a guarantee. It means SPF/DKIM/DMARC/MX/blacklist/SMTP-time are all passing. It does NOT guarantee Inbox placement for content reasons (spam words, all-caps subject, broken HTML, sender reputation).
  2. Mail-tester is the content check, not the verifier. Always run both. The verifier is infrastructure. Mail-tester is content.
  3. Domain reputation takes 2-4 weeks to build. First 2 weeks of DMARC reports will be SCARY (lots of spoofed mail hitting your quarantine). This is normal. After 2-4 weeks, the noise drops.
  4. Repeat for every domain you send from. If you also send from csoai.org or meok.ai, run the verifier for each. Each domain needs its own SPF/DKIM/DMARC.
  5. This verifier does not check BIMI, ARC, or TLSRPT. Those are nice-to-have but not P0 for first MOD emails. Skip for now.
  6. dstl-strict spam filter is real. dstl (Defence Science and Technology Laboratory) uses a stricter spam filter than commercial email. Even a 9/10 mail-tester score can Junk on dstl. The only defence is clean infrastructure (verifier GREEN) + clean content (mail-tester 9+) + no URL shorteners + no marketing tracking pixels.

§11 · Pair With These Pages