I want to run a staging copy of a production server on a local environment. The system runs a PHP application, which sends e-mail to customers in various scenarios and I want to make sure no e-mail is ever sent from the staging environment.

I can tweak the code so it uses a dummy e-mail sender, but i'd like to run the exact same code as the production environment. I can use a different MTA (Postfix is just what we use in production), but I'd like something that is easy to set up under Debian/Ubuntu :)

So, I'd like to set up the local Postfix install to store all e-mail in (one or more) files instead of relaying it. Actually, I don't really care how it's stored as long as it's feasible to check the e-mail that was sent. Even a set up option that tells postfix to keep the e-mail in the mail queue would work (I can purge the queue when I reload the staging server with a copy from production).

I know this is possible, I just haven't found any good solution online for what seems like a fairly common need.


5 Answers 5


I created a new transport with a pipe command that writes e-mail down to a file.


  1. Create a user that will own e-mail (or use an existing one). I called mine email
  2. mkdir /home/email/bin
  3. Place the following script in /home/email/bin/mail_eater (this uses PHP, but you can write your own version in any language you like, it just appends stdin to a file):

    $fd = fopen("php://stdin", "r");
    $email = "";
    while (!feof($fd)) {
        $email .= fread($fd, 1024);
    $fh = fopen('/home/email/email.txt','a');
    fwrite($fh, $email."\n-------------------------------------------------------\n\n");
  4. chmod a+x /home/email/bin/mail_eater
  5. touch /home/email/email.txt
  6. chmod a+r /home/email/email.txt
  7. Create a new transport using this file by appending the following line in master.cf:

    file_route unix -    n    n    -    -    pipe user=email  argv=/home/email/bin/mail_eater
  8. Use this as the default transport in main.cf:

    default_transport = file_route

There :)


You could put those domains into $mydestination in main.cf, so postfix will deliver it locally.

You can set up different local users if you want or you can setup a local catch-all address to deliver emails into just one account, more details here: http://www.postfix.org/ADDRESS_REWRITING_README.html#luser_relay

For all domains:

mydestination = pcre:/etc/postfix/mydestinations

and /etc/postfix/mydestinations should contain

/.*/    ACCEPT

I cannot test right now but it should work.

  • I don't know what the destination domains look like (they come from a real customer database). I will check the link though.
    – GomoX
    Nov 22, 2012 at 14:36
  • Thanks for the update, I posted my own solution to the problem as it seems "cleaner" but yours looks like it should work as well.
    – GomoX
    Nov 27, 2012 at 16:26
  • In order to work as intended, this also requires setting local_recipient_maps = . It is described in the link you provided, but I think it should also be mentioned in the answer.
    – jojman
    Sep 5, 2016 at 22:08

try (in main.cf):

defer_transports = smtp

you can then see queue postqueue -p and watch content with postcat


Depending on your distribution, you could look at "nullmailer". This is a relaying MTA, which relays to another SMTP on your network or remote. This could very well be an invalid SMTP, and in that case it would probably only put it into a queue on a folder on the machine.

On debian and ubuntu this is available as a replacement MTA for your system.


This is copied and slightly modified from my blog http://blog.malowa.de/2011/04/postfix-as-spam-trap-server.html:

You don't even have to configure Postfix to act as a nullmailer. Postfix ships with a neat tool called smtp-sink which does the trick. smtp-sink is mainly intended to act as a testing tool for SMTP clients which need a Server to play with. So you can configure it to log the whole conversation or even dump each received mail to a file. The latter is needed for a nullmailer.

There is no configuration file to configure smtp-sink. Everything is done via command-line options.

smtp-sink -c -d "%Y%m%d%H/%M." -f . -u postfix -R /tmp/ -B "550 5.3.0 The recipient does not like your mail. Don't try again." -h spamtrap.example.com 25 1024

Let's have a closer look to each parameter.

-u postfix
Runs the program under the user "postfix"
-R /tmp/
Sets the output directory to /tmp/. In this directory the mails will be stored. If you have a high spam volume (hundreds of Spam per minute) it is recommended to write the mails to a ramdisk
-d "%Y%m%d%H/%M."
Writes the mail to a directory of the format "YearMonthDayHour" and in this directory the files are name "Month.RandomID". Note that the dates are in UTC
Write statistics about connection counts and message counts to stdout while running
-f .
Reject the mail after END-OF-DATA. But the mail will be saved. Cool, isn't it?!
-B "550 5.3.0 The recipient does not like your mail. Don't try again"
This is the rejection message after END-OF-DATA.
-h spamtrap.example.com
Announce the hostname spamtrap.example.com
The port to listen on. Can be prepended with an IP or host if you want to bind on a special interface.
The backlog count of connections that can wait in the TCP/IP stack before they get a free slot for sending mail.

You can find more information in the man page of smtp-sink, but these are the important ones to run a catch-all spamtrap. In this configuration the program accepts any mail with any size from any sender to any recipient with IPv4 and IPv6. The only restrictions are that there are only 256 simultaneous connections possible with 1024 queued connections and the program is flagged experimental. So do not use smtp-sink in a production environment.

The -B option is only valid in newer versions of Postfix. In 2.7.1 it is missing. In 2.8.2 it is present. Somewhere in-between it was introduced.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.