I would like postfix to consult a simple (bash/python) script in order to find out (virtual_alias_maps - style) before-queue whether or not a recipient address it legitimate. E.g. i'd like to use mail addresses like


to either get accepted for $USER if $EXIPRYDATE has not yet passed and if $CHECKSUM passes, or to get rejected otherwise.

If it simplifies the solution, i'd be ok with doing this for a single, constant user, so postfix would only need to observe True/False from some address check script.

I am not interested in solutions that require postfix to accept a mail first and then bounce later if the recipient address is found invalid ("after-queue"). I need postfix to reject during the initial SMTP session ("before-queue").

  • I've never used a script. But you can do a lot with the reg-exp (pcre) version of the db tables. e.g. /^(?:prvs=[\da-f]{10}=)?abuse@foobarbaz.net$/ OK That example is for a back-scatter preventor that prefixed outgoing addresses with a MD5 hex pattern. Obviously it doesn't check the actual value of the hex-code (in this case the machine was 2ndary smtp). Would that help? See also: serverfault.com/questions/258469/… – ericx Aug 8 '14 at 14:48
  • Thx erics, but it doesn't help my use case. I do need to actually check not just format but expiry date and checksum. I know that i could pipe incoming mails to a script, but that would mean to accept incoming mails first and bounce later when found illegitimate, potentially producing backscatter. My aim is to let postfix reject straightaway if the address test fails. – Nils Toedtmann Aug 8 '14 at 15:19
  • Not sure about "that would mean to accept incoming mails first and bounce later when found illegitimate". amavisd for example, is attached as an in-line on incoming smtp: smtp inet n - n - - smtpd -o content_filter=smtp-amavis:localhost:10024 It is clever enough to hold the connection open. If it has a problem, postfix smtp blocks incoming. – ericx Aug 8 '14 at 15:29
  • 1
    You just need to use the before-queue milter, postfix.org/MILTER_README.html – NickW Aug 8 '14 at 15:30
  • 3
    Maybe the simplest solution would be to use 'check_policy_service' and to implement a little policy service (like the ones used for greylisting or SPF). – Nils Toedtmann Aug 8 '14 at 15:51

I have similar problem with yours. Pre-queue requirement left us to three option

Because we are only checking recipient, then SMTP access policy delegation would be suffice. Milter and SMTPD Proxy was used if we need access to header and body too.

My checker script was provided by developer. The program usage was only calling ./myscript recipient_address which recipient_address by postfix. To do that, I use modified version of perl script. This perl script allows me to read stdin, split string per line and split the line with equal = sign.

Then I declare spawn daemon in master.cf

policy  unix    -       n             n     -     -      spawn  user=mydedicateduser argv=/path/to/perl/script.pl

Then define it in main.cf

smtpd_recipient_restrictions =
    check_policy_service unix:private/policy

This script below can be substituted by another programming language. Here the pseudo-code to explain how it works.

while True:

    get the stdin    

    if stdin = ''  # ---> the end of parameter
        call the external script with recipient parameter

        get the return code

        if recipient valid 
            print "dunno"
        else if recipient not valid
            print "reject"
        else            # ---> command error
            print "defer if permit"


        validate the stdin, the proper format is 'parameter=value'

        if valid
            put it in array

For python code, you can modify code from this snippet. No need to set spawn daemon.

For the completeness, here the full source code of /path/to/perl/script.pl

#!/usr/bin/perl -w

use strict;
use Sys::Syslog qw(:DEFAULT setlogsock);
use Getopt::Long;

# Syslogging options for verbose mode and for fatal errors.
# NOTE: comment out the $syslog_socktype line if syslogging does not
# work on your system.
my $syslog_socktype = 'unix'; # inet, unix, stream, console
my $syslog_facility = 'mail';
my $syslog_options  = 'pid';
my $syslog_priority = 'info';

# Configuration

my $executable_path = '/my/execute/script.sh';

# Procedures

# Log an error and abort.
sub fatal_exit {
        my $first = shift @_;
        syslog "err", "fatal: $first", @_;
        exit 1;

# SMTPD access policy routine. The result is an action just like
# it would be specified on the right-hand side of a Postfix access
# table.    Request attributes are available via the %attr hash.
sub smtpd_access_policy() {
    system($executable_path, $attr{'recipient'});

    # -1 command error
    # 0 user isn't valid
    # else user valud
    if ( $? == -1 ) {
        # command error
        return "defer_if_permit Something error"

    if ( $? == 0 ) {
        return "reject user not found";

    return "dunno";

# Main program

# This process runs as a daemon, so it can't log to a terminal. Use
# syslog so that people can actually see our messages.
setlogsock $syslog_socktype;
openlog $0, $syslog_options, $syslog_facility;

unless(GetOptions('v:+' => \$verbose)) {
    syslog $syslog_priority, "Invalid option. Usage: %s [-v] [-v] ...", $0;
    exit 1;

# Unbuffer standard output.
select((select(STDOUT), $| = 1)[0]);

# Receive a bunch of attributes, evaluate the policy, send the result.
while (<STDIN>) {
    if (/^([^=]+)=(.*)$/) {
        $attr{substr($1, 0, 512)} = substr($2, 0, 512);
    } elsif ($_ eq '') {
        if ($verbose>2) {
            for (keys %attr) {
                syslog $syslog_priority, "Attribute: %s=%s", $_, $attr{$_};
        fatal_exit "unrecognized request type: '%s'", $attr{'request'}
            unless $attr{'request'} eq 'smtpd_access_policy';
        my $action = smtpd_access_policy();
        syslog $syslog_priority, "Action: %s", $action if $verbose>1;
        print STDOUT "action=$action\n\n";
        %attr = ();
    } else {
        syslog $syslog_priority, "warning: ignoring garbage: %.100s", $_;

exit 0;
  • updated with pseudocode and python code for SMTP access policy delegation – masegaloeh Dec 29 '14 at 12:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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