diff --git a/roles/mail/handlers/main.yml b/roles/mail/handlers/main.yml
index 6195f640d957eee41f893ed3c4cfca993d6aca08..7ef0864617f432d1762455e09d0e8963f648a387 100644
--- a/roles/mail/handlers/main.yml
+++ b/roles/mail/handlers/main.yml
@@ -76,3 +76,8 @@
   systemd:
     name: auth-server.service
     state: restarted
+
+- name: reload opendkim
+  systemd:
+    name: opendkim.service
+    state: restarted
diff --git a/roles/mail/tasks/frontend.yml b/roles/mail/tasks/frontend.yml
index 2a67cbec9851867e7d7bafa2da00daf1fcf24400..a3b748a73183d3f5308189f44fedd658f56a8c1d 100644
--- a/roles/mail/tasks/frontend.yml
+++ b/roles/mail/tasks/frontend.yml
@@ -9,6 +9,7 @@
     packages:
       - postfix-policyd-spf-python
       - policyd-rate-limit
+      - opendkim
 
 - set_fact:
     postfix_instances:
@@ -50,6 +51,34 @@
     dest: /etc/firewall/filter.d/20mail
   notify: "reload firewall"
 
+# Install opendkim
+
+- template:
+    src: opendkim.conf.j2
+    dest: /etc/opendkim.conf
+  notify: "reload opendkim"
+
+- file:
+    path: /etc/opendkim
+    state: directory
+
+- name: Install OpenDKIM key
+  copy:
+    src: "{{ credentials_dir }}/dkim/{{ dkim_selector }}.key"
+    dest: "/etc/opendkim/{{ dkim_selector }}.key
+  notify: "reload opendkim"
+
+- name: Create OpenDKIM key table
+  copy:
+    content: "% {{ dkim_selector }} /etc/opendkim/{{ dkim_selector }}.key\n"
+    dest: "/etc/opendkim/key-table"
+  notify: "reload opendkim"
+
+# This file is managed by ai-scripts, just ensure it exists.
+- file:
+    path: "/etc/opendkim/signing-table"
+    state: touch
+
 # Generate self-signed public certificates. This is only so dovecot
 # can start, certificates will be eventually replaced by ACME managed
 # ones.
diff --git a/roles/mail/templates/opendkim.conf.j2 b/roles/mail/templates/opendkim.conf.j2
new file mode 100644
index 0000000000000000000000000000000000000000..c210512b4652241311e5cc672f5424f92acd029d
--- /dev/null
+++ b/roles/mail/templates/opendkim.conf.j2
@@ -0,0 +1,22 @@
+# OpenDKIM configuration for the postfix-out instance.
+
+# Daemon operation.
+Syslog yes
+UMask 002
+AutoRestart no
+Background no
+DNSTimeout 5
+Socket local:/var/run/opendkim/opendkim.sock
+
+# DKIM configuration.
+Mode s
+Canonicalization relaxed/relaxed
+SignatureAlgorithm rsa-sha256
+SubDomains no
+X-Header no
+SendReports no
+RequiredHeaders yes
+
+# Tables used to read the list of domains to sign.
+KeyTable /etc/opendkim/key-table
+SigningTable /etc/opendkim/signing-table
diff --git a/roles/mail/templates/postfix-delivery/main.cf b/roles/mail/templates/postfix-delivery/main.cf
index 7832d7c5cd13929442cba584f09d6764b94491ef..962f9eb615229da1c4e9116b96209cb8cda4b56c 100644
--- a/roles/mail/templates/postfix-delivery/main.cf
+++ b/roles/mail/templates/postfix-delivery/main.cf
@@ -32,7 +32,7 @@ milter_default_action = tempfail
 virtual_transport = lmtp:unix:private/dovecot-lmtp
 
 # Recipient domains that are sent to virtual_transport.
-virtual_mailbox_domains = ${indexed}domains
+virtual_mailbox_domains = ${indexed}domains cdb:/etc/postfix/domains-auto
 
 # Aliases have already been resolved by the postfix-out instance.
 # The return value from the lookup is ignored, because we've set
diff --git a/roles/mail/templates/postfix-in/main.cf b/roles/mail/templates/postfix-in/main.cf
index 01287ca0dfec5b8a6cb593c08d4573c6aa832dfb..ac083b5e5c5487ca66e2d9cd1926f4370e66219c 100644
--- a/roles/mail/templates/postfix-in/main.cf
+++ b/roles/mail/templates/postfix-in/main.cf
@@ -83,7 +83,7 @@ postscreen_cache_map = lmdb:${data_directory}/postscreen_cache
 # Mailman lists are sent directly to the LMTP handlers, bypassing postfix-out,
 # because we want to be able to reject messages right away due to membership
 # failures etc.
-relay_domains = ${indexed}domains
+relay_domains = ${indexed}domains cdb:/etc/postfix/domains-auto
 relay_recipient_maps =
     ${indexed}virtual,
     ${ldap}recipients,
diff --git a/roles/mail/templates/postfix-out/main.cf b/roles/mail/templates/postfix-out/main.cf
index 3985d1415752e13ef1475b1e86812d378c4c469a..3e1b285984f905e595d71556f882451a6ab7f3d4 100644
--- a/roles/mail/templates/postfix-out/main.cf
+++ b/roles/mail/templates/postfix-out/main.cf
@@ -38,7 +38,7 @@ smtpd_timeout = 1200s
 # Use the "relay" transport for inbound mail, and the default
 # "smtp" transport for outbound mail (bounces, ...). The latter
 # won't starve the former of delivery agent slots.
-relay_domains = ${indexed}domains
+relay_domains = ${indexed}domains cdb:/etc/postfix/domains-auto
 relay_recipient_maps = ${ldap}recipients ${indexed}mailman_transport
 relay_destination_recipient_limit = 1
 
@@ -61,3 +61,6 @@ smtp_tls_security_level = may
 smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
 tls_random_source = dev:/dev/urandom
 smtp_tls_policy_maps = ${indexed}tls_policy
+
+# Process all messages through the opendkim milter.
+smtpd_milters = unix:/var/run/opendkim/opendkim.sock