mailops is a standalone mail deliverability, email security, and mail
operations checker. It is designed to fit the same ecosystem as dnsops and
certops: human-friendly terminal output, JSON/YAML reports, Prometheus text
output, and optional OTLP/HTTP metric export.
mailops check example.com
mailops check example.com --json
mailops check example.com --yaml
mailops check example.com --promUse a provider profile when the domain is backed by a known mail platform:
mailops check example.com --profile google-workspace
mailops check example.com --profile microsoft-365Run active SMTP and mailbox-access probes:
mailops check example.com --smtp --smtp-port 25
mailops check example.com --smtp --smtp-host smtp.example.com --smtp-port 465,587
mailops check example.com --mda --mda-host imap.example.com --mda-host pop.example.comCheck reputation lists:
mailops check example.com --reputation
mailops check example.com --reputation --domainbl dbl.spamhaus.org --domainbl multi.surbl.orgExport OTEL metrics:
mailops check example.com --otel-endpoint http://localhost:4318mailops requires Go 1.22 or newer. Build it from the project directory:
go mod download
go build -trimpath -o mailops .
./mailops versionInstall the resulting binary somewhere on PATH, for example:
sudo install -m 0755 mailops /usr/local/bin/mailopsChecks use the system DNS resolver. Core checks require outbound DNS and may fetch an MTA-STS policy over HTTPS. SMTP, MDA, reputation, and OTEL options also require access to their respective network endpoints.
mailops check <domain> [--json|--yaml|--prom] [options]
mailops version
Options may appear before or after the domain. List options are repeatable and also accept comma-separated values.
| Option | Default | Purpose |
|---|---|---|
--profile <name> |
none | Apply a provider profile; repeatable. |
--selector <name> |
common selectors | Select DKIM selectors; repeatable. |
--rbl <zone> |
built-in IPv4 DNSBLs | Select IP reputation zones; repeatable. |
--no-rbl |
false | Disable the otherwise enabled MX-IP DNSBL checks. |
--reputation |
false | Enable domain DBL/SURBL/URIBL-style checks. |
--domainbl <zone> |
built-in domain lists | Select domain reputation zones; repeatable. |
--smtp |
false | Probe SMTP endpoints. |
--smtp-host <host> |
MX hosts | Select SMTP hosts; repeatable. |
--smtp-port <port> |
25 |
Select SMTP ports; repeatable. |
--no-open-relay |
false | Skip the SMTP unauthenticated RCPT relay probe. |
--mda |
false | Probe IMAP and POP3 endpoints. |
--mda-host <host> |
common names | Select MDA hosts; repeatable. |
--mda-port <port> |
143,993,110,995 |
Select MDA ports; repeatable. |
--strict |
false | Promote selected policy and transport warnings to critical. |
--timeout <duration> |
10s |
Set the overall check deadline. |
--fail-on <level> |
critical |
Exit with status 1 at warn, critical, or error. |
--otel-endpoint <url> |
none | POST OTLP/HTTP JSON metrics to the endpoint. |
--json |
false | Emit JSON instead of terminal output. |
--yaml |
false | Emit YAML instead of terminal output. |
--prom |
false | Emit Prometheus text exposition. |
The three output flags are mutually exclusive. --otel-endpoint is
independent of the selected local output format. If its URL does not end in
/v1/metrics, that path is appended automatically.
Core DNS and policy checks:
- MX presence and MX host resolution.
- SPF presence, multiple SPF detection,
allpolicy warnings, and estimated DNS lookup count afterinclude/redirectexpansion. - DKIM selector discovery and public key parsing.
- DMARC presence, policy,
pct, report destinations, and external report authorization hints. - MTA-STS TXT and HTTPS policy.
- MTA-STS MX coverage: whether real MX hosts are covered by policy
mx:patterns. - TLS-RPT record.
- BIMI record syntax, HTTPS SVG/SVGZ logo and authority-evidence URIs,
explicit publication decline, duplicate records, and DMARC-enforcement
compatibility including
pct=100. - PTR and forward-confirmed reverse DNS for MX IPs.
- DNSBL/RBL checks for MX IPv4 addresses.
- Optional domain reputation checks through DNS-based DBL/SURBL/URIBL-style zones.
Active SMTP checks:
- TCP connect.
- Banner read.
- EHLO.
- STARTTLS on
25/587. - Implicit TLS on
465. - TLS version and certificate expiry.
- Advertised SMTP AUTH mechanisms.
- Unauthenticated open-relay RCPT probe without sending message
DATA.
Active MDA checks:
- IMAP
143. - IMAPS
993. - POP3
110. - POP3S
995. - TCP connect.
- Banner/capability checks.
- STARTTLS/STLS where applicable.
- TLS version and certificate expiry.
Profiles pre-fill common DKIM selectors and provider endpoints. Explicit CLI
flags still win: if you pass --smtp-host, --smtp-port, --mda-host, or
--mda-port, those values are kept.
Built-in profiles:
google-workspacegmailmicrosoft-365office365amazonsessendgridmailgunpostmarkmandrillzoho
Examples:
mailops check example.com --profile google-workspace --smtp --mda
mailops check example.com --profile sendgrid --smtp
mailops check example.com --profile microsoft-365 --selector selector1 --selector selector2By default, --smtp checks MX hosts on port 25.
mailops check example.com --smtpFor submission endpoints, use explicit hosts:
mailops check example.com --smtp --smtp-host smtp.example.com --smtp-port 587
mailops check example.com --smtp --smtp-host smtp.example.com --smtp-port 465,587465 is treated as implicit TLS. 25 and 587 use plain SMTP first and then
STARTTLS if advertised.
Open relay checking is enabled whenever --smtp is enabled. The probe issues:
MAIL FROM:<mailops-open-relay-test@example.net>
RCPT TO:<mailops-open-relay-test@example.org>
RSET
It does not send DATA. If the server accepts the external unauthenticated
recipient, mailops reports a critical open-relay finding.
Disable this probe only when a target policy forbids RCPT probing:
mailops check example.com --smtp --no-open-relay--mda checks mailbox access endpoints without logging in.
mailops check example.com --mda
mailops check example.com --mda --mda-host imap.example.com --mda-port 993
mailops check example.com --mda --mda-host imap.example.com --mda-host pop.example.com --mda-port 993,995When no host is provided, mailops tries common names:
imap.<domain>pop.<domain>mail.<domain>
The MDA probe checks service availability and TLS posture. It does not validate user credentials or read mailboxes.
--reputation checks domain reputation DNS zones. Defaults:
dbl.spamhaus.orgmulti.surbl.orgmulti.uribl.com
Example:
mailops check example.com --reputationSome public reputation zones return 127.0.0.1 for blocked, rate-limited, or
policy-denied queries. mailops treats that as a lookup error, not as a real
listing.
Terminal output is the default. It uses the same colored table style as
dnsops and certops.
mailops check example.comMachine-readable output:
mailops check example.com --json
mailops check example.com --yaml
mailops check example.com --promPrometheus metric names use the mailops_ prefix. OTEL metric names use the
mailops.* namespace.
JSON output is an array of reports, even for one domain. YAML output uses a
top-level reports key. Each report contains the normalized domain, aggregate
status, individual protocol results, findings, summary counts, generation
time, and duration. Prometheus and OTEL output expose the operational subset
as metrics rather than serializing the entire report.
OTEL export currently uses OTLP/HTTP JSON without custom authentication headers. An export failure is written to standard error but does not replace the mail check status or its output.
--prom is useful for:
node_exportertextfile collector.- cron/systemd timers.
- CI jobs.
- one-shot probes from a private agent.
For textfile collector usage, write atomically:
out=/var/lib/node_exporter/textfile_collector/mailops_example.prom
mailops check example.com --prom --profile google-workspace --reputation --smtp --mda --no-rbl > "${out}.tmp"
mv "${out}.tmp" "${out}"Useful operational metrics:
mailops_check_upmailops_check_duration_secondsmailops_last_check_timestamp_secondsmailops_status_code
Useful DNS/policy metrics:
mailops_mx_recordsmailops_record_presentmailops_record_validmailops_spf_estimated_lookupsmailops_spf_lookup_limit_usedmailops_dmarc_pctmailops_dkim_valid_selectorsmailops_mta_sts_mx_matched_ratiomailops_ptr_forward_confirmed_ratio
Useful reputation metrics:
mailops_rbl_hitsmailops_rbl_listedmailops_domainbl_hitsmailops_domainbl_listedmailops_domainbl_lookup_error
Useful SMTP/MDA metrics:
mailops_smtp_connect_okmailops_smtp_tls_okmailops_smtp_auth_mechanismsmailops_smtp_open_relay_checkedmailops_smtp_open_relaymailops_smtp_relay_mail_codemailops_smtp_relay_rcpt_codemailops_smtp_cert_days_remainingmailops_mda_connect_okmailops_mda_tls_okmailops_mda_cert_days_remaining
Example Prometheus alert ideas:
time() - mailops_last_check_timestamp_seconds > 900
mailops_status_code >= 2
mailops_smtp_open_relay == 1
mailops_smtp_tls_ok == 0
mailops_mda_tls_ok == 0
mailops_smtp_cert_days_remaining < 14
mailops_mta_sts_mx_matched_ratio < 1
mailops_domainbl_hits > 0
Examples are in:
examples/systemd/mailops-example.serviceexamples/systemd/mailops-example.timer
Typical install:
sudo cp examples/systemd/mailops-example.service /etc/systemd/system/
sudo cp examples/systemd/mailops-example.timer /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now mailops-example.timerMake sure node_exporter is started with:
--collector.textfile.directory=/var/lib/node_exporter/textfile_collector
--fail-on controls when mailops exits non-zero:
mailops check example.com --fail-on warn
mailops check example.com --fail-on critical
mailops check example.com --fail-on errorDefault:
critical
| Code | Meaning |
|---|---|
0 |
The aggregate status is below the configured --fail-on threshold. |
1 |
The aggregate status reached the configured threshold. |
2 |
The command, option, domain, or option value is invalid. |
--strict does not enable additional probes. It promotes selected findings:
DMARC p=none, missing or invalid MTA-STS and TLS-RPT, SMTP without STARTTLS,
and failures on requested MDA endpoints. This is useful for domains where
transport policy is mandatory rather than advisory.
The test suite uses in-memory DNS resolvers, protocol pipes, and HTTP transport stubs, so it does not require external DNS, SMTP, IMAP, POP3, or OTLP services.
go test ./...
go test -race ./...
go vet ./...Generate a coverage report with:
go test ./... -coverprofile=coverage.out
go tool cover -func=coverage.out- DMARC is currently queried only at
_dmarc.<input-domain>. The RFC 9989 DNS Tree Walk, Public Suffix Domain policies, and the newernp,psd, andtpolicy semantics are not implemented yet. Until they are, checking a subdomain can report DMARC as missing even when a parent policy applies. - SPF validation estimates DNS-triggering mechanisms and expands
include/redirect, but it is not a complete SPF evaluator. It does not model macros, void-lookup limits, or every per-mechanism DNS limit. - DKIM has no standardized selector discovery mechanism. Common selectors and
provider profiles are best-effort; use
--selectorfor authoritative results. - BIMI validates the DNS record and its HTTPS URI shapes, but does not fetch or parse the SVG Tiny P/S logo and does not validate VMC/CMC certificate contents.
- DNSSEC validation and SMTP DANE/TLSA policy checks are not implemented.
- SMTP TLS probing proves that a TLS 1.2+ session with a trusted certificate works. It does not enumerate accepted legacy protocol versions, cipher suites, or REQUIRETLS support.
- End-to-end inbox placement is not checked. That requires sending controlled messages to seed mailboxes and using credentials or provider APIs.
- SMTP open-relay probing stops before
DATA; it detects RCPT acceptance, not final delivery. - MDA probes do not log in, verify credentials, or read mailboxes.
- One domain is checked per invocation, using the system resolver. There is no batch/config-file mode or custom resolver option yet.
- Unicode IDNs must be supplied in DNS A-label (
xn--...) form. - Public reputation DNS zones may rate-limit or block generic resolver usage.
- Provider profiles are operational defaults, not authoritative vendor APIs.
- Automated tests use local resolvers, protocol pipes, TLS servers, and HTTP stubs. An opt-in live integration suite is not included yet.