DMARC aggregate reports are free XML that Google, Microsoft, Yahoo, and other mailbox providers email you every day. The dashboards that make them readable typically charge per domain per month. We were paying for one, so I built a self-hosted collector.
The main difference from parsedmarc (the mature, established project in this space) is that this one is the MTA. It runs aiosmtpd on port 25 and receives reports directly instead of polling a mailbox, so there are no IMAP credentials to manage. It also generates a unique report address for every domain and the DNS records to publish, including the _report._dmarc external destination verification record required for cross-domain reporting. If your host blocks port 25, it can poll an IMAP mailbox instead.
A few implementation details I'm particularly happy with:
- Raw messages are stored before they're parsed, so parser bugs are replayable instead of losing reports. Messages sent to unknown report addresses are quarantined rather than rejected, because a bounced DMARC report is gone forever.
- DNS verification queries the domain's authoritative nameservers instead of a recursive resolver. I originally did the obvious thing and spent far too long convinced Cloudflare wasn't saving my changes.
- Sending IPs are resolved to their network owner via PTR and RDAP, which turned out to be much more useful than I expected. The organisation sending the report is usually not the organisation that sent the email, and "Twilio SendGrid" is far more useful than an anonymous IP in a Google report.
It's built with FastAPI, aiosmtpd, SQLAlchemy, and PostgreSQL or SQLite. The UI is server-rendered with SVG charts—no JavaScript build step. Deployment is via Docker Compose or the included Helm chart. MIT licensed.
It's only a few weeks old and has one maintainer, but it's already collecting real reports for five of our own domains, which is where most of the bugs were found. A substantial amount of the implementation was written with Claude Code under my review; I made the architectural decisions, reviewed the code, and wrote the tests.
DMARC aggregate reports are free XML that Google, Microsoft, Yahoo, and other mailbox providers email you every day. The dashboards that make them readable typically charge per domain per month. We were paying for one, so I built a self-hosted collector.
The main difference from parsedmarc (the mature, established project in this space) is that this one is the MTA. It runs aiosmtpd on port 25 and receives reports directly instead of polling a mailbox, so there are no IMAP credentials to manage. It also generates a unique report address for every domain and the DNS records to publish, including the _report._dmarc external destination verification record required for cross-domain reporting. If your host blocks port 25, it can poll an IMAP mailbox instead.
A few implementation details I'm particularly happy with:
- Raw messages are stored before they're parsed, so parser bugs are replayable instead of losing reports. Messages sent to unknown report addresses are quarantined rather than rejected, because a bounced DMARC report is gone forever. - DNS verification queries the domain's authoritative nameservers instead of a recursive resolver. I originally did the obvious thing and spent far too long convinced Cloudflare wasn't saving my changes. - Sending IPs are resolved to their network owner via PTR and RDAP, which turned out to be much more useful than I expected. The organisation sending the report is usually not the organisation that sent the email, and "Twilio SendGrid" is far more useful than an anonymous IP in a Google report.
It's built with FastAPI, aiosmtpd, SQLAlchemy, and PostgreSQL or SQLite. The UI is server-rendered with SVG charts—no JavaScript build step. Deployment is via Docker Compose or the included Helm chart. MIT licensed.
https://github.com/pescheckit/dmarc-service
It's only a few weeks old and has one maintainer, but it's already collecting real reports for five of our own domains, which is where most of the bugs were found. A substantial amount of the implementation was written with Claude Code under my review; I made the architectural decisions, reviewed the code, and wrote the tests.
Happy to answer any questions.