mailreportd What it monitors Get started

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

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 rua address 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:

Published records

ProblemMetric
A DKIM selector is missing, malformed, or uses a key shorter than 1024 bits. mailreport_dkim_record_state
The SPF record requires more than 10 DNS lookups. Receivers return a permanent error, and SPF fails for all of your mail. mailreport_spf_lookup_limit_exceeded
An MX host isn't listed in the MTA-STS policy. Senders that enforce MTA-STS don't deliver to that host. mailreport_mtasts_mx_covered
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_dmarc_rua_authorized
The DMARC pct value is less than 100, so the policy applies to only part of your mail. mailreport_dmarc_record_pct

Reports

ProblemMetric or endpoint
Mail from your own servers fails DMARC at a receiver. mailreport_dmarc_messages{source_class="self"}
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_dmarc_messages{source_class="unknown"}
DKIM fails, but mail still passes DMARC because SPF passes. dkim_pass field in /api/v1/domains
Reports stop arriving. mailreport_report_last_received_timestamp_seconds

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

Supported standards

StandardSupport
RFC 7489DMARC aggregate reports, DMARC record checks, external rua authorization
RFC 6591DMARC failure reports
RFC 8460SMTP TLS reports, by email or HTTPS
RFC 7208SPF lookup count, source classification
RFC 6376DKIM selector checks
RFC 8461MTA-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

  1. 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.

  2. Set the rua tag of your DMARC record and the rua tag of your TLS-RPT record to an address that delivers to an IMAP mailbox.

  3. 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.db

    List your DKIM selectors under policy.selectors. DNS doesn't provide a way to discover them. For all options, see config.example.yaml.

  4. Validate the configuration:

    mailreportd -config config.yaml -check
  5. Start mailreportd:

    MAILREPORTD_IMAP_PASSWORD=PASSWORD mailreportd -config config.yaml
  6. Add localhost:9188 as a Prometheus scrape target.