NAME

dkim2-milter - Standalone DKIM2 milter for Postfix

SYNOPSIS

# Verify only (no signing)
dkim2-milter --socket unix:/var/run/dkim2.sock

# Sign for a single domain
dkim2-milter --socket unix:/var/run/dkim2.sock \
    --domain example.com \
    --selector sel1 \
    --keyfile /etc/dkim2/sel1.pem

# Sign for any domain with keys in a directory
dkim2-milter --socket unix:/var/run/dkim2.sock \
    --keydir /etc/dkim2/keys

# With message snapshots for diff-based Message-Instance
dkim2-milter --socket unix:/var/run/dkim2.sock \
    --keydir /etc/dkim2/keys \
    --snapshot-dir /var/spool/dkim2/snapshots

# Epilogue-based MI (body in MIME epilogue, compact header)
dkim2-milter --socket unix:/var/run/dkim2.sock \
    --keydir /etc/dkim2/keys \
    --snapshot-dir /var/spool/dkim2/snapshots \
    --use-epilogue

# Auto-switch to epilogue when diff exceeds 5 literal lines
dkim2-milter --socket unix:/var/run/dkim2.sock \
    --keydir /etc/dkim2/keys \
    --snapshot-dir /var/spool/dkim2/snapshots \
    --epilogue-threshold 5

# Testing with dns.json instead of real DNS
dkim2-milter --socket inet:8891@localhost \
    --keydir keys \
    --dns-json dns.json

DESCRIPTION

A standalone milter daemon that provides DKIM2 signature verification and signing for Postfix (or any milter-compatible MTA). Uses Sendmail::PMilter for the milter protocol.

Verification is enabled by default for all inbound messages.

Signing can be configured in two ways:

Single domain: --domain, --selector, and --keyfile. Signs only when the envelope sender matches the configured domain.
Key directory: --keydir PATH. The directory contains subdirectories named by domain, each containing key files named selector.key. The milter looks up the sender domain (with parent domain fallback) and uses the first key found. The algorithm is auto-detected from the key type (RSA or Ed25519).

This program implements draft-ietf-dkim-dkim2-spec-06; see "STATUS" in Mail::DKIM2.

KEY DIRECTORY LAYOUT

The --keydir option points to a directory tree where each subdirectory is named after a domain and contains one or more PEM key files with a .key extension. The filename (minus the extension) becomes the DKIM selector.

/etc/dkim2/keys/
    example.com/
        sel1.key              # RSA key, selector "sel1"
        sel2.key              # another key (used if newer)
    sub.example.com/
        default.key           # selector "default"
    other.org/
        ed25519.key           # Ed25519 key, auto-detected

Domain lookup

When a message arrives from user@sub.example.com, the milter:

  1. Looks for a directory named sub.example.com/ under the keydir.

  2. If not found, strips the leftmost label and tries example.com/.

  3. Continues stripping labels until a match is found or no labels remain.

The signing domain (d= tag) is set to the directory name that matched, not necessarily the full sender domain.

Key selection

If a domain directory contains multiple .key files, the newest file by ctime is used. This makes key rotation straightforward:

# Generate a new key and publish its DNS record
openssl genrsa -out /etc/dkim2/keys/example.com/sel2.key 2048

# The milter will start using sel2 for new signatures immediately
# (after the domain lookup cache is refreshed on next restart).
# Keep sel1.key around until its DNS record has propagated.

Algorithm detection

The signing algorithm is auto-detected from each key file:

  • Ed25519 keys (32-byte raw or PEM-wrapped) use the ed25519 algorithm.

  • All other keys (RSA) use rsa-sha256.

No --algorithm flag is needed in keydir mode.

Caching

Domain-to-key mappings are cached in memory for the lifetime of the milter process. After adding or removing key files, restart the milter to pick up the changes.

OPTIONS

--socket, -s SPEC

Milter socket specification. Default: unix:/var/run/dkim2-milter.sock. Examples: unix:/path/to/sock, inet:8891@localhost.

--domain, -d DOMAIN

Signing domain (single-domain mode). Requires --selector and --keyfile.

--selector SELECTOR

DKIM selector (single-domain mode). Requires --domain and --keyfile.

--keyfile, -k PATH

Path to a PEM-encoded private key (single-domain mode).

--keydir PATH

Directory of per-domain key subdirectories (multi-domain mode). Cannot be combined with --domain/--selector/--keyfile.

--algorithm, -a ALG

Signing algorithm for single-domain mode. Default: rsa-sha256. In keydir mode, the algorithm is auto-detected from each key file.

--snapshot-dir PATH

Directory for message snapshots. Enables diff-based Message-Instance computation: on inbound, a snapshot is stored keyed by the MI header value; on outbound, the snapshot is used to compute recipes describing changes.

--use-epilogue

When computing a diff-based MI, always store the previous message body in the MIME epilogue rather than as inline diff lines. This keeps MI headers small regardless of how much the body changed, at the cost of increasing the transmitted message size. Cannot be combined with --epilogue-threshold.

--epilogue-threshold N

When computing a diff-based MI, switch to epilogue storage only when the diff recipe would contain more than N literal (non-range) lines. Small changes (<= N lines) stay as a compact inline diff; large changes fall back to epilogue. 5 is a reasonable starting value. Cannot be combined with --use-epilogue.

--dns-json PATH

Path to a JSON file with DNS key records (for testing without real DNS). Format: { "domain": { "selector._domainkey": [["txt", "v=DKIM1; ..."]] } }.

--verify, --no-verify

Enable or disable verification. Default: enabled.

--sign, --no-sign

Enable or disable signing. Default: enabled if signing config is available.

--help, -h

Show this help message.

POSTFIX CONFIGURATION

Add to main.cf:

# For inbound verification
smtpd_milters = unix:/var/run/dkim2-milter.sock

# For outbound signing (locally-generated mail)
non_smtpd_milters = unix:/var/run/dkim2-milter.sock

# Milter default action if the milter is unavailable
milter_default_action = accept

DEPENDENCIES

Requires Sendmail::PMilter 1.28 or later, a recommended (not required) dependency of the Mail-DKIM2 distribution:

cpanm Sendmail::PMilter

1.28 (July 2026) is the first release that answers a null-sender MAIL FROM:<>; with 1.27 the MTA waited out its milter timeout and bounces went out unsigned. This program refuses to start with an older version. deploy/smoke-null-sender-milter.pl in the interop repository checks a running milter answers a null sender.

AUTHOR

Bron Gondwana <brong@fastmailteam.com>

COPYRIGHT AND LICENSE

Copyright (c) 2025 Fastmail Pty Ltd. This is free software; you can redistribute it and/or modify it under the same terms as Perl itself.