Note: This is an AI written summary of the issue I experienced this morning. I had it help me fix the issue, and it did, but I’m concerned about editing files that may be overwritten during the next run of the update.sh script.
Begin AI issue summary:
I’m migrating my mailcow-hosted domains from Gmail POP3 fetching to sieve forwarding, since Google is dropping POP3 support. I configured forwarding to Gmail addresses via the mailcow UI. Forwarding was completely non-functional. After debugging, I found two separate issues that had to be fixed independently.
Issue 1: Dovecot cannot reach Postfix on port 588 when inet_protocols = ipv4 is set
Dovecot’s submission_host = postfix:588 resolves to the IPv6 address of the Postfix container when the Docker network has enable_ipv6: true. If Postfix has inet_protocols = ipv4 set (via extra.cf), it only binds on IPv4, and the connection is refused:
smtp-client: conn postfix:588 ([fde4:xxxx:xxxx::xx]:588): connect(postfix:588) failed: Connection refused
sieve: redirect action: failed to redirect message: smtp(postfix:588): RCPT TO failed: Failed to connect to remote server (temporary failure)
smtp-client: conn postfix:588 ([fde4:xxxx:xxxx::xx]:588): connect(postfix:588) failed: Connection refusedsieve: redirect action: failed to redirect message: smtp(postfix:588): RCPT TO failed: Failed to connect to remote server (temporary failure)
This is a mismatch between Docker’s dual-stack networking and Postfix’s IPv4-only configuration. The inet_protocols = ipv4 setting is common — users set it to avoid IPv6 outbound delivery issues, and smtp_address_preference = ipv4 alone doesn’t prevent IPv6 DNS lookups.
Fix: Changed inet_protocols = ipv4 to inet_protocols = all in data/conf/postfix/extra.cf, keeping smtp_address_preference = ipv4 for outbound preference.
Suggestion for mailcow: Dovecot’s submission_host could reference Postfix by its static IPv4 address (e.g., ${IPV4_NETWORK}.253:588) instead of the hostname, making it immune to IPv6 resolution issues. Alternatively, the documentation could warn that inet_protocols = ipv4 in extra.cf will break sieve redirects.
Issue 2: rspamd re-scans sieve-redirected mail and rejects/greylists it
After fixing the connectivity issue, forwarded mail was being rejected or greylisted by rspamd. The sieve redirect submits mail back through Postfix port 588, which passes through rspamd (milters are inherited from main.cf). rspamd scans the already-delivered message a second time, and the forwarded copy scores badly because:
- R_SPF_FAIL (6): The Docker container’s internal IP isn’t in any public SPF record
- VIOLATED_DIRECT_SPF (3.5): Additional SPF penalty
- HFILTER_HELO_5 (3): The container’s internal HELO hostname looks suspicious to rspamd
These penalties easily push the score above the reject threshold (15) or greylist threshold, even for completely legitimate mail. I tested with both external mail (via https://sendtestemail.com/) and internal mail between two domains on the same mailcow instance — both were blocked on the forwarding leg.
This is particularly frustrating because the mail was already scanned and accepted on intake. The second scan is redundant and harmful.
Fix: Added a dedicated port in master.cf for sieve redirects that bypasses rspamd:
# sieve redirect bypass - skip milters for already-scanned forwarded mail
5870 inet n - n - - smtpd
-o smtpd_client_restrictions=permit_mynetworks,reject
-o smtpd_tls_auth_only=no
-o smtpd_milters=
-o non_smtpd_milters=
-o cleanup_service_name=smtp_sender_cleanup
-o syslog_name=postfix/sieve-redirect
Then created data/conf/dovecot/extra.conf:
submission_host = postfix:5870
This keeps port 588 intact for SOGo (with rspamd scanning) and gives sieve redirects a clean path that doesn’t re-scan.
Suggestion for mailcow: This feels like it should be the default behavior. Sieve redirects are for mail that has already passed rspamd on intake — re-scanning them through milters on port 588 causes false rejections for any forwarding setup. A dedicated submission port for Dovecot sieve redirects (separate from SOGo’s port 588) with milters disabled would fix this out of the box.
Concern about persistence:
Both fixes involve files (master.cf, extra.cf, extra.conf) that may be overwritten by update.sh. The extra.cf and extra.conf files should be safe, but the master.cf addition is at risk. It would be great if mailcow had a master.cf override mechanism similar to extra.cf/extra.conf.
Environment: mailcow-dockerized (latest as of Feb 2026), Postfix 3.7.11, Dovecot 2.3.21, Docker with IPv6 enabled.
(Note: Multiple edits were to clean up the markdown so it wasn’t ugly!)