Instrumenting Mail Infrastructure with Prometheus: Deliverability, TLS, and Auth Metrics
Most mail operators learn about a deliverability incident when a customer forwards a bounce. Your MTA, DNS, and inbound auth results already emit the signals — here's how to scrape a Postfix/Dovecot/Rspamd stack with Prometheus so deferrals, cert expiry, and DKIM pass rates become alertable time-series before a mailbox provider notices.
EvilMail TeamJuly 28, 202612 min read
You almost never catch a deliverability incident in real time. The bounce lands in a customer's inbox first, they forward it to support, and by the time it reaches you Gmail has been deferring your mail for six hours and your domain reputation has already taken the hit. An expired TLS cert or a DKIM selector that silently stopped resolving plays out the same way — you find out when a mailbox provider tells you, which is the worst possible moment to find out.
Every signal you need is already being emitted. Postfix logs every deferral and every TLS negotiation. Rspamd tallies every SPF, DKIM, and DMARC verdict. Your DNS zone and your MX are answerable from anywhere on the internet. None of it is hidden — it's just unscraped. This is how to wire Prometheus exporters around a Postfix/Dovecot/Rspamd stack so that deliverability, TLS, and authentication health become first-class time-series you can alert on before a provider does.
The three metric families that predict an incident
The pager should fire on three things, none of which is a customer complaint:
Deliverability — rising deferral ratio, growing queue age, bounce ratio creeping up. Source: Postfix logs and the showq socket.
Authentication — SPF/DKIM/DMARC pass rates on inbound, alignment on your outbound as the world reports it back through DMARC aggregate reports. Source: Rspamd, mail-log auth results, and parsed RUA XML.
Each family maps to an exporter and a sane scrape interval. MTA metrics change fast, so scrape them at 15s. External probes are cheap but not free, so 5m is plenty. DMARC aggregate reports arrive daily, so ingest them hourly and let the trend build over 90 days.
Wiring postfix_exporter: queues, deferrals, bounce ratio
Deploy kumina/postfix_exporter. It reads /var/log/mail.log directly, or the systemd journal with --systemd.enable [email protected], and it attaches to the showq socket at /var/spool/postfix/public/showq to size the live queues.
bash
# systemd unit fragment — journal mode, no logfile grepping
ExecStart=/usr/local/bin/postfix_exporter \
--systemd.enable \
[email protected] \
--postfix.showq_path=/var/spool/postfix/public/showq \
--web.listen-address=127.0.0.1:9154
The gotcha that bites everyone: the exporter's user has to be in the postfix group to read the showq socket, and it needs journal access (systemd-journal group) or read on the logfile. If postfix_showq_message_size_bytes is empty, it's a permissions problem 100% of the time, not a config one.
postfix_smtp_deferred_messages_total — outbound deferrals; the numerator of your deferral ratio.
postfix_smtp_delivery_delay_seconds_count — successful outbound deliveries; the denominator you actually want.
postfix_qmgr_messages_inserted_total / postfix_qmgr_messages_removed_total — queue flow, in and out.
postfix_showq_message_size_bytes and postfix_showq_message_age_seconds — per-queue histograms labelled active, deferred, hold; the size _count is your live backlog and the age histogram feeds the queue-age alert.
postfix_up — the exporter's own liveness, so a dead exporter doesn't read as zero deferrals.
Turn the raw counters into ratios with recording rules — never alert on a raw counter, alert on a rate. And measure deferrals against *delivery attempts*, not against inbound volume, or your denominator drifts every time receive and send rates diverge:
A deferral ratio that steps from 0.5% to 6% over ten minutes is a mailbox provider throttling you. That is the earliest signal you will ever get, and it arrives hours before the bounce reaches a human.
TLS: cert expiry, protocol floor, and STARTTLS failures
TLS monitoring has two layers, and you need both.
Negotiation health comes from Postfix's own TLS loglines. Point an mtail program at lines like Anonymous TLS connection established ...: TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 and export counters keyed by tlsproto and tlscipher. This is how you discover that 3% of your outbound still negotiates TLS 1.0 to some ancient partner MX — or worse, falls back to cleartext because smtp_tls_security_level=may let it. You can't fix a distribution you can't see.
Cert expiry comes from blackbox_exporter probing every TLS-bearing port you run: 25 and 587 (STARTTLS), 465 (implicit TLS submission), 993 (Dovecot IMAPS), and 443 (webmail and API). The series that matters is probe_ssl_earliest_cert_expiry, a Unix timestamp of the nearest expiry in the chain.
Once the basics are green, add the advanced layer. Monitor MTA-STS policy freshness by fetching https://mta-sts.evilmail.pro/.well-known/mta-sts.txt and checking that max_age hasn't lapsed, and verify your DANE TLSA record at _25._tcp.evilmail.pro still matches the deployed cert's hash. A rotated cert with a stale TLSA record is a self-inflicted delivery outage to every DANE-validating peer.
Authentication: SPF/DKIM/DMARC as time-series
Rspamd exposes native Prometheus metrics at http://127.0.0.1:11334/metrics, but that endpoint gives you scan volume and the per-action breakdown (rspamd_actions_total{action="reject"} and friends) — not per-mechanism auth results. The SPF/DKIM/DMARC verdicts live in the Authentication-Results header Rspamd stamps on every inbound message. Point an mtail program at the mail log, match the dkim=pass / spf=fail / dmarc=pass tokens, and emit counters:
Track the pass rate as pass / (pass + fail), and a sudden drop tells you a sending partner broke their signing or an upstream started mangling headers.
Outbound is the harder, higher-value half. Nothing on your own box can tell you how the *world* sees your mail — only DMARC aggregate reports can. So parse them. Set rua=mailto:[email protected] in your DMARC record, then run an hourly cron that ingests the RUA XML, computes per-source alignment percentages, and writes them as node_exporter textfile metrics:
That last line is the entire point. A forwarder or a misconfigured ESP relaying your domain with broken SPF alignment shows up as a sagging ratio days before it moves your aggregate reputation. Always write the textfile to a temp name and mv it into place — the collector reads whole files, and a half-written .prom produces garbage samples.
Self-check your own signing too: resolve default._domainkey.evilmail.pro, confirm the key is present and at least 2048-bit, and export a dkim_selector_present gauge. A selector that vanishes from DNS during a zone edit is a silent catastrophe, and it's a one-line check to catch.
Blackbox from the outside: probe your MX like a stranger
Internal scrapes lie by omission. Split-horizon DNS resolves your MX to a private address, host firewalls wave localhost through, and your monitoring host sits in the same trust zone as the mail server. None of that reflects what Gmail's connecting relay experiences. Run at least one blackbox_exporter from a different network or region and probe your public MX exactly as a stranger would:
This one module verifies MX reachability, the greeting banner, that STARTTLS is actually advertised, that the handshake completes, and that the chain validates — all from a vantage firewalls and split-horizon DNS can't fake. Because it's a TLS-bearing TCP probe, blackbox also exports probe_ssl_earliest_cert_expiry for it, so your cert ladder covers port 25 too. For a manual spot-check, nothing beats openssl s_client -starttls smtp -connect mx.evilmail.pro:25 to read the negotiated protocol and full chain by eye.
Alerts and dashboards that page correctly
Thresholds, each with a for: duration to kill flapping and routed to the team that can actually act:
Deferral ratio > 5% for 10m → deliverability team, warning.
`postfix_showq_message_age_seconds` top bucket crossing 900s → deliverability, page.
Cert expiry < 7d → infra, page (< 21d warns).
DKIM or DMARC pass rate drops > 20% against a 6h baseline → deliverability, page. Compare with avg_over_time(...[6h] offset 6h) so a genuine step change trips it but normal diurnal wobble doesn't.
`dmarc_aligned_ratio < 0.95` for any source over your volume floor → deliverability, warning.
Test the routing before you trust it — amtool config routes test and amtool alert add let you fire a synthetic alert and confirm it lands with the right team and silences, rather than discovering the receiver is misconfigured at 3am. On the Grafana side, one row per metric family — deliverability, TLS, auth — keeps the on-call's eye where it belongs; don't cram cert expiry next to queue depth.
The 2026 context makes this non-optional. Google and Yahoo's bulk-sender rules require enforced DMARC, one-click unsubscribe, and a spam-complaint rate under 0.3%. A silent SPF misalignment on high-volume transactional or temp-email traffic torches reputation fast, and "we'll notice when bounces spike" is not a strategy that survives those thresholds.
Ship-it checklist
postfix_exporter deployed with postfix and systemd-journal group membership; postfix_showq_message_size_bytes populated.