mail.dkim
import mail
mail.dkim, soimport mailis enough and the names are called asmail.dkim.*.import mail.dkimreaches the same definitions directly.
DomainKeys Identified Mail: signing outgoing messages so a receiver can tell they came from the domain they claim to, and checking the signatures on incoming ones.
A signature covers the body and a chosen set of headers. The public key lives in the domain’s own DNS, under the selector the signature names, which is what ties the message to the domain.
import mail
import mail.dkim { Signer }
var signer = Signer('example.com', 'default', private_key)
signer.sign(note)
mail.send('smtp://mail.example.com', note)
Verification looks the key up through net.resolver unless handed
something else to look it up with:
import mail.dkim
for result in dkim.verify(incoming) {
echo '${result.domain}: ${result.valid ? 'ok' : result.reason}'
}
What is covered
Both canonicalisations, simple and relaxed, on headers and on
bodies, and both signing algorithms in use: rsa-sha256 and the
ed25519-sha256 of RFC 8463.
A signature says nothing about who wrote the message, only that the domain that signed it takes responsibility for it. Deciding what a valid signature from a given domain is worth is a policy question, and the answer is usually DMARC.
Constants
ALGORITHMS
mail.dkim.ALGORITHMS: list = [...]
The signing algorithms this implements.
CANONICALISATIONS
mail.dkim.CANONICALISATIONS: list = [...]
The canonicalisations this implements, on either headers or body.
simple covers the bytes as they are, which means any change at all on
the way breaks the signature. relaxed forgives the whitespace and
folding changes a mail server may make in passing, and is what almost
everything uses.
DEFAULT_HEADERS
mail.dkim.DEFAULT_HEADERS: list = [...]
The headers signed when the signer is not told which to sign.
A header that is not in the message is skipped rather than signed as empty, and one that is listed twice is signed twice, which is how a signature says “there was exactly one of these”.
Functions
canonicalise_body()
mail.dkim.canonicalise_body(raw, method: string) -> bytes
Canonicalises a body.
Parameters
raw(string|bytes)method(string) —simpleorrelaxed.
Returns bytes
Raises DkimError if method is neither.
canonicalise_header()
mail.dkim.canonicalise_header(name: string, value: string, method: string)
Canonicalises one header.
Parameters
name(string)value(string) — The value as it was written, folding and all.method(string) —simpleorrelaxed.
Returns — string, without the line break that ends it.
Raises DkimError if method is neither.
public_key_pem()
mail.dkim.public_key_pem(encoded: string, algorithm: string) -> string
Turns the p= value of a key record into a PEM the crypto module will
accept.
An RSA key is published as the whole SubjectPublicKeyInfo structure and only needs wrapping. An Ed25519 key is published as the 32 raw bytes, and has that structure put around it here.
Parameters
encoded(string) — The base64 from the record’sp=tag.algorithm(string) — One ofALGORITHMS.
Returns string
Raises DkimError if the key will not decode.
verify()
mail.dkim.verify(message, lookup) -> list
Checks every signature on a message.
One result comes back per DKIM-Signature header, in the order they
appear, whether it passed or not. A message with no signatures gives an
empty list, which is not a failure: it is a message nobody signed.
import mail.dkim
for result in dkim.verify(incoming) {
echo '${result.domain}: ${result.valid ? 'pass' : result.reason}'
}
Parameters
message(Message)lookup(?function) — Given a name, returns the text records published under it.net.resolveris used when not given.
Returns list — of Result
Note: A message that was parsed is checked against the bytes it was parsed from, which is the only thing a signature can be checked against. Changing a parsed message and checking it again reports on the message that arrived, not the one now in hand.
is_signed()
mail.dkim.is_signed(message, lookup) -> bool
Whether a message carries at least one signature that checks out.
Parameters
message(Message)lookup(?function)
Returns bool
Classes
DkimError
class mail.dkim.DkimError < MailError
Raised when a signature cannot be built: an algorithm this does not implement, a key that will not parse, a canonicalisation that is not one of the two.
- printable — has a
@to_string(), soechoandprint()show something useful
DkimError.to_string()
mail.dkim.DkimError.to_string()
Signature
class mail.dkim.Signature
One DKIM-Signature header, read into its parts.
- printable — has a
@to_string(), soechoandprint()show something useful
Constructor
mail.dkim.Signature(tags: dict, raw: ?string)
Parameters
tags(dict) — The tags of the header, by name.raw(?string) — The header value it was read from.
Signature.parse()
mail.dkim.Signature.parse(value: string) -> Signature
Reads a DKIM-Signature header value.
Parameters
value(string)
Returns Signature
Signature.record_name()
mail.dkim.Signature.record_name() -> string
The name the public key is published under.
Returns string
Signature.to_string()
mail.dkim.Signature.to_string()
Signer
class mail.dkim.Signer
Signs outgoing messages for one domain with one key.
Build one and keep it: a signer holds no connection and no state beyond its key, and signing is the only thing it does.
- printable — has a
@to_string(), soechoandprint()show something useful
Constructor
mail.dkim.Signer(domain: string, selector: string, private_key: string, options: ?dict)
| option | default | what it does |
|---|---|---|
algorithm | rsa-sha256 | or ed25519-sha256 |
canonicalisation | relaxed/relaxed | headers then body |
headers | DEFAULT_HEADERS | which headers to cover |
identity | none | the i= tag, an address within the domain |
expires_in | none | seconds until the signature stops counting |
timestamp | true | whether to record when it was signed |
Parameters
domain(string) — The domain taking responsibility.selector(string) — Which of the domain’s keys this is.private_key(string) — The key, PEM encoded.options(?dict)
Raises DkimError if an option names something not implemented.
Signer.sign()
mail.dkim.Signer.sign(message) -> string
Signs a message, adding the DKIM-Signature header to the top of it.
The message is signed as it stands, so everything else about it has to be settled first. Changing a signed header afterwards breaks the signature.
Parameters
message(Message)
Returns string — the header value that was added.
Raises DkimError if the key will not sign.
Signer.to_string()
mail.dkim.Signer.to_string()
Result
class mail.dkim.Result
What checking one signature came to.
- printable — has a
@to_string(), soechoandprint()show something useful
Constructor
mail.dkim.Result(signature, valid: bool, reason: ?string)
Parameters
signature(Signature) — The signature that was checked.valid(bool)reason(?string) — Why not, when it is not valid.
Result.to_string()
mail.dkim.Result.to_string()
2026, Richard Ore and Zuri contributors