Preface

Mailcow is an awesome self-hosted, container-based email solution.

SRS (Sender Rewriting Framework) (from Wikipedia):

For a mail transfer agent (MTA), the Sender Rewriting Scheme (SRS) is a scheme for rewriting the envelope sender address of an email message, in view of remailing it. In this context, remailing is a kind of email forwarding. SRS was devised in order to forward email without breaking the Sender Policy Framework (SPF), in 2003.

Mailcow does not include support for SRS which causes problems with email aliases that point to external email addresses.

This article show how I have ‘hacked’ SRS into Mailcow using postsrsd. Most of the Postfix configuration was taking from this postsrsd issue.

Updated 2023-05-21: PostSRSd has been upgraded to 2.x in Alpine 3.18+ - The article has been updated to work with PostSRSd 2.x. - I am currently not running Mailcow, so I cannot confirm if it works or not.

Updated 2023-07-24: After deploying Mailcow again, I have been able to create a configuration which works. I take no credit for this, all information was found in this Github issue.

Container

I have created a container with postsrsd.

First, add a new container to docker-compose.override.yml

    postsrsd-mailcow:
            image: ajoergensen/postsrsd:latest
            restart: always
            environment:
                    - SRS_DOMAIN=mydomain.dom
                    - SRS_SECRET=<secret>
            networks:
                    mailcow-network:
                            ipv4_address: ${IPV4_NETWORK:-172.22.1}.42

SRS_DOMAIN is the domain to be used as sender domain for the rewritten emails.

SRS_SECRET is the key used to generate the hashed rewritten sender addresses.
This should be treated with same care as a password as knowledge of this secret would allow someone to use your mailserver as an open relay. It is generated using this command:

dd if=/dev/random bs=18 count=1 | base64

After adding the container, start it:

docker compose up -d postsrsd-mailcow

The container’s configuration is documented on Docker Hub.

Postfix configuration

In the file data/conf/postfix/extra.cf (relative to where the Mailcow git repository is checked out), add these lines:

# For postsrsd
## In order to disable postsrsd, just comment out the following two blocks and restart postfix-mailcow!
## There is also config in master.cf, but it shouldn't interfere without these config lines here

## postsrsd's reverse service is listening on port 10002
sender_canonical_classes = envelope_sender
recipient_canonical_maps = socketmap:inet:172.30.1.42:10003:reverse, proxy:mysql:/opt/postfix/conf/sql/mysql_recipient_canonical_maps.cf
recipient_canonical_classes = envelope_recipient, header_recipient

# Also for postsrsd, we override the default transport maps to use the smtpd on port 10029 for all non-local recipients
transport_maps = pcre:/opt/postfix/conf/custom_transport.pcre,
  pcre:/opt/postfix/conf/local_transport,
  proxy:mysql:/opt/postfix/conf/sql/mysql_relay_ne.cf,
  proxy:mysql:/opt/postfix/conf/sql/mysql_transport_maps.cf,
  proxy:mysql:/opt/postfix/conf/sql/mysql_non-local_srs.cf

In the file data/conf/postfix/master.cf, add these lines:

# SRS config
cleanup-srs unix  n       -       -       -       0       cleanup
  -o sender_canonical_maps=proxy:mysql:/opt/postfix/conf/sql/mysql_local_senders.cf,socketmap:inet:172.30.1.42:10003:forward
  -o sender_canonical_classes=envelope_sender
  #-o recipient_canonical_maps=regexp:/opt/postfix/conf/regex_sender_canonical_srs
  -o syslog_name=cleanup-srs

# Only non-local recipients should end up here per our transport map in extra.cf
127.0.0.1:10029 inet    n       -       -       -       -       smtpd
  -o cleanup_service_name=cleanup-srs
  -o smtpd_tls_security_level=none
  -o content_filter=smtp:
  -o smtpd_recipient_restrictions=permit_mynetworks,reject
  -o smtpd_milters=
  -o syslog_name=srs

Create the file data/conf/postfix/sql/mysql_local_senders.cf with this content:

user = mailcow
password = ********
hosts = unix:/var/run/mysqld/mysqld.sock
dbname = mailcow
query = SELECT '%s' FROM alias WHERE domain = '%d'

Create the file data/conf/postfix/sql/mysql_non-local_srs.cf with this content:

user = mailcow
password = ********
hosts = unix:/var/run/mysqld/mysqld.sock
dbname = mailcow
query = SELECT IF(EXISTS(SELECT domain FROM domain WHERE domain = '%d' UNION SELECT alias_domain FROM alias_domain WHERE alias_domain = '%d'), NULL, 'smtp:127.0.0.1:10029') AS 'transport'

(the password for MySQL is found in mailcow.conf)

In the file data/conf/postfix/main.cf add the following to lines to proxy_read_maps:

  proxy:mysql:/opt/postfix/conf/sql/mysql_local_senders.cf,
  proxy:mysql:/opt/postfix/conf/sql/mysql_non-local_srs.cf,

Finally, restart the Postfix container:

docker-compose restart postfix-mailcow / docker compose restart postfix-mailcow

Dovecot

I do not use mailforwarding via Sieve myself, but according to the Github issue, a tweak to the Dovecot Sieve plugin is required

Once I got that resolved I then found that the Sieve filter for redirecting to my @gmail.com address wasn’t using the SRS redirect, and it appears that this is because Dovecot in Mailcow seems to be configured in a non-default way for the sieve_redirect_envelope_from setting to use the final recipient address as the sender/return-path address for the forwarded copy of the email, so it was using a local address for the sender and hence not doing the SRS address rewrite.

To fix this, add the following to data/conf/dovecot/extra.conf

plugin {
    sieve_redirect_envelope_from = sender
}

Remember to restart Dovecot

docker-compose restart dovecot-mailcow / docker compose restart dovecot-mailcow

Add external aliases

With this configuration in place, simply add external aliases like any other alias.