Automation / Live tool · Own-domain study
MailProof
Email authentication, explained. A live diagnostic tool for SPF, DKIM, DMARC, MX, and bounce messages.
The work
The challenge
Diagnose why moarkid.com could receive email but could not send authenticated email reliably.
The approach
Read DNS directly, follow SPF includes, count lookups, and test common DKIM selectors. Explain what can be verified and what a missing selector cannot prove.
Implementation
A CLI, HTTP API, and web interface run on Docker Compose behind Caddy, with rate limiting, automated delivery, and external monitoring.
Context & results
The original study reports authenticated mail on my own domain. The complete case study records the changes, checks, and limitations.
Technology
Explore
Technical notes & complete original case study
Case study
MailProof: getting moarkid.com to send authenticated email
A domain that could receive email but not send it reliably, a tool built to find out why, and the before and after on my own domain.
- Client
- My own domain, moarkid.com (not a paid engagement)
- Stack
- Python, FastAPI, Docker Compose, Caddy, GitHub Actions
- Dates
- Before 2026-10-03, after 2026-10-04
10/10
mail-tester score
Pass
SPF, DKIM and DMARC in Gmail
Inbox
Gmail delivery, not spam
0
blocklists listing the sending IP
Every result on this page was recorded in the MailProof repository (docs/case-study-moarkid.md) on the dates shown. This was my own domain, not a client's.
The problem
On 2026-10-03, moarkid.com could receive mail through Cloudflare Email Routing, but nothing was set up to send mail as the domain. There was no DMARC record and no DKIM key, so a receiving server had no way to confirm that a message from hello@moarkid.com was genuine. Lead emails from this site go out through that address, so that had to change.
The first MailProof report on the domain said exactly that:
[HIGH] No DMARC record
No record at _dmarc.moarkid.com.
[WARN] No DKIM key at any of 21 common selectors
[INFO] SPF record found
1 of 10 DNS lookups used. Authorized through: Cloudflare Email Routing.Constraints
- Read only what DNS says. A check has to report facts read from DNS, never generated text, so a finding can be checked by anyone with
dig. - DNS cannot list DKIM selectors. The tool can only try common ones (21 of them), so a miss is not proof that DKIM is absent, and the report has to say so.
- SPF stops at 10 DNS lookups. Receivers fail a record past that limit, so the tool has to follow every include and count.
- Some networks block DNS over TCP. Large TXT answers do not fit in UDP, and the standard retry over TCP port 53 fails on those networks.
- One small server. The public checker runs on a single host with Docker Compose and Caddy, so it needs rate limiting and must survive a reboot without manual steps.
Approach
- Built the checker first. A command line tool and an HTTP API that read SPF, DMARC, MX and DKIM for a domain and return a list of findings, each with a severity and the fix. Bounce messages from Microsoft and Gmail are decoded into a cause and a fix, with the source of each rule recorded.
- Ran it on moarkid.com and fixed what it reported, one finding at a time:
- published DMARC at
_dmarc.moarkid.comwithp=noneand reports todmarc@moarkid.com, which cleared the HIGH finding; - chose Resend as the sending service (it sends through Amazon SES in eu-west-1) and published its DKIM key at
resend._domainkey.moarkid.com, plus two CNAME records for the return path; - left the root SPF record as it was. Resend uses
rsend.moarkid.comas the return path, which carries its own SPF record, and SPF still aligns for DMARC because both names share the organizational domain.
- published DMARC at
- Tested delivery from outside: a message to Gmail and one to mail-tester.com, both on 2026-10-04.
Key decisions
- Findings are structured data, not prose (decision 001). Every check returns a list of findings with a code, severity, title, detail and fix. The command line, the API and the web page all read that list, and the tool works without any language model.
- Retry over DNS-over-HTTPS (decision 002). A failed lookup is retried once through a public DoH resolver, because TCP port 53 is blocked on some networks. The trade-off is that the queried name goes to that resolver, so the retry can be turned off.
- Run independent lookups at the same time (decision 003). A check made about 25 lookups in a row, which took 3 to 4 seconds on the live site. The four checks, the 21 DKIM selectors and each level of the SPF include tree now go out together. Replaying real answers for gmail.com, stripe.com, moarkid.com and shopify.com at 120 ms per lookup took 0.24 to 0.37 seconds, against 3.1 to 3.9 seconds before, with identical output. A test fails if those lookups ever run one after another again.
- Cap the SPF tree at 50 includes. Without a limit, a hostile zone where each include names ten more would turn one check into millions of lookups. Receivers stop at 10, so 50 still shows every tree that matters.
- Rate limit the public API. Each client address gets 10 requests per minute across checks and bounce decoding, then a 429 with
Retry-After.
What went wrong, and the fix
On 2026-10-05, before a planned reboot test, I found that ports 80 and 443 were open in the running firewall but missing from /etc/iptables/rules.v4, the file the firewall loads at boot. The next reboot would have taken the site down with healthy containers behind it. I added both rules to the file, validated it with iptables-restore --test, and rebooted with nothing done by hand afterwards. /healthz returned 200 from outside and HTTP redirected to HTTPS. The write-up is incident 001 in the repository.
Verified results
Recorded on 2026-10-04:
- MailProof: exit code 0. The HIGH finding (no DMARC) and the DKIM warning were gone.
- Gmail "Show original":
dkim=passformoarkid.com,spf=passfor thersend.moarkid.comreturn path,dmarc=pass. The message arrived in the inbox, not spam, over TLS 1.3. Both SPF and DKIM align, so DMARC would still pass if either failed. - mail-tester.com: 10/10. SpamAssassin scored the message -0.1 against a threshold of 5.0, and the sending IP was on no blocklist, with reverse DNS matching its HELO name.
The engineering behind the live checker, checked on 2026-10-20:
- 124 automated tests pass, with no network needed.
- Every push to
mainthat passes CI deploys to the server; the server never holds GitHub credentials. - An hourly job checks the live site from outside (
/healthzover HTTPS, and the HTTP redirect), which catches a broken certificate, DNS record or firewall rule that the deploy check cannot see.
What I would do next
- Move DMARC from
p=nonetoquarantineonce the aggregate reports show every legitimate sender passing. Until then, spoofed mail is still delivered. - Rotate to a 2048-bit DKIM key. Resend issued a 1024-bit key; 2048-bit is the current recommendation.
- Read pasted email headers, for the case where every DNS check passes and mail still fails.
- Add the blocklist check to MailProof itself. Today the blocklist result above comes from mail-tester, not from the tool.
- Record the time taken. The repository's case study leaves it blank, so I make no claim about it here.
Want the same approach on your AWS bill? The AWS Cost Audit is $250 fixed, delivered in 48 hours.