Configuring the Sender Rewriting Scheme (SRS) on qmail

July 11, 2023 by Roberto Puzzanghera 7 comments

SPF "breaks" email forwarding. SRS is a way to fix it. SRS is a simple way for forwarding MTAs to rewrite the sender address.


Configure srsfilter, so that it will be called when an email for the srs user is received:

echo "| /var/qmail/bin/srsfilter" > /var/qmail/alias/.qmail-srs-default

Then create and configure a virtual domain to be used exclusively for SRS purposes. Be aware that this virtual domain should not be created by the usual vadddomain program, as it exists just to run srsfilter via the alias/.qmail-srs-default account that we created before and its definition is different from the vpopmail's virtual domains.

echo $SRSDOMAIN:srs >> /var/qmail/control/virtualdomains

Refer to the Life With Qmail bible to understand the logic behind, expecially for what virtual domains, aliases, .qmail and extensions addresses  are concerned. An explanation is also provided below in the testing section.

Add srs.mydomain.tld to rcpthosts so that qmail-smtpd will accept mails for that domain. Do not add it to control/locals otherwise the virtualdomains file will be ignored and srsfilter will not be run.

echo $SRSDOMAIN >> /var/qmail/control/rcpthosts

Add srs.mydomain.tld in the srs_domain control file, so that srsfilter will use it in the rewritten address for all virtual hosts. Let's also create the srs_secret file, as well. It is a random string to generate and check SRS addresses.

echo $SRSDOMAIN > /var/qmail/control/srs_domain
echo "xxxxxxxxxxxxxxxxxxxxxx" > /var/qmail/control/srs_secrets

These are the only mandatory settings; look at the links above to have informations about all the other configuration parameters.

Of course we have to provide an MX record and also an SPF record like this to the newly created srs_domain in our DNS:

srs.mydomain.tld. IN TXT "v=spf1 a mx -all"

We should have already created an SPF record for the control/me domain as well. If not, let's do it now.

We can now test our SRS system.


We are going to test the ability of our SRS system to rewrite the addresses in case of a broken forwarder. Let's use a account as the original sender, as gmail is known to be a very restrictive provider.

In what follows mydomain.tld is a virtual domain in the same server where we have configured the SRS stuff, srs.mydomain.tld is the SRS domain defined in control/srs_domain, is the original sender, while fake@remotedomain.tld is the account where the forwarder has to forward the message. Of course it will bounce back and our MTA has to perform the rewriting and notify the original sender without breaking the SPF.

Create a forwarder on our server, so that all the messages for srstest@mydomain.tld will be forwarded to fake@remotedomain.tld:

echo "&fake@remotedomain.tld" > ~vpopmail/domains/mydomain.tld/.qmail-srstest
chown vpopmail:vchkpw ~vpopmail/domains/mydomain.tld/.qmail-srstest
chmod 600 ~vpopmail/domains/mydomain.tld/.qmail-srstest

In the remotedomain.tld server, disable chkuser in your run file (comment out CHKUSER_START=ALWAYS) and set the .qmail-default file of remotedomain.tld so that it bounces the messages for not existent mailboxes

|~vpopmail/bin/vdelivermail '' bounce-no-mailbox

Since fake@remotedomain.tld is a fake recipient address, remotedomain.tld will bounce back any message to our MTA, which will inform after the rewriting.

Let's see the journey of the message around the net

This is the same as seen from our qmail logs.

1. qmail-smtpd receives and accepts the message from the original sender

2023-06-20 22:54:52.607641500 tcpserver: pid 16574 from 
2023-06-20 22:54:52.627258500 tcpserver: ok 16574 smtp.mydomain.tld: 
2023-06-20 22:55:13.058900500 qlogenvelope: result=accepted code=250 reason=rcptto detail=chkuser  
       rcptto=srstest@mydomain.tld  <---- this is the forwarder
       qp= pid=16574

2. qmail-send passes the message to the forwarder srstest@mydomain.tld

2023-06-20 22:55:19.493086500 info msg 32560286: bytes 3377 
       from <>
       qp 16615 uid 89 
2023-06-20 22:55:19.493086500 starting delivery 60: msg 32560286 
       to local mydomain.tld-srstest@mydomain.tld <---- qmail converts srstest@mydomain.tld to mydomain.tld-srstest@mydomain.tld and treats the result as mydomain.tld is a local address
2023-06-20 22:55:19.493087500 status: local 1/10 remote 0/20 
2023-06-20 22:55:19.495105500 delivery 60: success: did_0+1+0/qp_16617/

Let's see what's happening more in detail.

  1. qmail opens the file control/virtualdomains looking for a line related to the recipient domain, i.e. mydomain.tld. Here is the content of that line:
    Be aware that the two fields before and after the colon : are not a duplicate, because they have a different meaning. The one on the left is the recipient address (even though the user@ part is often omitted), while the one on the right is the user whose .qmail file will have to handle the delivery for that domain.
  2. qmail prepends the field on the right of the colon : to the recipient address, which will be mydomain.tld-srstest@mydomain.tld, and treats mydomain.tld as a local address to deliver the message to.
  3. qmail-local searches the user mydomain.tld (technically it's a simple user even before a domain) in the users/assign file (actually its compiled version, which is users/cdb) and it finds something like:
    It retrieves the home directory, which is /home/vpopmail/domains/mydomain.tld in this example.
  4. Then qmail-local opens the home directory to look for the file srstest/.qmail, which will have to handle the delivery. If it doesn't find that file, it will look for the files ~mydomain.tld/.qmail-srstest and ~mydomain.tld/.qmail-default in this order.
  5. In our case the file .qmail-srstest does exist and, being this the forwarder created earlier, it directs qmail-send in order to forward the message to someone else.

3. The address of the original sender is rewrited to and the message sent to the remote server by qmail-remote

2023-06-20 22:55:19.495905500 info msg 32560301: bytes 3491 
       from <>   <----- SPF check would be valid
       qp 16617 uid 89 
2023-06-20 22:55:19.495918500 starting delivery 61: msg 32560301 
       to remote fake@remotedomain.tld 
2023-06-20 22:55:19.495920500 status: local 0/10 remote 1/20 
2023-06-20 22:55:23.822750500 delivery 61: success: 

4. qmail-smtpd receives the bounce (actually if the remote server has the SRS feature it will be able to bounce the message towards, but not in this case).

2023-06-20 22:55:24.049537500 tcpserver: pid 16687 from <remoteip>
2023-06-20 22:55:24.049945500 tcpserver: ok 16687 smtp.mydomain.tld: <remoteip>.<remotehost>:<remoteip>::37391 
2023-06-20 22:55:44.826126500 qlogenvelope: result=accepted code=250 reason=rcptto detail=chkuser  
       mailfrom=        <------ null sender, as it's a system msg  <------- the recipient has the srs_domain
       qp= pid=16687 

5. qmail-send sends the bounce to the local address, which has the SRS domain that we bound to srsfilter

2023-06-20 22:55:51.265166500 info msg 32560286: bytes 5688 
       from <> <------- null sender, SPF is ok
       qp 16716 uid 89
2023-06-20 22:55:51.265166500 starting delivery 62: msg 32560286 
       to local <------- the srs prepend will trigger the default srs account, which in turn will run srsfilter
2023-06-20 22:55:51.265167500 status: local 1/10 remote 0/20 
2023-06-20 22:55:51.270712500 delivery 62: 
       success: srsfilter:_qp_16720/did_0+0+1/   <------- srsfilter in action... he knows what to do with that address

Now a few more details concerning what's happening behind the scene:

  1. qmail receives a message for a user of the domain srs.mydomain.tld and it deliver it locally, as that domain is listed in rcpthosts.
  2. qmail searches a line with the field srs.mydomain.tld on the left of the colon : in the virtualdomains file:
  3. The field on the right side of the colon : will be prepended to the local recipient address, which will be, with the srs prefix. This prefix represents the user whose .qmail file has to handle the delivery.
  4. qmail-local opens the users/assign file (actually its compiled version users/cdb) looking for a line holding srs in the first field, but it doesn't find it, because we have no srs users (this is why we avoided to use vadddomain to create the SRS alias).
  5. Since the user srs doesn't exist, it will look for an alias like alias/.qmail-srs, with no luck. Finally it finds the alias file alias/.qmail-srs-default, which will be the .qmail file that will handle the delivery.
  6. The alias/.qmail-srs-default file contains the instructions to run srsfilter.
  7. srsfilter is capable to "disassemble" the address in order to retrieve the recipient address, which will be

6. qmail-send sends the bounce to the original sender, the mailfrom is the null sender <>

2023-06-20 22:55:51.271507500 info msg 32560301: bytes 5707 
       from <>      <------ null sender
       qp 16720 uid 1000 
2023-06-20 22:55:51.271507500 starting delivery 63: msg 32560301 
       to remote 
2023-06-20 22:55:51.271508500 status: local 0/10 remote 1/20 
2023-06-20 22:55:51.915441500 delivery 63: 
       success: <>_74.125.133.26_accepted_message./Remote_host_said:_250_2.0.0_OK__1687294551_f7-20020adff8c700000>

and gmail will show a successfull SPF check. Here his the important part of the header as seen from

Return-Path: <> Received: from smtp.mydomain.tld (smtp.mydomain.tld. []) by with ESMTPS id v28-20020a5d591c000000b0030e57d7da1dsi2291075wrd.363.2023. for <> [...] Received-SPF: pass ( domain of postmaster@smtp.mydomain.tld designates <smtp.mydomain.tld IP> as permitted sender) client-ip=<smtp.mydomain.tld IP>; Authentication-Results:; dkim=temperror (no key for signature) header.i=@smtp.remotedomain.tld header.s=default header.b=M9b4zh72; spf=pass ( domain of postmaster@smtp.mydomain.tld designates <smtp.mydomain.tld IP> as permitted sender) smtp.helo=smtp.mydomain.tld smtp.helo=smtp.mydomain.tld
Date: 20 Jun 2023 22:55:59 +0200 From: postmaster@smtp.remotedomain.tld <--- <bouncefrom>@<bouncehost> To: Subject: failure notice Hi. This is the qmail-send program at smtp.remotedomain.tld. <--- <bouncehost> I'm afraid I wasn't able to deliver your message to the following addresses. This is a permanent error; I've given up. Sorry it didn't work out. <fake@remotedomain.tld>: Sorry, no mailbox here by that name. (#5.1.1) --- Below this line is a copy of the message. Return-Path: <>

Note that the SPF check was done against smtp.mydomain.tld, i.e. the control/me domain, because the message to the original sender was sent with the null sender. That domain was retrieved from the HELO. This is the reason why we have to define the SPF also for the control/me domain.

The bounce that we are forwarding has been sent on behalf of postmaster@smtp.remotedomain.tld, which is derived from control files as bouncefrom and bouncehost.


SRS Reverse DNS - only one PTR

Got to this point so far, thanks for posting!

Emails may be undeliverable because the reverse DNS mistmatch,  what is the recommended procedure to overcome the Reverse DNS lookup.

Only one PTR; example, but needed also for so emails will go thru?

Reply |

SRS Reverse DNS - only one PTR

Good point. I think the best choice would be to have the reverse DNS pointed to the domain that you have in srs_domain, bouncehost and me at the same time. bouncehost defaults to me and can be omitted

Reply |

SRS Reverse DNS - only one PTR


doing this way has a downside: the control/me domain is stored in control/locals and this prevents the possibility of defining it as a virtual domain for srs. So you should drop it from locals.

Let me know if it works

Reply |

virtualhost for SRS


is it possible to have SRS setup in virtual hosting mode? as far as I've understood, with the setup described here, all the emails from give server are forwarded under default domains (e.g srs.domain.tld). would it be possible, to have the domain according to the recipient, who is then forwarding the email? so if I'll be hosting domain-a.tld and domain-b.tld, the emails will be forwarded via srs.domain-a.tld and srs.domain-b.tld respectively?

thank you


Reply |

virtualhost for SRS

No, it's not possible. This is how srsfilter works... it just picks up the 1st line of control/srs_domain.

But it's something that users do not see, and for me it would be annoying, for administrators, to setup multiple srs domains when the srs domain is just for system messages as it is for other messages like bounces and so on.

Reply |

Correct name of secrets file


In the Configuration section, you have the example:

echo "xxxxxxxxxxxxxxxxxxxxxx" > /var/qmail/control/srs_secret

However, the name of the file is "srs_secrets", with a plural s at the end. So the example should read:

echo "xxxxxxxxxxxxxxxxxxxxxx" > /var/qmail/control/srs_secrets

Best regards

Reply |

Correct name of secrets file

Thank you. Corrected

Reply |

Recent comments
Recent posts

RSS feeds