Monitor DMARC, TLS reporting, and mail DNS records with Prometheus
mailreportd is a daemon that reads DMARC and SMTP TLS reports from a mailbox and checks the SPF, DKIM, DMARC, and MTA-STS records that your domain publishes. It exports the results as Prometheus metrics, so you can use your existing alerting to find out when mail authentication fails.
mailreportd is a single Go binary. It needs a YAML configuration file and stores its data in SQLite. It doesn't include a web UI and doesn't integrate with a specific mail server.
go install github.com/elsbrock/mailreportd@latest
Source code Releases Documentation
Background
Receiving mail servers check several DNS records to decide whether mail that uses your domain is legitimate. Sending mail servers check other records to decide whether they can deliver to you securely.
- SPF
- Lists the servers that are allowed to send mail for your domain.
- DKIM
- Publishes the public keys that receivers use to verify the signatures that your servers add to outgoing mail. Each key is published under a name called a selector.
- DMARC
- Tells receivers what to do with mail that fails both SPF and DKIM, for example to reject it. It also tells receivers where to send reports.
- DMARC reports
- Receivers such as Gmail and Outlook send aggregate reports to the
ruaaddress in your DMARC record, usually once a day. A report lists the servers that sent mail using your domain, and whether that mail passed SPF, DKIM, and DMARC. Some receivers also send failure reports about individual messages. - MTA-STS
- Tells sending servers that your MX hosts support TLS, and which hosts are valid. Senders that enforce MTA-STS don't deliver mail over an unencrypted or unverified connection.
- SMTP TLS reports (TLS-RPT)
- Sending servers report whether they could establish a TLS connection to your MX hosts, usually once a day.
If one of these records is wrong, receivers can reject your mail or mark it as spam, and your own servers don't log an error. The reports show how receivers handled your mail, but they arrive as compressed XML and JSON attachments. mailreportd parses the reports, checks the records, and turns both into metrics that you can alert on.
What mailreportd monitors
mailreportd gets data from two sources:
- DNS and HTTPS. mailreportd checks your published records every 15 minutes by default.
- Reports from receivers. Receivers usually send aggregate reports once a day, so report data is up to a day old.
Published records
| Problem | Metric |
|---|---|
| A DKIM selector is missing, malformed, or uses a key shorter than 1024 bits. | mailreport_ |
| The SPF record requires more than 10 DNS lookups. Receivers return a permanent error, and SPF fails for all of your mail. | mailreport_ |
| An MX host isn't listed in the MTA-STS policy. Senders that enforce MTA-STS don't deliver to that host. | mailreport_ |
The MTA-STS policy changed, but its id value didn't. Senders
continue to use the cached policy until max_age expires. |
mailreport_mtasts_stale_id |
The rua address is on another domain that doesn't authorize it.
Most large receivers don't send reports in this case. |
mailreport_ |
The DMARC pct value is less than 100, so the policy applies to
only part of your mail. |
mailreport_dmarc_record_pct |
Reports
| Problem | Metric or endpoint |
|---|---|
| Mail from your own servers fails DMARC at a receiver. | mailreport_ |
| Senders can't establish a TLS connection to your MX hosts, for example because of an expired certificate. | mailreport_tlsrpt_failures |
| Mail that uses your domain comes from servers that your SPF record doesn't authorize. | mailreport_ |
| DKIM fails, but mail still passes DMARC because SPF passes. | dkim_pass field in /api/v1/domains |
| Reports stop arriving. | mailreport_ |
Example output
mailreportd serves metrics on a private listener:
$ curl -s localhost:9188/metrics | grep example.org mailreport_dkim_record_state{domain="example.org",selector="mail",state="ok",tenant="default"} 1 mailreport_dkim_record_state{domain="example.org",selector="old",state="revoked",tenant="default"} 1 mailreport_mtasts_mx_covered{domain="example.org",tenant="default"} 0 mailreport_spf_lookups{domain="example.org",tenant="default"} 7 mailreport_dmarc_record_policy{domain="example.org",p="reject",sp="reject",tenant="default"} 1 mailreport_dmarc_messages{disposition="none",dkim="pass",domain="example.org",reporter="google.com",source_class="self",spf="pass",tenant="default"} 1843 mailreport_dmarc_messages{disposition="reject",dkim="fail",domain="example.org",reporter="google.com",source_class="unknown",spf="fail",tenant="default"} 412 mailreport_tlsrpt_sessions{domain="example.org",policy_type="sts",result="successful",tenant="default"} 2210
In this example, the highlighted lines show two problems. An MX host is missing
from the MTA-STS policy (mailreport_mtasts_mx_covered is 0), and Google
rejected 412 messages from a server that your SPF record doesn't authorize.
The repository includes Prometheus alerting rules and a Grafana dashboard for these metrics.
To find the sources and receivers behind an alert, use the JSON API on the same listener:
$ curl -s 'localhost:9188/api/v1/sources?domain=example.org&limit=1'
{
"window_start": "2026-09-22T19:00:00Z",
"tenant": "default",
"sources": [
{
"source_ip": "203.0.113.24",
"domain": "example.org",
"total": 412,
"passing": 0,
"failing": 412,
"dkim_pass": 0,
"spf_pass": 0,
"quarantined": 0,
"rejected": 412,
"first_seen": "2026-10-19T00:00:00Z",
"last_seen": "2026-10-21T00:00:00Z"
}
]
}
Label cardinality
Metrics don't use source IP addresses or DKIM signing domains as labels. Senders choose these values, so they can create an unlimited number of time series. Per-source data is available from the JSON API instead.
How it works
rua mailbox ── IMAP, read-only ─┐ maildir ────────────────────┼─▶ decode ─▶ parse ─▶ SQLite ─┬─▶ /metrics POST /tlsrpt ────────────────────┘ └─▶ /api/v1/* DNS + HTTPS ── SPF, DKIM, DMARC, MTA-STS ──────────────────────────▶ /metrics
- Read-only mailbox access. mailreportd doesn't flag, move, or
delete messages. It reads
INBOXand the folder that the server marks as\Junk. - Idempotent ingestion. mailreportd detects duplicate messages and resent reports. You can reprocess the whole mailbox safely.
- Quarantine. mailreportd stores messages that it can't parse, together with the error.
- Failure report encryption. DMARC failure reports contain recipient data. mailreportd encrypts their content with a key that you provide, deletes it after seven days, and logs every read. If you don't provide a key, mailreportd stores only the report metadata.
- Retention. mailreportd keeps parsed reports for 365 days and failure report content for 7 days by default. Exported metrics cover the last 30 days.
- Check errors. If a DNS lookup fails, mailreportd increments
mailreport_policy_check_errors_totalinstead of reporting the record as broken.
Supported standards
| Standard | Support |
|---|---|
| RFC 7489 | DMARC aggregate reports, DMARC record checks, external rua authorization |
| RFC 6591 | DMARC failure reports |
| RFC 8460 | SMTP TLS reports, by email or HTTPS |
| RFC 7208 | SPF lookup count, source classification |
| RFC 6376 | DKIM selector checks |
| RFC 8461 | MTA-STS policy checks |
mailreportd doesn't check DANE or BIMI records.
Comparison with other tools
- parsedmarc
- Parses aggregate, failure, and TLS reports, and sends the results to Elasticsearch, OpenSearch, Splunk, and other outputs. It doesn't export Prometheus metrics or check DNS records.
- dmarc-metrics-exporter
- Exports Prometheus metrics for DMARC aggregate reports. It doesn't process TLS reports or check DNS records, and it moves processed messages to other folders.
- dmarc-report-viewer
- Shows DMARC and TLS reports in a web UI. It doesn't export metrics.
- Hosted services
- Services such as URIports, dmarcian, EasyDMARC, and Postmark process reports for you. Your report data is sent to the service provider.
Get started
-
Install mailreportd:
go install github.com/elsbrock/mailreportd@latest
You can also use the container image
ghcr.io/elsbrock/mailreportd, a release binary, or the Nix flake. -
Set the
ruatag of your DMARC record and theruatag of your TLS-RPT record to an address that delivers to an IMAP mailbox. -
Create a file named
config.yaml:tenants: - id: default domains: [example.org] policy: selectors: example.org: [mail] feeders: - type: imap tenant: default host: mail.example.org username: dmarc@example.org password_env: MAILREPORTD_IMAP_PASSWORD storage: path: /var/lib/mailreportd/reports.dbList your DKIM selectors under
policy.selectors. DNS doesn't provide a way to discover them. For all options, seeconfig.example.yaml. -
Validate the configuration:
mailreportd -config config.yaml -check
-
Start mailreportd:
MAILREPORTD_IMAP_PASSWORD=PASSWORD mailreportd -config config.yaml
-
Add
localhost:9188as a Prometheus scrape target.