Skip to content
Sender rate limit policy daemon for postfix
Branch: master
Clone or download
Pull request Compare This branch is 1 commit ahead, 1 commit behind onlime:master.
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Type Name Latest commit message Commit time
Failed to load latest commit information.


A Sender rate limit policy daemon for Postfix.

Original Copyright (c) Onlime Webhosting (

Modified by Mathieu Pellegrin for WellHosted (


This project was forked from onlime/ratelimit-policyd and modified to ensure that only authenticated users are counted for quota. All credits go to Simone Caruso for his original work (bejelith/send_rate_policyd).


This small Perl daemon limits the number of emails sent by users through your Postfix server, and store message quota in a RDMS system (MySQL). It counts the number of recipients for each sent email. You can setup a send rate per user or sender domain (via SASL username) on daily/weekly/monthly basis.

The program uses the Postfix policy delegation protocol to control access to the mail system before a message has been accepted (please visit SMTPD_POLICY_README.html for more information).

For a long time we were using Postfix-Policyd v1 (the old 1.82) in production instead, but that project was no longer maintained and the successor PolicyD v2 (codename "cluebringer") got overly complex and badly documented. Also, PolicyD seems to have been abandoned since 2013.

ratelimit-policyd will never be as feature-rich as other policy daemons. Its main purpose is to limit the number of emails per account, nothing more and nothing less. We focus on performance and simplicity.

This daemon caches the quota in memory, so you don't need to worry about I/O operations!

New Features

The original forked code from bejelith/send_rate_policyd was improved with the following new features:

  • automatically inserts new SASL-users (upon first email sent)
  • Debian default init.d startscript
  • added installer and documentation
  • bugfix: weekly mode did not work (expiry date was not correctly calculated)
  • bugfix: counters did not get reset after expiry
  • additional information in DB: updated timestamp
  • added view_ratelimit in DB to make Unix timestamps human readable (default datetime format)
  • syslog messaging (similar to Postfix-policyd) including all relevant information and counter/quota
  • more detailed logging
  • added logrotation script for /var/log/ratelimit-policyd.log
  • added flag in ratelimit DB table to make specific quotas persistent (all others will get reset to default after expiry)
  • continue raising counter even in over quota state

The script from Onlime Webhosting was modified to :

  • Support smtpd_sender_restrictions (triggerd only on successful SASL login) instead of smtpd_data_restrictions (triggered when processing any outgoing mail)
  • As a consequence, the script is neutral for ISPConfig auto reply, auto forward, and any mail sent by Postfix without authentication (it will not count +1 on the quota for system mails, as long as your $mynetworks is configured accordingly)


Recommended installation:

$ cd /opt/
$ git clone ratelimit-policyd
$ cd ratelimit-policyd
$ chmod +x
$ ./

Create the DB schema and user:

$ mysql -u root -p < mysql-schema.sql
GRANT USAGE ON *.* TO policyd@'localhost' IDENTIFIED BY '********';
GRANT SELECT, INSERT, UPDATE, DELETE ON policyd.* TO policyd@'localhost';

Adjust configuration options in

my @allowedhosts    = ('', '');
my $LOGFILE         = "/var/log/ratelimit-policyd.log";
my $PIDFILE         = "/var/run/";
my $SYSLOG_IDENT    = "ratelimit-policyd";
my $SYSLOG_LOGOPT   = "ndelay,pid";
chomp( my $vhost_dir = `pwd`);
my $port            = 10032;
my $listen_address  = ''; # or ''
my $s_key_type      = 'email'; # domain or email
my $dsn             = "DBI:mysql:policyd:";
my $db_user         = 'policyd';
my $db_passwd       = '************';
my $db_table        = 'ratelimit';
my $db_quotacol     = 'quota';
my $db_tallycol     = 'used';
my $db_updatedcol   = 'updated';
my $db_expirycol    = 'expiry';
my $db_wherecol     = 'sender';
my $deltaconf       = 'daily'; # hourly|daily|weekly|monthly
my $defaultquota    = 1000;
my $sql_getquota    = "SELECT $db_quotacol, $db_tallycol, $db_expirycol FROM $db_table WHERE $db_wherecol = ? AND $db_quotacol > 0";
my $sql_updatequota = "UPDATE $db_table SET $db_tallycol = $db_tallycol + ?, $db_updatedcol = NOW(), $db_expirycol = ? WHERE $db_wherecol = ?";
my $sql_updatereset = "UPDATE $db_table SET $db_tallycol = ?, $db_updatedcol = NOW(), $db_expirycol = ? WHERE $db_wherecol = ?";
my $sql_insertquota = "INSERT INTO $db_table ($db_wherecol, $db_quotacol, $db_tallycol, $db_expirycol) VALUES (?, ?, ?, ?)";

Take care of using a port higher than 1024 to run the script as non-root (our init script runs it as user "postfix").

In most cases, the default configuration should be fine. Just don't forget to paste your DB password in $db_password.

Now, start the daemon:

$ service ratelimit-policyd start


Check if the daemon is really running:

$ netstat -tl | grep 10032
tcp        0      0 localhost.localdo:10032 *:*                     LISTEN

$ cat /var/run/

$ ps aux | grep
postfix  30566  0.4  0.1 176264 19304 ?        Ssl  14:37   0:00 /opt/send_rate_policyd/

$ pstree -p | grep ratelimit
        |                        |-{/opt/ratelimit-}(11301)
        |                        |-{/opt/ratelimit-}(11302)
        |                        |-{/opt/ratelimit-}(14834)
        |                        |-{/opt/ratelimit-}(15001)
        |                        |-{/opt/ratelimit-}(15027)
        |                        |-{/opt/ratelimit-}(15058)
        |                        `-{/opt/ratelimit-}(15065)

Print the cache content (in shared memory) with update statistics:

$ service ratelimit-policyd status
Printing shm:
Domain		:	Quota	:	Used	:	Expire
Threads running: 6, Threads waiting: 2

Postfix Configuration

Modify the postfix data restriction class smtpd_sender_restrictions like the following, /etc/postfix/

smtpd_sender_restrictions = check_policy_service inet:$IP:$PORT

sample configuration (chained with classic SASL authentication from MySQL):

smtpd_sender_restrictions = check_sender_access mysql:/etc/postfix/, check_policy_service inet:

If you're sure that ratelimit-policyd is really running, restart Postfix:

$ service postfix restart


Detailed logging is written to ``/var/log/ratelimit-policyd.log```. In addition, the most important information including the counter status is written to syslog:

$ tail -f /var/log/ratelimit-policyd.log 
Sat Jan 10 12:08:37 2015 Looking for
Sat Jan 10 12:08:37 2015 07F452AC009F:[], sasl_method=PLAIN,, recipient_count=1, curr_count=6/1000, status=UPDATE

$ grep ratelimit-policyd /var/log/syslog
Jan 10 12:08:37 mx1 ratelimit-policyd[2552]: 07F452AC009F:[], sasl_method=PLAIN,, recipient_count=1, curr_count=6/1000, status=UPDATE
You can’t perform that action at this time.