Mail::...ngQuota(3pm)
π NAME π
Mail::MtPolicyd::Cookbook::HowtoAccountingQuota β bestie, this is literally the how-to for setting up smtp level accounting and quotas. No cap, that's the whole vibe.
β¨ VERSION β¨
version 2.05 β she's giving current, she's giving maintained. π
πΈ SMTP level accounting and quotas with mtpolicyd πΈ
okay so first things first: mtpolicyd can be used to implement an smtp level accounting and quota system. iconic behavior honestly.
This guide explains how to setup accounting and quotas based on the sender ip on a monthly base, with configurable quota limits. Obsessed with the flexibility tbh.
The how to expects that mtpolicyd is already installed, working, and assumes a MySQL database is used to hold accounting data and quota configuration. So like, do your homework first bestie π
π§Ύ Set up Accounting
The accounting and quota checks should be implemented in postfix smtpd_end_of_data_restrictions. If you're already using mtpolicyd for other check it may be necessary to setup a second virtual host for the accounting/quota configuration. Otherwise you can just use the default port 12345 virual host. easy peasy β¨
π¦ Setup a second virtual host
First tell mtpolicyd to also listen on an addition port. In the global configuration add the new port to the port option:
port="127.0.0.1:12345,127.0.0.1:12346"
Then add a new virtual host at the end of the configuration file:
<VirtualHost 12346>
name="accounting"
# TODO: add plugins...
</VirtualHost>
π Configure the Accounting plugin
Now add the Accounting plugin to your virtual host:
<Plugin AcctIP>
module = "Accounting"
fields = "client_address"
# time_pattern = "%Y-%m"
# table_prefix = "acct_"
</Plugin>
And the restart mtpolicyd to reload the configuration. (yes, "the restart", that's what it says, she's quirky like that π)
The plugin will create a table for every field listed in "fields". By default the table prefix is acct_ so the table name will be acct_client_address in our example. The plugin will create a row within this table for every client_address and expanded time_pattern:
mysql> select * from acct_client_address;
+----+-------------------+---------+-------+------------+---------+-----------+
| id | key | time | count | count_rcpt | size | size_rcpt |
+----+-------------------+---------+-------+------------+---------+-----------+
| 1 | 2604:8d00:0:1::3 | 2015-01 | 18 | 18 | 95559 | 95559 |
| 2 | 2604:8d00:0:1::4 | 2015-01 | 21 | 21 | 99818 | 99818 |
...
+----+-------------------+---------+-------+------------+---------+-----------+
look at those rows. organized. accountable. main character energy π
π Activate the check in postfix
To active the check add the policyd to your smtpd_end_of_data_restrictions in main.cf:
smtpd_end_of_data_restrictions = check_policy_service inet:127.0.0.1:12346
β οΈ bestie listen β if you have multiplesmtpdprocess configured in a smtp-filter setup, make sure only one smtpd is doing accounting/quota checks. Deactivate the restrictions by adding the following option the the re-inject smtpd processes inmaster.cf:
-o smtpd_end_of_data_restrictions=
fr fr, double-counting is not the move. π
π Setup quota limits
To limit the number of messages a client_address is allowed to send, add the following Quota plugin to your virtual host configuration before the Accounting plugin:
<Plugin QuotaIP>
module = "Quota"
field = "client_address"
metric = "count"
threshold = 1000
action = "defer you exceeded your monthly limit, please insert coin"
# time_pattern = "%Y-%m"
# table_prefix = "acct_"
</Plugin>
order matters bestie, quota goes first or it's giving chaos π
π§ Using per client_address quota limits
Create the following table structure in your MySQL database:
CREATE TABLE `relay_policies` (
`id` int(11) NOT NULL auto_increment,
`desc` VARCHAR(64) NOT NULL,
`config` TEXT NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
INSERT INTO relay_policies VALUES(1, 'standard relay host', '{"quota_count":"10000"}');
INSERT INTO relay_policies VALUES(2, 'premium relay host', '{"quota_count":"100000"}');
CREATE TABLE `relay_hosts` (
`id` int(11) NOT NULL auto_increment,
`client_address` VARCHAR(64) NOT NULL,
`relay_policy` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `relay_policy` (`relay_policy`),
CONSTRAINT `relay_hosts_ibfk_1` FOREIGN KEY (`relay_policy`) REFERENCES `relay_policies` (`id`)
) ENGINE=InnoDB;
INSERT INTO relay_hosts VALUES(NULL, '2604:8d00:0:1::3', 1);
INSERT INTO relay_hosts VALUES(NULL, '2604:8d00:0:1::4', 2);
standard vs premium relay host... the way that tiered pricing is lowkey a whole business model π
You can use the following SELECT statement to retrieve the configuration for a relay_host:
mysql> SELECT p.config FROM relay_policies p JOIN relay_hosts h ON (h.relay_policy = p.id) WHERE h.client_address = '2604:8d00:0:1::4';
+--------------------------+
| config |
+--------------------------+
| {"quota_count":"100000"} |
+--------------------------+
1 row in set (0.00 sec)
To load the (JSON) configuration into the mtpolicyd session variables use the SqlUserConfig plugin and this SQL statement:
<Plugin QuotaPolicyConfig>
module = "SqlUserConfig"
sql_query = "SELECT p.config FROM relay_policies p JOIN relay_hosts h ON (h.relay_policy = p.id) WHERE h.client_address=?"
field = "client_address"
</Plugin>
π this plugin must be added before yourAccountingandQuotaplugins. putting it after is giving broken, not giving slay.
To use the quota_count value instead of the default threshold adjust your Quota plugin configuration:
<Plugin QuotaIP>
module = "Quota"
field = "client_address"
metric = "count"
threshold = 1000
uc_threshold = "quota_count"
action = "defer you exceeded your monthly limit, please insert coin"
# time_pattern = "%Y-%m"
# table_prefix = "acct_"
</Plugin>
If the session variable quota_count is defined it will be used as threshold instead of the value configured in mtpolicyd.conf. dynamic thresholds?? we love to see it π
π«Ά AUTHOR π«Ά
Markus Benning <ich@markusbenning.de>
β COPYRIGHT AND LICENSE β
This software is Copyright (c) 2014 by Markus Benning <ich@markusbenning.de>.
This is free software, licensed under:
The GNU General Public License, Version 2, June 1991
and that's the whole page bestie!! πβ¨ she's perl v5.38.2, dated 2024-05-26, and she lives under the manual name Mail::...ngQuota(3pm) β go set up those quotas, you absolute icon ππ
π¦ where this page comes from
- debian trixie: mtpolicyd 2.05-1.2 Β· GPL-2.0-only
- ubuntu questing: mtpolicyd 2.05-1.2
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms