mailcheck is a small command-line SMTP/DNS/TLS diagnostic tool written in Go. It checks whether a domain appears correctly configured to receive SMTP mail and can emit Nagios/Icinga-compatible output.
Checks
- MX records, sorted by preference
- implicit-MX fallback to A/AAAA when no MX exists
- null MX (RFC 7505) detection
- A and AAAA resolution
- IPv4/IPv6 selection
- TCP SMTP connectivity
- SMTP greeting and EHLO
- STARTTLS support and negotiation
- optional implicit TLS (
--implicit-tls, useful for port 465) - certificate parsing, hostname/PKIX verification, issuer, dates and expiry
- TLS 1.2/1.3 and modern AEAD cipher classification
- TLSA lookup and certificate/public-key matching for DANE-TA(2) and DANE-EE(3); PKIX usages 0 and 1 are unusable for SMTP (RFC 7672)
- DNSSEC indication using the AD bit returned by the selected recursive resolver
- SPF record discovery and basic syntax/lookup-limit checks
- DMARC record parsing and policy checks, with organizational-domain fallback
- DKIM selector checks, including RSA and Ed25519 public-key validation
- human, JSON and Nagios/Icinga output
Build
Requires Go 1.24 or newer.
go build -o mailcheck .
For a Linux install:
install -m 0755 mailcheck /usr/local/bin/mailcheck
Examples
./mailcheck example.com
./mailcheck example.com --show-expiry
./mailcheck example.com --ipv4
./mailcheck example.com --ipv6
./mailcheck example.com --host mail.example.net
./mailcheck example.com --host mail.example.net --address 192.0.2.25
./mailcheck example.com --dkim selector1,selector2
./mailcheck example.com --json
./mailcheck example.com --nagios
./mailcheck example.com --port 465 --implicit-tls
./mailcheck example.com --dns 1.1.1.1
When --host is supplied, that hostname is used for TLS SNI/name validation and TLSA lookup. --address controls the TCP destination without changing the TLS hostname. If only --address is supplied, it selects which MX address to test, and the check fails if no MX host has that address.
Nagios exit status
0 OK
1 WARNING
2 CRITICAL
3 UNKNOWN
The overall status is the most severe result, ranked CRITICAL, WARNING, UNKNOWN, OK. Invalid options exit with UNKNOWN.
Example:
MAILCHECK WARNING - SPF=OK (valid SPF record (1 DNS-lookup mechanism(s))); DMARC=OK (policy=reject); mx.example.net./192.0.2.25 SMTP=OK (SMTP and STARTTLS available) TLS=OK (TLS1.3 / TLS_AES_128_GCM_SHA256) TLSA=WARNING (no TLSA record) | duration_ms=142
DNSSEC / DANE note
The program requests EDNS DNSSEC data and reports DNSSEC as secure when the recursive resolver returns the DNS AD (Authenticated Data) bit. It does not implement a complete DNSSEC validator itself. For production DANE monitoring, point --dns at a validating recursive resolver you trust.
TLSA matching implements the TLSA selector and matching-type operations for usages 2 and 3. TLSA records are only treated as DNSSEC-secure when the MX lookup was secure too (RFC 7672 2.2.1). The result should be read together with the DNSSEC status: a matching TLSA record obtained without DNSSEC validation is not equivalent to authenticated DANE.
SPF / DKIM / DMARC scope
These checks are deliberately DNS-policy diagnostics, not a complete mail-authentication implementation.
SPF checks the record syntax, multiple-record condition, terminal policy, and the ten-DNS-lookup limit at the top level. It does not simulate every possible sender IP through a complete recursive SPF evaluation.
DKIM requires selectors because DNS does not provide a universal way to enumerate all selectors. Example:
./mailcheck example.com --dkim selector1,google
The DKIM check validates the public-key encoding and RSA key size / Ed25519 key length, but it does not verify a signed message.
DMARC validates the record and key policy tags but does not evaluate an actual message's SPF/DKIM alignment. If the domain has no DMARC record, the organizational domain's record is used (RFC 7489 section 6.6.3), with its sp policy if present.
Security / operational considerations
The SMTP test performs a real TCP connection to the target's port 25 (or the explicitly selected port). It does not send mail or authenticate. With --implicit-tls, the greeting and EHLO are checked inside TLS.
TLS certificate verification is performed separately from the TLS handshake so that a certificate that is not publicly trusted can still be inspected and compared with a DANE TLSA record. This is intentional for SMTP/DANE diagnostics.
The default TLS client minimum is TLS 1.2. A server that only supports TLS 1.0/1.1 will therefore fail the modern TLS check rather than being silently accepted.
Suggested future extensions
- full recursive SPF evaluator with RFC lookup accounting
- complete DNSSEC chain validation instead of relying on resolver AD
- more exact RFC 7672 DANE policy evaluation
- TLS version capability matrix (separate probes for TLS 1.2 and 1.3 and optional legacy probes)
- SMTP MTA-STS policy retrieval/validation
- TLS-RPT record checks
- CAA checks
- PTR / forward-confirmed reverse DNS diagnostics
- SMTP banner hostname diagnostics
- optional concurrency and retry controls
DNS implementation
DNS queries are implemented using the upstream github.com/miekg/dns library (v1.1.70). It provides native RR parsing, UDP/TCP transport, EDNS0 and DNSSEC-related support.
The tool requests DNSSEC data with the EDNS DO bit and reports the resolver's Authenticated Data (AD) bit separately. An AD=true result means the configured recursive resolver authenticated the response; it does not mean mailcheck itself is acting as a validating recursive resolver.
--timeout applies to each DNS query as well as to the SMTP connection.
DKIM selectors
--dkim accepts either selectors:
mailcheck example.com --dkim selector1,selector2
or complete owner names:
mailcheck example.com --dkim selector1._domainkey.example.com.,selecto2._domainkey.example.com.
The latter are queried exactly as supplied (apart from normalizing the trailing DNS root dot).
Command-line option order
Both forms are accepted:
mailcheck example.com --json --dkim selector1
mailcheck --json --dkim selector1 example.com
SMTP/TLS diagnostics
The SMTP probe reports the connection as a sequence of stages: TCP, SMTP greeting, EHLO, STARTTLS, and TLS. A failure at an earlier stage does not mark later stages as failures; they are reported as UNKNOWN because they could not be tested.
Use --debug to print the SMTP/TLS exchange diagnostics to stderr without putting protocol chatter into JSON or Nagios stdout:
mailcheck --debug --host mail.example.net example.com
For example, a TCP timeout is reported as SMTP=CRITICAL while TLS and TLSA remain UNKNOWN (unless DANE applies, see below), with the underlying socket error included in the summary.
TLSA is reported separately as:
WARNINGwhen TLS succeeds but no TLSA record exists or the TLSA RRset is not DNSSEC-authenticated.WARNINGwhen a DNSSEC-backed TLSA RRset has no record usable for SMTP.CRITICALwhen a DNSSEC-backed TLSA RRset exists but does not match the negotiated certificate.CRITICALwhen a DNSSEC-backed TLSA RRset exists but TLS was not negotiated, since DANE senders will not deliver without TLS.UNKNOWNwhen TLS was not negotiated and DANE does not apply.
This distinction prevents a failed TCP connection from being reported as a TLS certificate failure.