diff --git a/postfix/defaults/main.yml b/postfix/defaults/main.yml
index 98ec6718fbcc74c1ee2080f9ecb79482ae8e52db..c83309f5ccd74a467436e80ba6638ae320d58905 100644
--- a/postfix/defaults/main.yml
+++ b/postfix/defaults/main.yml
@@ -75,11 +75,18 @@ postfix_verify_spf_testmode: true
 postfix_enable_srs: false
 # Note: This requires at least buster-backports or newer.
 postfix_enable_mta_sts: false
+postfix_enable_dkim: false
 
 postfix_my_networks: []
 postfix_notify_classes: []
 postfix_satellite_only: false
 
+postfix_dkim_internal_hosts:
+  - "127.0.0.1"
+  - "::1"
+  - "localhost."
+  - "{{ ansible_fqdn }}"
+
 ## sane defaults for postfix satellites
 #
 # postfix_satellite_only: true
diff --git a/postfix/handlers/main.yml b/postfix/handlers/main.yml
index 3e156d74a5f75dcb92afbbb8c65488197d014a05..519529d222e57886bb27b1707fdc0d9c856b590a 100644
--- a/postfix/handlers/main.yml
+++ b/postfix/handlers/main.yml
@@ -23,3 +23,6 @@
 
 - name: postmap transport
   command: postmap cdb:/etc/postfix/transport
+
+- name: restart dkimpy-milter
+  service: name=dkimpy-milter state=restarted
diff --git a/postfix/tasks/dkim.yml b/postfix/tasks/dkim.yml
index 50f2fba933c44a2d17cdcd823d55aa5a6a4db827..5b7080d7807edf3dd9093148c98ccae246230904 100644
--- a/postfix/tasks/dkim.yml
+++ b/postfix/tasks/dkim.yml
@@ -1,55 +1,61 @@
---- 
+---
 
-- name: ensure we have opendkim
+- name: ensure we have dkimpy-milter
   apt:
     name:
-      - opendkim
-      - opendkim-tools
-    state: present
-
+      - dkimpy-milter
+    state: "{{ 'present' if postfix_enable_dkim else 'absent' }}"
+  tags:
+    - mail
+    - postfix
 
 - name: ensure we have keys for any domain
   command:
-    cmd: "opendkim-genkey  --directory=/etc/dkimkeys --domain={{ item }} --selector={{ item }}-{{ dkim_selector }}   --nosubdomains"
-    creates: 
-      - "/etc/dkimkeys/{{ item }}-{{ dkim_selector }}.private"
-      - "/etc/dkimkeys/{{ item }}-{{ dkim_selector }}.txt"
-  become: yes
-  become_user: opendkim
-  loop: "{{  postfix_domains +  postfix_virtual_domains }}"
-
-
-- name: ensure we have a folder for systemd overrides
-  file:
-    state: directory
-    path: "/etc/systemd/system/opendkim.service.d/"
-    mode: '0755'
-    owner: root
-    group: root
-
-- name: ensure we run the service without root
-  copy: 
-    src: "opendkim-systemd-service-override.conf"
-    dest: "/etc/systemd/system/opendkim.service.d/override.conf"
-
+    cmd: "dknewkey {{ ansible_hostname }}-{{ item }}-{{ dkim_selector }}"
+    # yamllint disable-line rule:line-length
+    creates: "/etc/dkimpy-milter/{{ ansible_hostname }}-{{ item }}-{{ dkim_selector }}.*"
+    chdir: /etc/dkimpy-milter
+  loop: "{{ (postfix_domains + postfix_virtual_domains)|unique }}"
+  when: postfix_enable_dkim
+  tags:
+    - mail
+    - postfix
 
 - name: ensure we have our config
   template:
-    src: "opendkim.conf.j2"
-    dest: "/etc/opendkim.conf"
+    src: "dkimpy-milter.conf.j2"
+    dest: "/etc/dkimpy-milter/dkimpy-milter.conf"
     owner: root
     group: root
     mode: '0644'
+  notify: restart dkimpy-milter
+  when: postfix_enable_dkim
+  tags:
+    - mail
+    - postfix
 
-
-- name: ensure we have the key table
+- name: ensure we have the key and sigining table
   template:
-    src: "opendkim-{{ item }}.j2"
-    dest: "/etc/dkimkeys/{{ item }}"
+    src: "dkimpy-milter-{{ item }}.j2"
+    dest: "/etc/dkimpy-milter/{{ item }}"
     owner: root
     group: root
     mode: '0644'
   loop:
     - "keytable"
     - "signingtable"
-    - "trustedhosts"
+  notify: restart dkimpy-milter
+  when: postfix_enable_dkim
+  tags:
+    - mail
+    - postfix
+
+- name: ensure service is enabled and running
+  service:
+    name: dkimpy-milter
+    state: started
+    enabled: true
+  when: postfix_enable_dkim
+  tags:
+    - mail
+    - postfix
diff --git a/postfix/tasks/main.yml b/postfix/tasks/main.yml
index cfc6d666514e79bc9e7e250435650b513b2278d6..55255cb5b410e03dbf77b3e810acc6268b4320d7 100644
--- a/postfix/tasks/main.yml
+++ b/postfix/tasks/main.yml
@@ -106,6 +106,7 @@
 - import_tasks: mta-sts.yml
 - import_tasks: spf.yml
 - import_tasks: srs.yml
+- import_tasks: dkim.yml
 
 - name: install rt-mailgate if needed
   apt:
diff --git a/postfix/templates/dkimpy-milter-keytable.j2 b/postfix/templates/dkimpy-milter-keytable.j2
new file mode 100644
index 0000000000000000000000000000000000000000..59afb7196340b5590ddb75e80c73c47449a6cf3c
--- /dev/null
+++ b/postfix/templates/dkimpy-milter-keytable.j2
@@ -0,0 +1,3 @@
+{% for domain in (postfix_domains + postfix_virtual_domains)|unique %}
+{{ domain }}	{{ domain }}:{{ ansible_hostname }}-{{ domain }}-{{ dkim_selector }}:/etc/dkimpy-milter/{{ ansible_hostname }}-{{ domain }}-{{ dkim_selector }}.key
+{% endfor %}
diff --git a/postfix/templates/dkimpy-milter-signingtable.j2 b/postfix/templates/dkimpy-milter-signingtable.j2
new file mode 100644
index 0000000000000000000000000000000000000000..ab52f8a8f266a17b4b24d73a22635b3c6ea71b90
--- /dev/null
+++ b/postfix/templates/dkimpy-milter-signingtable.j2
@@ -0,0 +1,3 @@
+{% for domain in (postfix_domains + postfix_virtual_domains)|unique %}
+*@{{domain}}	 {{ domain }}
+{% endfor %}
diff --git a/postfix/templates/dkimpy-milter.conf.j2 b/postfix/templates/dkimpy-milter.conf.j2
new file mode 100644
index 0000000000000000000000000000000000000000..44aebd4e51552c4126520b5c88fa52493cb5447a
--- /dev/null
+++ b/postfix/templates/dkimpy-milter.conf.j2
@@ -0,0 +1,49 @@
+# This is a basic configuration that can easily be adapted to suit a standard
+# installation. For more advanced options, see dkimpy-milter.conf(5).
+
+# Log to syslog
+Syslog			yes
+
+# Required to use local socket with MTAs that access the socket as a non-
+# privileged user (e.g. Postfix)
+UMask			007
+
+KeyTable	/etc/dkimpy-milter/keytable
+SigningTable	/etc/dkimpy-milter/signingtable
+InternalHosts	{{ postfix_dkim_internal_hosts | join(',') }}
+
+# Commonly-used options; the commented-out versions show the defaults.
+#Canonicalization	relaxed/simple
+#Mode			sv
+
+# ##  Socket socketspec
+# ##
+# ##  Names the socket where this filter should listen for milter connections
+# ##  from the MTA.  Required.  Should be in one of these forms:
+# ##
+# ##  inet:port@address           to listen on a specific interface
+# ##  inet:port                   to listen on all interfaces
+# ##  local:/path/to/socket       to listen on a UNIX domain socket
+#
+#
+Socket			inet:8892@localhost
+
+##  PidFile filename
+###      default /run/dkimpy-milter/dkimpy-milter.pid
+###
+###  Name of the file where the filter should write its pid before beginning
+###  normal operations.
+#
+PidFile			/run/dkimpy-milter/dkimpy-milter.pid
+
+##  Userid userid
+###      default dkimpy-milter
+###
+###  Change to user "userid" before starting normal operation?  May include
+###  a group ID as well, separated from the userid by a colon.
+#
+UserID			dkimpy-milter
+
+Mode			sv
+MacroList		daemon_name|ORIGINATING
+MacroListVerify		daemon_name|VERIFYING
diff --git a/postfix/templates/master.cf.j2 b/postfix/templates/master.cf.j2
index dbd62667c2a55e6af22a531405949dcbfd170492..397781b0ff971f56db652aca7375f7e906cb3920 100644
--- a/postfix/templates/master.cf.j2
+++ b/postfix/templates/master.cf.j2
@@ -16,6 +16,10 @@ smtp     inet  n       -       y       -       -       smtpd
 {% if postfix_enable_postscreen %}
 smtp      inet  n       -       y       -       1       postscreen
 smtpd     pass  -       -       y       -       -       smtpd
+{% if postfix_enable_dkim %}
+  -o milter_macro_daemon_name=VERIFYING
+  -o smtpd_milters=inet:localhost:8892
+{% endif %}
 {% if postfix_content_filter %}
   -o content_filter={{ postfix_content_filter }}
 {% endif %}
@@ -36,6 +40,10 @@ submission inet n       -       y       -       -       smtpd
 {% if postfix_content_filter %}
   -o content_filter={{ postfix_content_filter }}
 {% endif %}
+{% if postfix_enable_dkim %}
+  -o milter_macro_daemon_name=ORIGINATING
+  -o smtpd_milters=inet:localhost:8892
+{% endif %}
 {% endif %}
 {% if postfix_enable_smtps %}
 smtps     inet  n       -       y       -       -       smtpd
diff --git a/postfix/templates/opendkim.conf.j2 b/postfix/templates/opendkim.conf.j2
deleted file mode 100644
index 4d53e4bf3ad3792ff22a88ba6f56d30d7957f64e..0000000000000000000000000000000000000000
--- a/postfix/templates/opendkim.conf.j2
+++ /dev/null
@@ -1,52 +0,0 @@
-# This is a basic configuration for signing and verifying. It can easily be
-# adapted to suit a basic installation. See opendkim.conf(5) and
-# /usr/share/doc/opendkim/examples/opendkim.conf.sample for complete
-# documentation of available configuration parameters.
-
-Syslog                  yes
-SyslogSuccess           yes
-#LogWhy                 no
-
-# Common signing and verification parameters. In Debian, the "From" header is
-# oversigned, because it is often the identity key used by reputation systems
-# and thus somewhat security sensitive.
-Canonicalization        relaxed/simple
-#Mode                   sv
-#SubDomains             no
-OversignHeaders         From
-
-
-# In Debian, opendkim runs as user "opendkim". A umask of 007 is required when
-# using a local socket with MTAs that access the socket as a non-privileged
-# user (for example, Postfix). You may need to add user "postfix" to group
-# "opendkim" in that case.
-UserID                  opendkim
-UMask                   007
-
-# Socket for the MTA connection (required). If the MTA is inside a chroot jail,
-# it must be ensured that the socket is accessible. In Debian, Postfix runs in
-# a chroot in /var/spool/postfix, therefore a Unix socket would have to be
-# configured as shown on the last line below.
-Socket                  local:/run/opendkim/opendkim.sock
-#Socket                 inet:8891@localhost
-#Socket                 inet:8891
-#Socket                 local:/var/spool/postfix/opendkim/opendkim.sock
-
-PidFile                 /run/opendkim/opendkim.pid
-
-# Hosts for which to sign rather than verify, default is 127.0.0.1. See the
-# OPERATION section of opendkim(8) for more information.
-#InternalHosts          192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12
-
-# The trust anchor enables DNSSEC. In Debian, the trust anchor file is provided
-# by the package dns-root-data.
-TrustAnchorFile         /usr/share/dns/root.key
-#Nameservers            127.0.0.1
-
-
-# Specify the list of keys
-KeyTable file:/etc/dkimkeys/keytable
-# Match keys and domains. To use regular expressions in the file, use refile: instead of file:
-SigningTable refile:/etc/dkimkeys/signingtable 
-# Match a list of hosts whose messages will be signed. By default, only localhost is considered as internal host.
-InternalHosts refile:/etc/dkimkeys/trustedhosts