mailcheck

· Sig-I/O Forgejo

6 min read Original article ↗

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:

  • WARNING when TLS succeeds but no TLSA record exists or the TLSA RRset is not DNSSEC-authenticated.
  • WARNING when a DNSSEC-backed TLSA RRset has no record usable for SMTP.
  • CRITICAL when a DNSSEC-backed TLSA RRset exists but does not match the negotiated certificate.
  • CRITICAL when a DNSSEC-backed TLSA RRset exists but TLS was not negotiated, since DANE senders will not deliver without TLS.
  • UNKNOWN when TLS was not negotiated and DANE does not apply.

This distinction prevents a failed TCP connection from being reported as a TLS certificate failure.