29

Using postfix, I'd like all incoming mail, to any address (including those that don't map to local users) to be piped to a script. I've tried configuring mailbox_command in /etc/postfix/main.cf:

mailbox_command = /path/to/myscript.py

This works great if the user is a local user, but it fails for "unknown" users who don't have aliases. I tried setting luser_relay to a local user, but this pre-empts mailbox_command, and so the command doesn't get run. I tried setting local_recipient_maps= (empty string), but the message is still bounced (unknown user).

Is there a magic invocation I can use to get all known and unknown users to go to the script as well?

Full /etc/postfix/main.cf follows -- it's the default Ubuntu 10.04, with the exception of the mailbox_command line:

# See /usr/share/postfix/main.cf.dist for a commented, more complete version


# Debian specific:  Specifying a file name will cause the first
# line of that file to be used as the name.  The Debian default
# is /etc/mailname.
#myorigin = /etc/mailname

smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
biff = no

# appending .domain is the MUA's job.
append_dot_mydomain = no

# Uncomment the next line to generate "delayed mail" warnings
#delay_warning_time = 4h

readme_directory = no

# TLS parameters
smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
smtpd_use_tls=yes
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache

# See /usr/share/doc/postfix/TLS_README.gz in the postfix-doc package for
# information on enabling SSL in the smtp client.

myhostname = ... snip ...
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
mydestination = sassafras, ... snip ...,localhost.localdomain, localhost
relayhost =
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all

mailbox_command = /path/to/my/script.py
user67641
  • 1,242
  • 2
  • 14
  • 18
  • After the script, what then? This sounds like the kind of thing that virus scanners and spam filters do, maybe you can find a guide to configuring one of those and use its configuration for your script? – DerfK Apr 11 '11 at 20:48
  • The messages go to a web application. So after the script: a database, other processing, other stuff, but not normal mail stuff. The key question is getting the unknown users to not bounce -- this is outside the normal virus/spam scenario, where you would bounce unmapped users. – user67641 Apr 11 '11 at 20:53
  • @user67641 hi do i need to inject the mail into postfix in my script or by adding some more lines in master.cf – merveotesi Dec 04 '13 at 08:40

4 Answers4

40

Ok, I just got this working -- though hairier than I thought it would be. I dropped the maildir_command part, and went with transport_maps. The key is to do 5 things:

  1. Set up a db file to handle aliases (and add a catch-all alias)
  2. Set up a db file to map the 'transport' for the domain in question to a special handler.
  3. Compile the db files into berkeley db format that postfix wants.
  4. Set up the handler in /etc/postfix/master.cf to pipe mail to the script.
  5. Set /etc/postfix/main.cf to use the transport db for transport_maps, and the alias db for virtual_alias-maps.

(1) Create /etc/postfix/virtual_aliases to add a catch-all alias -- localuser needs to be an existing local user:

@mydomain.tld   localuser@mydomain.tld

(2) Create /etc/postfix/transport to add a transport mapping. "mytransportname" can be whatever you want; it's used below in master.cf:

mydomain.tld    mytransportname:

(3) Next, both transport and virtual_aliases need to be compiled into berkeley db files:

$ sudo postmap /etc/postfix/virtual_aliases
$ sudo postmap /etc/postfix/transport

(4) Add the transport to /etc/postfix/master.cf:

mytransportname   unix  -       n       n       -       -       pipe
  flags=FR user=localuser argv=/path/to/my/script.py
  ${nexthop} ${user}

(5) In /etc/postfix/main.cf:

  ...
  transport_maps = hash:/etc/postfix/transport
  virtual_alias_maps = hash:/etc/postfix/virtual_aliases

And... good to go! Sheesh.

user67641
  • 1,242
  • 2
  • 14
  • 18
  • 1
    extremely useful answer – merveotesi Nov 12 '13 at 15:42
  • 1
    Virtual alias maps are not needed. You can accomplish the same thing with `relay_domains` (in which case the transport file can filter based on the actual To: address). – Brilliand Jul 14 '15 at 21:03
  • I found you can route all virtual mail to a script with `virtual_transport = mailcaptcha:` in `main.cf` and `mailcaptcha unix - n n - - pipe flags=FR user=vmail argv=/usr/bin/perl /usr/local/mail-captcha/verify.pl ${sender} ${recipient}` in `master.cf`. See http://www.postfix.org/transport.5.html and http://www.postfix.org/VIRTUAL_README.html. – Chloe Jan 19 '17 at 18:44
  • The (4) should be in one single line, is that right? Also doesn't it require a `postfix restart` or `reload` or something like this? Last thing: is there a log file to see the mails who arrived to postfix? – Basj Nov 21 '17 at 15:25
  • @Basj, the log file is `/var/log/mail.log`. – fiedl Jul 22 '19 at 19:18
  • In order to use the envelope recipient ( `RCTP TO`) in the script, either append the envelope recipient as header by using `flags=FRDO`, or pass it to the script as parameter by adding the `${original_recipient}` command-line macro. Documentation: http://www.postfix.org/pipe.8.html – fiedl Jul 22 '19 at 22:31
  • +1 Thanks for this. Saved me a bunch of time setting this up for a bounce catchall. – Ro Achterberg Mar 31 '21 at 15:06
5

The only time I've used something like this was for a specific user's mailbox. All that was required was to alias that user's name to a pipe and a process in aliases:

pong: "| /usr/local/bin/gotit.pl"

This sent traffic destined for "pong@mymailserver.com" to a perl script I wrote to process it.

gotit.pl (as an example, don't pick on me for crappy programming skillz =). It's job was to process an email I'd sent to our Exchange server (where it was auto-replied via some VB code) to verify that Exchange was processing email in a timely fashion. If not, the mail server would send out an alert email to our pagers and write a lock file so we didn't get constantly spammed.

#! /usr/bin/perl -w
use vars qw ( $mung $sent $tvalue $remainder $delta $fout );
$mung = time;
while (<STDIN>) {
    ($sent, $tvalue, $remainder ) = split /: /, $_, 3;
    $tvalue =~ s/(\D+)//g;
    chomp($tvalue);
    $delta = $mung-$tvalue;
    if ( $sent =~ "Sent" ) {
        $fout = "/var/spool/mailcheck/$tvalue";
        next unless ( -e $fout );
        open (TMP, "> $fout") or die "Couldn't open output file: $!\n";
        print TMP "Received in: $delta seconds.\n";
                close TMP;
        last;
    }
}
Greeblesnort
  • 1,739
  • 8
  • 10
  • Yeah, and just redirect @mydomain.tld to pong and all incoming main for mydomain.tld will be piped. – Zaar Hai Jul 13 '14 at 10:55
  • and for those of us using multiple virtual domains, don't forget to redirect it to pong@localhost so that it doesn't attach the default domain name to the alias – therightstuff Aug 22 '19 at 22:25
2

After a lot of headaches I put together this solution based on a couple of different sources that resulted in much less effort, the critical steps were configuring virtual_alias_domains as well as virtual_alias_maps and making sure that the virtual mapping was to my-alias@localhost instead of just my-alias. In my example the command alias is to pipe the email to a website API endpoint, but it could just as easily pipe into something else.

Here are the steps you'll need to take:

  • Set up your A and MX records for your domain, the A record @ pointing to the IP of the server you’re going to be receiving emails on and MX with the hostname @ and the value 10 mail.your-domain-name
  • sudo apt-get install postfix
  • Select "Internet Site" and enter your-domain-name (fully qualified)
  • sudo vi /etc/postfix/main.cf
  • Add mail.your-domain-name to the list of mydestination values
  • Append
virtual_alias_domains = hash:/etc/postfix/virtual_domains
virtual_alias_maps = hash:/etc/postfix/virtual

to the end of the file

  • sudo apt-get vi /etc/aliases
curl_email: "|curl --data-binary @- http://your-domain-name/email"
  • sudo newaliases
  • sudo apt-get vi /etc/postfix/virtual_domains
example.net   #domain
example.com   #domain
your-domain-name   #domain

(the #domain fields suppress warnings)

  • sudo postmap /etc/postfix/virtual_domains
  • sudo apt-get vi /etc/postfix/virtual
info@your-domain-name bob@gmail.com
everyone@your-domain-name bob@gmail.com jim@gmail.com
email_processor@your-domain-name curl_email@localhost
@your-domain-name catchall@whereveryouwant.com
ted@example.net jane@outlook.com
  • sudo postmap /etc/postfix/virtual
  • sudo /etc/init.d/postfix reload
Michael Hampton
  • 237,123
  • 42
  • 477
  • 940
0

I've used an old style "flat file" Mailbox to receive all mail (and then prune it every few hours if it's large), instead of using the modern maildir/ folders, to process mail through scripts. You could run logrotate over the file too I suppose to keep it manageable.

This way you could simply copy all mail to a Mailbox as a local user.

Jonathan Ross
  • 2,173
  • 11
  • 14