So, forever and a day I’ve been using a service at autorespond+dkim@dk.elandsys.com to provide a simple autoreply for testing connectivity to a mail server. Sadly, the service has been discontinued after many years and so I decided to set up my own with Postfix, the most venerable of mail servers. Here’s how I did that.

Assumptions

  • You’re using a fairly recent edition of Postfix. I’m using 3.7.10. You can find out your version using postconf -d mail_version.

  • You’re using virtual mailboxes, meaning you have a file similar to /etc/postfix/virtual (and .db) that maps email addresses to local user mailboxes. You can check this by looking in /etc/postfix/main.cf for a line like virtual_alias_maps = hash:/etc/postfix/virtual and virtual_alias_domains = yourdomain.com.

  • You’ve backed up your Postfix configuration files using the command cd /etc/postfix && tar -czvf /root/postfix-backup-$(date +%d.%m.%Y).tar.gz ..

  • You’ve checked the backup with tar -tf /root/postfix-backup-$(date +%d.%m.%Y).tar.gz

Everything else we’ll set up so let’s get started.

Step 1: Create a new user called filter

This will create the user called filter with a home directory of /var/spool/filter and a shell of /bin/false. This user will be used to run the autoreply scripts safely.

sudo useradd -d /var/spool/filter -m -s /bin/false filter

Step 2: Add the new user’s mailbox entry to the virtual file

Add the autoreply line autoreply@yourdomain.com filter to your /etc/postfix/virtual file, so it looks something like this..

# compile with postmap /etc/postfix/virtual
hello@yourdomain.com simon
morgan@yourdomain.com morgan
james@yourdomain.com james
simon@yourdomain.com simon
sandra@yourdomain.com sandra
recruitment@yourdomain.com simon sandra
salesteam@yourdomain.com james morgan sandra reporting@parentco.co.uk
autoreply@yourdomain.com filter

This means that email to autoreply@yourdomain.com will be delivered to the filter user’s mailbox.

Save the file and then compile it with postmap /etc/postfix/virtual.

Step 3: Update the Postfix configuration, adding a new service

The /etc/postfix/master.cf file controls how Postfix runs daemons for mail collection, delivery and so on. We’re going to add a new service that will be called every time a mail is received. Add the following to the end of the file:

filter    unix  -       n       n       -       10      pipe
  flags=Rq user=filter null_sender=
  argv=/var/spool/filter/mailfilter.sh -f ${sender} -- ${recipient}

In plain English, this says “Set up a service called filter that is..

  • a unix process
  • is private (- = ‘use the default setting, yes’)
  • is unprivileged (n = ‘use no. Default is yes’)
  • is chrooted (n = ‘use no. Default is yes’)
  • wakeup (- = ‘use the default setting, never’)
  • 10 (max number of processes)
  • pipe (use a pipe to communicate with the script or software which we will call)

On the next line, the most important parts are:

  • user=filter (run the script as the filter user). Note that the service and user names are the same, but they are not somehow dependant. They are unrelated.
  • argv=/var/spool/filter/mailfilter.sh -f ${sender} -- ${recipient} (run the script /var/spool/filter/mailfilter.sh which will be passed four arguments: -f, the sender, -- and the recipient). Seems odd, but this is from the postfix documentation. We’ll be using arguments 2 and 4 in our script for the sender and recipient.

Step 4: Update the Postfix configuration, adding the filter service to the smtpd service

To turn on the content filtering service for smtp that we configured in step 3, add the following to the /etc/postfix/master.cf file:

Look down the left hand side of the file for the smtp service and add the -o content_filter=filter:dummy line just below it, as shown below:

  smtp      inet  ...other stuff here, do not change...   smtpd
        -o content_filter=filter:dummy

Mine looks like this because I’m also using Amavis for spam filtering:

smtp      inet  n       -       y       -       20      smtpd
  -o content_filter=smtp-amavis:[127.0.0.1]:10024
  -o smtpd_sasl_auth_enable=no
  -o content_filter=filter:dummy

You can save master.cf. We’re done with that file.

Step 5: Create the mailfilter.sh script

Create the file /var/spool/filter/mailfilter.sh with the following contents. Download from Github Gists.

#!/bin/sh

# Simple shell-based filter. It is meant to be invoked as follows:
#       /path/to/script -f sender recipients...

# Localize these. The -G option does nothing before Postfix 2.3.
INSPECT_DIR=/var/spool/filter
SENDMAIL="/usr/sbin/sendmail -G -i" # NEVER NEVER NEVER use "-t" here.

# Exit codes from <sysexits.h>
EX_TEMPFAIL=75
EX_UNAVAILABLE=69

# Clean up when done or when aborting.
trap "rm -f in.$$" 0 1 2 3 15

# Start processing.
cd $INSPECT_DIR || {
    echo $INSPECT_DIR does not exist; exit $EX_TEMPFAIL; }

cat >in.$$ || {
    echo Cannot save mail to file; exit $EX_TEMPFAIL; }

# Specify your content filter here.
/var/spool/filter/process-mail.sh "$@" <in.$$ || {
   echo Message content rejected; exit $EX_UNAVAILABLE; }

$SENDMAIL "$@" <in.$$ || {
   echo Unable to send mail; exit $EX_UNAVAILABLE;
}

exit $?

This file does not need any modification. It will pass control to the process-mail.sh script which we will create next. This script will do all the work.

Step 6: Create the process-mail.sh script

Create the file /var/spool/filter/process-mail.sh with the following contents, adjusting SENDMAIL and DOMAIN values as needed. LOCAL_AUTOREPLY_ADDRESS should be fine. Download from Github Gists.

#!/bin/bash

# $2 is the email sender.  We want to autoreply to them
# $4 is our filter user email address, typically filter@localhost
# Check the parameters below are right for your system

SENDMAIL="/usr/lib/sendmail"
DOMAIN="YOUR-DOMAIN-NAME-HERE.com"
LOCAL_AUTOREPLY_ADDRESS="filter@localhost"

if [ "$4" = $LOCAL_AUTOREPLY_ADDRESS ]; then
  echo "OK, I'll send a reply to |$2|"
  printf '%s\n' "Subject: Autoreply from $DOMAIN" \
    "From: Autoreply <autoreply@$DOMAIN>" \
    "To: Recipient <$2>" \
    "" \
    "--------------------------------------------------------------------------------" \
    " " \
    " This is the autoresponder for $DOMAIN.  Your mail was received!" \
    " " \
    "--------------------------------------------------------------------------------" | $SENDMAIL -oi -t
else
  echo "Not an autoreply address."
fi

Step 7: Set privs and ownership

Make the scripts executable and owned by the filter user:

sudo chown filter:filter /var/spool/filter/mailfilter.sh /var/spool/filter/process-mail.sh
sudo chmod +x /var/spool/filter/mailfilter.sh /var/spool/filter/process-mail.sh

Step 8: Restart Postfix

Restart Postfix to pick up the changes:

sudo systemctl restart postfix

Step 9: Test

Start by tailing the mail log, but only looking for the appearance of the pipe service we added to master.cf. postfix is a bit chatty.

tail -f /var/log/mail.log | grep pipe

OK, now send a test email to yourself e.g. morgan@yourdomain.com from outside to get something like this..

2024-05-03T20:02:49.693321+02:00 mail postfix/pipe[403102]: 7D5342001BB: to=<morgan@localhost>, orig_to=<morgan@yourdomain.com>, relay=filter, delay=0.24, delays=0.22/0/0/0.02, dsn=2.0.0, status=sent (delivered via filter service (Not an autoreply address.))

As you can see, it’s been through the filter and the process-mail.sh script has decided it’s not an autoreply address.

Now send an email to autoreply@yourdomain.com and you should see something like this:

2024-05-03T20:03:07.066805+02:00 mail postfix/pipe[403102]: EC7882001BB: to=<filter@localhost>, orig_to=<autoreply@yourdomain.com>, relay=filter, delay=0.2, delays=0.15/0/0/0.05, dsn=2.0.0, status=sent (delivered via filter service (OK, I'll send a reply to |morgans.testmails@outlook.com|))

The end user should get something like this..

Subject: Autoreply from yourdomain.com
From: Autoreply <autoreply@yourdomain.com>
To: Recipient <morgans.testmails@outlook.com>
Message-Id: <20240503180307.091F72003BC@mail.yourdomain.com>
Date: Fri,
  3 May 2024 20:03:07 +0200 (CEST)

--------------------------------------------------------------------------------

 This is the autoresponder for yourdomain.com.  Your mail was received!

--------------------------------------------------------------------------------

So there you have it. A simple autoreply service for your domain using Postfix. You can now customise the process-mail.sh script to do whatever you like with the incoming mail, such as incrementing a counter on Slack or website or maybe writing it to an .rrd file for graphing or something.

You can test all this by emailing autoreply@cloudguyinbroadstone.com and it will reply to you.