Passing email to scripts with Postfix

Why?

While most of the time, email is simply used for a short message here and there about sales or correspondence, it's an ubiquitious and can be easily harnessed to do more interesting things for us. I've found that sometimes I want to know more than just the text that's there... Analytics, Data collection, triggering scripts, or even just more indepth processing of a stream of emails, using an external script can help out tremendously. It's fairly straight forward to setup, so let's take a peek at the process!

Install Postifx

sudo apt-get update; sudo apt-get install postfix -y

Then following the install prompts, you'd choose "Internet Site", input your FQDN (example.org).

Setup your MX Record

At this point, you'll want to talk to/connect into your DNS Server and update your MX Record to point toward this machine. If you already have working email coming to this server, you can skip this. If you don't, but you have another SMTP server you're currently using, you can either start using this one later after we get this all setup to try to reduce your downtime, or you can have your primary one forward only certain addresses to this machine to process in this fashion.

If you're setting this up for the first time, adding your MX records at this point will help us test things quicker as they can sometimes take a while to roll out to things that'll be sending mail you're way. They're special types of DNS records for directing mail. Unlike most other types of DNS records, you can have multiple MX records in a single zone, each with their own priority. The lower the number the higher priority.

Create a quick script

To work with incoming emails, we're going to use the pipe output of postfix and throw it at a quick php script we create that will allow us to intercept any email, encode it as a json document, and append it to a local file. Obviously, you could do anything with this. Things like firing it at a remote API, triggering an ITTT or IOT object, or even charting emails over time. The world is your oyster.

We'll be able to choose a few things to get thrown toward our script, but the most notable are the sender and recipient. The body will come through as a pipe normally would, so we make our script accept parameters and then read from stdin for the message itself.

/usr/local/bin/emailtofile.php
#!/usr/bin/php
<?php
// See what options are passed in and absorb them:
$opts=getopt('e:f:t:h');

// Open the output file
$fh=fopen('/var/spool/postfix/temp/data',"a+");

// Get the message from stdin 
$fd    = fopen("php://stdin", "r");  
$lines = array();
while ($line=fgets($fd)) {
    $lines[]=$line;
}
fclose($fd);

// Write it out
fwrite($fh,"Content:".json_encode($lines)."\n");
fclose($fh);
?>

Make sure this script is Executable! (If you don't know how, just run chmod 755 /usr/local/bin/emailtofile.php)

Configure Postfix

First, we need to add a service (we'll call it 'etof' as it's 'email to file') to the end of /etc/postfix/master.cf:

etof  unix  -       n       n       -       10      pipe
  flags=Rq user=filter null_sender=
  argv=/usr/local/bin/emailtofile.php -e live -f ${sender} -t ${recipient}

You'll note that we put user=filter in there. That's the user we're going to run this as, filter. Not running it as the postfix user helps security a bit, and allows us to limit it's access to the rest of the system. You can name this user whatever you want, just be consistent. We'll have to create that user in a moment. First, let's add the following to allow it to be used as an email transport for example.org, to /etc/postfix/transport

example.org etof:

However, don't forget that this needs to be referenced in the main.cf file or it won't get pulled in. Do so by tacking this onto the end of your /etc/postfix/main.cf file:

transport_maps = hash:/etc/postfix/transport

Create that filter user

We then create that filter user we talked about, (give it a strong password, you'll never use it anyway). While we're at it, let's make that data file that the script outputs to. It should be writable by filter

adduser filter

Export transport and Reload Postfix

Finally, we'll run the follwing commands. The first updates the transport.db to match what we specified in transport. The second reloads the postfix service to get it to load in all the config changes we made.

sudo postmap /etc/postfix/transport
sudo service postfix reload

Test it out

Go to your favorite email provider, and send an email toward your system!

Mar  4 03:08:16 ip-10-0-0-106 postfix/smtpd[9307]: connect from mail-ua1-f48.google.com[209.85.222.48]
Mar  4 03:08:16 ip-10-0-0-106 postfix/smtpd[9307]: 8170F17E082: client=mail-ua1-f48.google.com[209.85.222.48]
Mar  4 03:08:16 ip-10-0-0-106 postfix/cleanup[9310]: 8170F17E082: message-id=<CAJe7ci7V3xvvzgpp6MPRh_5MU1AUFO_bj9NvZvgaL2Q=LRUY2Q@mail.gmail.com>
Mar  4 03:08:16 ip-10-0-0-106 postfix/qmgr[9245]: 8170F17E082: from=<[email protected]>, size=5758, nrcpt=1 (queue active)
Mar  4 03:08:16 ip-10-0-0-106 postfix/smtpd[9307]: disconnect from mail-ua1-f48.google.com[209.85.222.48] ehlo=2 starttls=1 mail=1 rcpt=1 data=1 quit=1 commands=7
Mar  4 03:08:16 ip-10-0-0-106 postfix/pipe[9311]: 8170F17E082: to=<[email protected]>, relay=etof, delay=0.23, delays=0.07/0.01/0/0.16, dsn=2.0.0, status=sent (delivered via etof service)
Mar  4 03:08:16 ip-10-0-0-106 postfix/qmgr[9245]: 8170F17E082: removed

You'll notice in this log, that we get a connection from google (I tested this via gmail), and delivered message ID: 8170F17E082 to our server. You'll then see who it was to, and that it used the relay etof which is our new script. And shows that it was "delivered via etoa service".

You'll also see there's a lot of data in that file. Now is the time where you get to start filling out your script to handle it and have fun expanding on it!