diff --git a/postfix/defaults/main.yml b/postfix/defaults/main.yml
index 1b8e12d5b15303db5f7f33f104ebc547cbbc61dc..9946b458c8f3669183ecc3b734ed1302251701d8 100644
--- a/postfix/defaults/main.yml
+++ b/postfix/defaults/main.yml
@@ -43,6 +43,8 @@ postfix_transport_maps: []
 #    protocol: smtp
 #    use_mx: true
 
+postfix_verify_spf: false
+postfix_verify_spf_testmode: true
 # Note: This requires at least buster-backports or newer.
 postfix_enable_mta_sts: false
 
diff --git a/postfix/tasks/main.yml b/postfix/tasks/main.yml
index fb14d196e91a5effd8c98a40ebe7b12b84b18a3f..4483802e9a831ae8f9efdddcbfb94b9fbf15572b 100644
--- a/postfix/tasks/main.yml
+++ b/postfix/tasks/main.yml
@@ -79,6 +79,7 @@
     - mail
 
 - import_tasks: mta-sts.yml
+- import_tasks: spf.yml
 
 - name: install rt-mailgate if needed
   apt:
diff --git a/postfix/tasks/spf.yml b/postfix/tasks/spf.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d3a4bce23f9f9a9b37af83755cdbd299c59b8423
--- /dev/null
+++ b/postfix/tasks/spf.yml
@@ -0,0 +1,19 @@
+---
+
+- name: ensure we got the SPF policy daemon installed
+  apt:
+    state: "{{ 'present' if postfix_verify_spf else 'absent' }}"
+    name: postfix-policyd-spf-python
+  notify:
+    - restart postfix
+
+- name: ensure the SPF policy daemon is configured
+  template:
+    src: policyd-spf.conf.j2
+    dest: /etc/postfix-policyd-spf-python/policyd-spf.conf
+    owner: root
+    group: root
+    mode: '0644'
+  when: postfix_verify_spf
+  notify:
+    - restart postfix
diff --git a/postfix/templates/main.cf.j2 b/postfix/templates/main.cf.j2
index 784102c9d1c32818ba0de246546d2276ab01ed75..b645042a4158b3642a1fe3399b45368b29a3d59e 100644
--- a/postfix/templates/main.cf.j2
+++ b/postfix/templates/main.cf.j2
@@ -45,6 +45,13 @@ smtpd_relay_restrictions =
 	permit_mynetworks
 	permit_sasl_authenticated
 	defer_unauth_destination
+{% if postfix_verify_spf %}
+smtpd_recipient_restrictions=
+     permit_mynetworks
+     permit_sasl_authenticated
+     reject_unauth_destination
+     check_policy_service unix:private/policy-spf
+{% endif %}
 
 smtpd_use_tls = yes
 smtp_tls_security_level = may
diff --git a/postfix/templates/master.cf.j2 b/postfix/templates/master.cf.j2
index 2d4a4ffaf6422475a0381eabe0ac06f5a7c246c5..90d235d5d6d03c8616b1176b865d6bfa832f14df 100644
--- a/postfix/templates/master.cf.j2
+++ b/postfix/templates/master.cf.j2
@@ -94,6 +94,11 @@ postlog   unix-dgram n  -       n       -       1       postlogd
 # maildrop. See the Postfix MAILDROP_README file for details.
 # Also specify in main.cf: maildrop_destination_recipient_limit=1
 
+{% if postfix_verify_spf %}
+policy-spf unix -       n       n       -       0       spawn
+  user=nobody argv=/usr/bin/policyd-spf
+{% endif %}
+
 {% if not postfix_satellite_only %}
 dovecot   unix  -       n       n       -       -       pipe
   flags=DRhu user=5001:5000 argv=/usr/lib/dovecot/dovecot-lda -f ${sender} -a ${original_recipient} -d ${user}@${nexthop}
diff --git a/postfix/templates/policyd-spf.conf.j2 b/postfix/templates/policyd-spf.conf.j2
new file mode 100644
index 0000000000000000000000000000000000000000..f7a781fd31b77dc986c7d9d2eb842953b6607060
--- /dev/null
+++ b/postfix/templates/policyd-spf.conf.j2
@@ -0,0 +1,13 @@
+#  For a fully commented sample config file see policyd-spf.conf.commented
+
+debugLevel = 1
+TestOnly = {{ '1' if postfix_verify_spf_testmode else '0' }}
+
+HELO_reject = Fail
+Mail_From_reject = Fail
+
+PermError_reject = False
+TempError_Defer = False
+
+skip_addresses = 127.0.0.0/8,::ffff:127.0.0.0/104,::1
+