NAME

Mail::DKIM2 - DKIM2 signing and verification for email

SYNOPSIS

use Mail::DKIM2;

# Sign: record the message in a Message-Instance (an originating hop
# adds m=1; a hop that changed a message adds the next m= with a Recipe,
# see Mail::DKIM2::MessageInstance), then add a DKIM2-Signature over it.
my $mi = Mail::DKIM2::MessageInstance->calculate($message);
$message = Mail::DKIM2::Common::fold_header('Message-Instance: ' . $mi->as_string)
         . "\r\n" . $message;
my $signer = Mail::DKIM2::Signer->new(
    Domain   => 'example.com',
    Selector => 'sel1',
    KeyFile  => '/etc/dkim2/sel1.pem',
    MailFrom => '<sender@example.com>',
    RcptTo   => ['<recipient@example.net>'],
)->load($message);
die $signer->result_detail unless $signer->result eq 'signed';
my $header = $signer->as_string;   # "DKIM2-Signature: i=1; ..."

# Verify: check every signature in the chain and the Message-Instance
# chain beneath it.
my $verifier = Mail::DKIM2::Verifier->new->load($message);
print $verifier->result_detail, "\n";   # pass (i=1..2 verified)

# Streaming, for a milter or other filter that sees the message in
# pieces (CRLF line endings); one object per message:
my $v = Mail::DKIM2::Verifier->new;
$v->PRINT($chunk) for @chunks;
$v->CLOSE;

DESCRIPTION

DKIM2 is a successor to DKIM in which every hop that handles a message signs it, each signature covers all the signatures before it, and a Message-Instance header records hashes of the message at each hop together with a Recipe for undoing that hop's changes. A recipient can therefore tell who handled a message, in what order, and what each of them changed.

This distribution implements signing and verification for draft-ietf-dkim-dkim2-spec-06 (see "STATUS"). The modules:

Mail::DKIM2::Signer

Adds a DKIM2-Signature header for this hop.

Mail::DKIM2::Verifier

Verifies the chain of DKIM2-Signature headers and the Message-Instance chain, reporting pass, fail, none, permerror or temperror.

Mail::DKIM2::MessageInstance

Computes, verifies and undoes Message-Instance headers, including the Recipes that describe a hop's changes.

Mail::DKIM2::Signature

Parses and builds one DKIM2-Signature header.

Mail::DKIM2::DSN

Generates, authenticates and propagates DKIM2-signed Delivery Status Notifications (spec-06 section 12).

Mail::DKIM2::Common

Canonicalization, hashing, folding and key-loading functions shared by the above.

Mail::DKIM2::HeaderParser, Mail::DKIM2::TagValueList

The streaming message parser the Signer and Verifier are built on, and the tag=value list a DKIM2-Signature is built on.

Mail::DKIM2::MessageStore, Mail::DKIM2::Reflector, Mail::DKIM2::Validate and Mail::DKIM2::Split support the authentication_milter handlers (Mail::Milter::Authentication::Handler::DKIM2Sign, Mail::Milter::Authentication::Handler::DKIM2Verify) and the dkim2.com demonstration server, and are not needed to sign or verify mail.

The command-line tools dkim2sign and dkim2verify sign and verify a message from a file or standard input.

CONVENTIONS

The whole distribution follows these rules; each module's documentation assumes them.

Options and methods

Constructor options and the options of class methods are CamelCase (SkipTimestampCheck, IgnorePrefixes). Methods are snake_case (skip_timestamp_check). Where an option can also be set after construction, the method has the same name as the option in snake_case and acts as a getter with an optional setter argument (a code-reference option has a set_ method instead). A constructor refuses an option it does not know, so a misspelling is an error rather than a silently ignored setting.

Feeding a message

The Signer and Verifier are streaming parsers. PRINT($bytes) feeds any amount of the message, in chunks of any size, and CLOSE() finishes it. Line endings must be CRLF, as they are on the wire; every hash in DKIM2 is defined over CRLF text. load($input) is the one-shot form: it takes the message as a string, a reference to one, a filehandle or an Email::MIME, normalises bare LF to CRLF, and calls PRINT then CLOSE. A Signer or Verifier can also be tied to a filehandle, which routes print and close to PRINT and CLOSE.

A Signer or Verifier object handles one message. Make a new one for the next.

Results and errors

A mistake in how the library is called, such as a missing required option or an unknown one, is reported by croak from the call that made it.

The outcome of processing a message is never an exception. The Verifier reports it through result() (one of pass, fail, none, permerror, temperror) and details() (the reason); the Signer through result() (signed or fail) and details(). Both have a result_detail() combining the two for display. PRINT and CLOSE return normally whatever the verdict. The Mail::DKIM2::MessageInstance class methods follow the same split: verify and chain_verifies return a status, while calculate and undo die when the message they are handed cannot be processed, which a caller treats as a configuration error.

The library only ever dies with a plain string. Any exception that is a reference, such as an object a milter framework throws to signal a timeout, passes through every eval in the library untouched and reaches the host.

Public keys

The Verifier fetches public keys from DNS through a Net::DNS::Resolver (the Resolver option, created on demand if not given). A PubkeyCallback replaces the lookup entirely, and is handed the verifier so it can fall back to the standard fetch for keys it does not know. A DNS failure that is not a definite "no such record" is a temperror.

Operator-local header fields

Fields an operator's own systems add after a message is signed and strip before it leaves are named by prefix with IgnorePrefixes, on a Verifier and on each Mail::DKIM2::MessageInstance call. They are local policy and are never process-wide state.

STATUS

This implements draft-ietf-dkim-dkim2-spec-06, an Internet-Draft that is still changing. The wire format follows the draft, so a message signed by this version may not verify with a version tracking a later draft, and the other way round. It is in production use: Fastmail signs outbound mail with it, and the dkim2.com interoperability server runs it. The API is at 0.x and may still change in incompatible ways between releases; the Changes file records every such change.

SEE ALSO

https://github.com/dkim2wg/interop/blob/master/docs/dkim2-postfix-list-host-guide.md, the guide to running a Postfix mailing-list host with this distribution, Mailman 3 or Sympa.

https://datatracker.ietf.org/doc/draft-ietf-dkim-dkim2-spec/, https://github.com/dkim2wg/interop, https://dkim2.com/, Mail::DKIM.

AUTHOR

Bron Gondwana <brong@fastmailteam.com>

COPYRIGHT AND LICENSE

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