Signing all outbound email with OpenDKIM: installing the milter, generating keys with opendkim-genkey, publishing the DNS record, configuring Postfix to use the milter, understanding SPF alignment and DKIM alignment, then escalating the DMARC policy from none to reject.

Episode 5 prepared the SPF, DKIM, and DMARC records in DNS. Now we bring the active side to life: signing every outbound email with OpenDKIM. Without signatures, the DKIM record is just an unused key — and DMARC can't pass alignment.
This episode installs OpenDKIM as a milter, generates and publishes the keys, connects it to Postfix, then ties everything together with SPF/DKIM alignment and a DMARC policy escalated step by step to reject.
DKIM proves that an email really came from a server that controls the domain. How it works: the private key signs the headers and body; the public key is published in DNS; the recipient verifies and checks whether the signature matches.
The consequences are significant: mailbox providers use DKIM as proof — not just a "claim" — of who the sender is. In the 2026 era, email without DKIM is considered riskier, especially with the tightening trend of DMARC enforcement.
Install OpenDKIM along with its supporting packages:
sudo apt install -y opendkim opendkim-toolsCreate the key directory and its owner:
sudo mkdir -p /etc/opendkim/keys/example.com
sudo chown -R opendkim:opendkim /etc/opendkimThe main configuration /etc/opendkim.conf — the important parts:
Mode sv
Syslog yes
Socket inet:8891@localhost
KeyTable /etc/opendkim/key.table
SigningTable refile:/etc/opendkim/signing.table
InternalHosts /etc/opendkim/trusted.hosts
Canonicalization relaxed/simple
Selector mailMode sv — sign and verify.Socket inet:8891 — the milter listens on localhost port 8891.KeyTable / SigningTable — map domains to keys.Selector mail — the selector name, which must match what's published in DNS.Fill in the three supporting files:
# key.table
example.com example.com:mail:/etc/opendkim/keys/example.com/mail.private
# signing.table
*@example.com example.com
# trusted.hosts
localhost
mail.example.comGenerate the key pair with opendkim-genkey:
sudo opendkim-genkey -D /etc/opendkim/keys/example.com -d example.com -s mailThe command above produces mail.private (the private key) and mail.txt (the DNS-ready public key). The private key must be protected with strict ownership and permissions, then look at the public key to publish it as a TXT record at mail._domainkey.example.com:
sudo chown opendkim:opendkim /etc/opendkim/keys/example.com/mail.private
sudo chmod 600 /etc/opendkim/keys/example.com/mail.private
cat /etc/opendkim/keys/example.com/mail.txtThe published record looks like v=DKIM1; k=rsa; p=MIGfMA.... Verify after DNS propagates:
dig TXT mail._domainkey.example.com +shortPostfix uses a milter to call OpenDKIM in the middle of the email flow. Enable it in main.cf:
sudo postconf -e 'milter_default_action = accept'
sudo postconf -e 'milter_protocol = 6'
sudo postconf -e 'smtpd_milters = inet:localhost:8891'
sudo postconf -e 'non_smtpd_milters = inet:localhost:8891'
sudo postfix reloadmilter_default_action = accept — if the milter has a problem, don't reject the email (graceful).milter_protocol = 6 — the latest milter protocol version.smtpd_milters — the milter for email passing through smtpd.Start OpenDKIM and check:
sudo systemctl enable --now opendkim
sudo journalctl -u opendkim --since "1 minute ago"Send a test email and check for the signature in the headers:
echo "dkim test" | mail -s "DKIM check" admin@example.com
grep -i "dkim-signature" /var/mail/vhosts/example.com/admin/Maildir/new/*If the DKIM-Signature header appears, signing works. The b= and bh= lines are the signature and body hash.
DMARC checks alignment: does the domain visible in the From: header align with the domain that passed SPF or DKIM?
Return-Path) or header must match the From: domain. strict mode requires an exact match; relaxed allows subdomains.d= of the DKIM signature must match the From: domain.DMARC passes if either one of them aligns. This is why signing all outbound email with the same domain as From: is key — you fully control DKIM alignment, while SPF alignment can be disrupted by third-party relays.
Now assemble a complete DMARC record. An example for production:
_dmarc.example.com. IN TXT "v=DMARC1; p=reject; sp=reject; adkim=s; aspf=r; pct=100; rua=mailto:dmarc@example.com; ruf=mailto:forensic@example.com"Let's break down each tag:
p=reject — the main policy for this domain.sp=reject — the policy for subdomains.adkim=s — DKIM alignment strict.aspf=r — SPF alignment relaxed.pct=100 — applied to 100% of email.rua / ruf — the destinations for aggregate and forensic reports.Don't jump straight to reject. Escalate gradually with report data as your guide:
p=none — monitor the rua reports for a few weeks.p=quarantine once all legitimate email passes alignment.p=reject once you're confident no legitimate email will be rejected.Verify the published record:
dig TXT _dmarc.example.com +shortThe habit of checking verification results in the OpenDKIM logs reveals problems early — monitor in real time with journalctl -fu opendkim in a separate terminal while sending a test email:
sudo journalctl -u opendkim --since "1 hour ago" | grep -E "dkim_verify|dkim=pass|dkim=fail"When sending to Gmail, check the Authentication-Results header on the received email — that line shows dkim=pass and spf=pass from the recipient's point of view. That's the most convincing proof that your identity passes verification.
Episode 17 is done. Key takeaways:
smtpd_milters = inet:localhost:8891 connects Postfix to OpenDKIM.From: domain secures DKIM alignment.none → quarantine → reject, monitored via rua reports.Email identity is solid. In episode 18 we secure the protocol itself: SMTP Smuggling & Protocol Security — closing command smuggling gaps and requiring TLS between servers via MTA-STS and DANE. See you in episode 18!