296 lines
9.9 KiB
Text
296 lines
9.9 KiB
Text
---
|
||
title: Quotas
|
||
lastUpdated: 2025-12-28
|
||
slug: ispmail-trixie/quotas
|
||
sidebar:
|
||
order: 330
|
||
---
|
||
|
||
import { Aside } from "@astrojs/starlight/components";
|
||
|
||
<Aside type="tip" title="Optional feature">
|
||
This feature is completely optional. If you are eager to get finished then skip this page and maybe come back later.
|
||
</Aside>
|
||
|
||
<Aside type="danger" title="Potential breakage with virtual_aliases">
|
||
There is an unsolved issue with `virtual_aliases` if one of the recipients is over quota. I am currently investigating
|
||
that. See [Julien's
|
||
comment](https://workaround.org/ispmail-trixie/quotas/#comentario-e1cdd47e-fa21-4213-83c3-1eb4fbd030f5).
|
||
</Aside>
|
||
|
||
Quotas are size limits for users. You can make sure that users do not waste arbitrary amounts of disk space but are
|
||
forced to clean up old emails every now and then.
|
||
|
||
The magic happens in two places:
|
||
|
||
1. Dovecot needs to keep track of the quota and how much the user has already used up of it.
|
||
1. Postfix needs to reject new emails if the user’s mailbox is over quota.
|
||
|
||
## Dovecot
|
||
|
||
### Enable quota service
|
||
|
||
As usual we need a new piece of configuration to enable the _quota_ plugin and define how we want it to behave:
|
||
|
||
```sh title="Run this on your server"
|
||
cat > /etc/dovecot/conf.d/99-ispmail-quota.conf << 'EOF'
|
||
# Enable the quota plugin
|
||
mail_plugins {
|
||
quota = yes
|
||
}
|
||
|
||
# Set global defaults. Keep this outside of a "quota {}" section so that it
|
||
# can be overriden with data from the virtual_users table.
|
||
# Allow 5 GB of space per default.
|
||
quota_storage_size = 5G
|
||
|
||
# It is important to allow it so that Postfix can deliver an email to
|
||
# bring the user above 100% of quota usage. Otherwise you risk that the user
|
||
# is at 99.99% of quota but the last email cannot be delivered and gets stuck
|
||
# between Postfix and Dovecot. No email should be larger than this.
|
||
quota_storage_grace = 50M
|
||
|
||
# Define two warnings at 80% and 95% of quota usage.
|
||
quota "User quota" {
|
||
# Set one warning level to 100%
|
||
warning warn-100 {
|
||
quota_storage_percentage = 100
|
||
execute quota-warning {
|
||
args = 100 %{user}
|
||
}
|
||
}
|
||
|
||
# Set another warning level to 80% for early warnings
|
||
warning warn-80 {
|
||
quota_storage_percentage = 80
|
||
execute quota-warning {
|
||
args = 80 %{user}
|
||
}
|
||
}
|
||
|
||
# User is no longer over quota
|
||
warning warn-under {
|
||
quota_storage_percentage = 100
|
||
threshold = under
|
||
execute quota-warning {
|
||
args = below %{user}
|
||
}
|
||
}
|
||
}
|
||
|
||
# Define what happens if the user goes over quota. Point to a script that
|
||
# creates a warning email to the user. The name "quota-warning" corresponds
|
||
# to the "execute" statement in the warning definitions.
|
||
service quota-warning {
|
||
executable = script /usr/local/bin/ispmail-quota-warning.sh
|
||
unix_listener quota-warning {
|
||
mode = 0660
|
||
user = vmail
|
||
group = vmail
|
||
}
|
||
}
|
||
|
||
# Add a new service so that Postfix can check if a user is over quota.
|
||
# It listens on TCP port 13373.
|
||
service quota-status {
|
||
executable = quota-status -p postfix
|
||
inet_listener quota-status {
|
||
listen = 127.0.0.1
|
||
port = 13373
|
||
}
|
||
client_limit = 1
|
||
}
|
||
|
||
# Enable the IMAP QUOTA extension, allowing IMAP clients to ask for the
|
||
# current quota usage. Roundcube displays the current space usage in
|
||
# the lower left corner.
|
||
protocol imap {
|
||
mail_plugins {
|
||
imap_quota = yes
|
||
}
|
||
}
|
||
EOF
|
||
|
||
# Apply the configuration
|
||
systemctl reload dovecot
|
||
```
|
||
|
||
The script that is launched by the `quota-warning` service is pretty simple. It sends an email to the affected user. It
|
||
uses the special way of `dovecot-lda -o quota_enforce=no` though so that the warning email itself is always delivered
|
||
even if the user is over quota.
|
||
|
||
```sh title="Run this on your server"
|
||
cat > /usr/local/bin/ispmail-quota-warning.sh << 'EOF'
|
||
#!/bin/bash
|
||
#
|
||
# This script is part of the ISPmail configuration.
|
||
# It is called from /etc/dovecot/conf.d/99-ispmail-quota.conf
|
||
# and sends an email to the user if the quota is at 80%, over 100%
|
||
# or back below 100%.
|
||
#
|
||
# Christoph Haas <ispmail@christoph-haas.de>
|
||
# Version 1.0 – 2025-12-30
|
||
|
||
LEVEL=$1
|
||
USER=$2
|
||
MAIL="From: postmaster@${USER#*@}"
|
||
MAIL+="\nSubject: Quota information"
|
||
MAIL+="\nX-Priority: 1"
|
||
MAIL+="\n\nThis is an automatic email from your friendly mail server.\n\n"
|
||
|
||
if [ $LEVEL = "below" ]; then
|
||
MAIL+="Thanks for making some space. You will now be able to receive emails again."
|
||
elif [ $LEVEL -eq 80 ]; then
|
||
MAIL+="Your mailbox is currently using up 80% of the maximum space."
|
||
MAIL+="\nPlease consider deleting some emails. If your used space reached"
|
||
MAIL+="\n100% then you would not be able to receive further emails. I will"
|
||
MAIL+="\nhowever send you another email in that case."
|
||
MAIL+="\n"
|
||
MAIL+="\nThanks for your attention."
|
||
elif [ $LEVEL -eq 100 ]; then
|
||
MAIL+="I am afraid to tell you that your mailbox is now 100% full."
|
||
MAIL+="\nNew emails to your address will be rejected by the server."
|
||
MAIL+="\nPlease delete some emails immediately. I will send you another"
|
||
MAIL+="\nemail once you have made some space."
|
||
MAIL+="\n"
|
||
MAIL+="\nThanks for your attention."
|
||
fi
|
||
|
||
echo -e $MAIL | /usr/lib/dovecot/dovecot-lda -d $USER -o quota_enforce=no
|
||
EOF
|
||
|
||
chmod u=rwx,g=rx,o= /usr/local/bin/ispmail-quota-warning.sh
|
||
chown vmail:vmail /usr/local/bin/ispmail-quota-warning.sh
|
||
```
|
||
|
||
### Global versus local
|
||
|
||
By default you will probably allow all users to use a certain amount of disk space for their emails. That would be the
|
||
global value that you set using `quota_storage_size = …`.
|
||
|
||
But maybe you have some special users that need a different value. For a power user you could allow more space than for
|
||
other users. Then set the `quota` field in his record in the `virtual_users` table to a value larger than 0.
|
||
|
||
So the logic for the `quota` field in the database is:
|
||
|
||
- **0** means: take the global setting of `quota_storage_size`
|
||
- **anything else**: override by this value (in bytes)
|
||
|
||
We added that logic into the `userdb sql` section earlier:
|
||
|
||
```sql
|
||
IF(quota > 0, CONCAT(quota, 'B'), NULL) AS quota_storage_size
|
||
```
|
||
|
||
That's the SQL way of saying: if "quota" is 0, then return NULL. NULL tells Dovecot to fall back to the global value.
|
||
Otherwise add a "B" (for "bytes" – Dovecot requires that) and return that.
|
||
|
||
### Showing current usage
|
||
|
||
Now that quota restrictions are in place, you can ask Dovecot for the current status of all mailboxes:
|
||
|
||
```sh title="Run this on your server"
|
||
doveadm quota get -A
|
||
```
|
||
|
||
You will get output like this:
|
||
|
||
```
|
||
Username Quota name Type Value Limit %
|
||
john@example.org User quota STORAGE 22856 1048576 2
|
||
john@example.org User quota MESSAGE 19 - 0
|
||
```
|
||
|
||
John is shown twice because Dovecot keeps track of:
|
||
|
||
- the **amount of storage** John uses (`STORAGE`)
|
||
- the **number of emails** that John has (`MESSAGE`)
|
||
|
||
The current configuration just cares about the space that is used on disk. So in the above example he has 22,856 KB used
|
||
of the 1 GB limit – which is 2%. And he has a total of 19 messages but there is no limit to that.
|
||
|
||
### Testing the quota service (optional)
|
||
|
||
You may wonder why there is a `quota-status` service. If a user is over quota then it would reject an more incoming
|
||
emails. But Postfix – which sits in front front of Dovecot – would still happily accept more emails. For a sender on a
|
||
remote server it looks like the email was delivered. But it's now stuck between Postfix and Dovecot until the user makes
|
||
some space.
|
||
|
||
The obvious solution: Postfix needs to know if a user is over quota so that it can reject further emails. That's the
|
||
purpose of the `quota-status` service which is now listening on TCP port 13373 (on the _local_ interface) for such
|
||
queries. The protocol is very simple and you can even play with it as a human:
|
||
|
||
```sh
|
||
echo -e "recipient=john@example.org\n" | nc localhost 13373
|
||
```
|
||
|
||
And the service will respond with:
|
||
|
||
```
|
||
action=OK
|
||
```
|
||
|
||
The response tells you whether the user is over quota:
|
||
|
||
- `action=OK` or
|
||
- `action=554 5.2.2 Quota exceeded (mailbox for user is full)`
|
||
|
||
## Postfix
|
||
|
||
Telling Postfix to use the `quota-status` service is suprisingly simple:
|
||
|
||
```sh title="Run this on your server"
|
||
postconf smtpd_recipient_restrictions="check_policy_service inet:localhost:13373"
|
||
```
|
||
|
||
---
|
||
|
||
### Test it
|
||
|
||
If you are curious to see this working, then set John’s mailbox quota to 5 KB:
|
||
|
||
```sql
|
||
# mariadb mailserver
|
||
mysql> update virtual_users set quota=5120 where email='john@example.org';
|
||
```
|
||
|
||
Verify that he now has a limit of 5 KB:
|
||
|
||
```sh
|
||
doveadm quota get -u john@example.org
|
||
```
|
||
|
||
Send him a few emails using the ‘swaks’ tool:
|
||
|
||
```
|
||
swaks --server localhost --to john@example.org -tls
|
||
```
|
||
|
||
After a few emails you will see the rejection message:
|
||
|
||
```
|
||
-> RCPT TO:john@example.org
|
||
<** 452 4.2.2 john@example.org: Recipient address rejected: Mailbox is full and cannot receive any more emails
|
||
```
|
||
|
||
### Troubleshooting
|
||
|
||
These are things you should consider if quotas do not seem to work properly:
|
||
|
||
- Check that your `/etc/dovecot/conf.d/99-ispmail-sql.conf` file looks as
|
||
[described](/ispmail-trixie/dovecot#99-ispmail-sqlconf). Earlier version of this Trixie guide did not contain the
|
||
`quota_storage_size` field in the `userdb sql` query.
|
||
- Run `tcpdump -Ai lo port 13373` on the server to see if Postfix talks to the `quota-status` service.
|
||
- Your users may complain that they have deleted many emails but are still over quota. Let them check if they actually
|
||
emptied the _Trash_ folder. Of course emails in that folder also contribute to the disk space usage. Once the Trash
|
||
folder is expunged the problem should be gone. You may also allow your users more space in the Trash folder. That’s
|
||
explained in the [Dovecot documentation](https://doc.dovecot.org/configuration_manual/quota/#quota-rules).
|
||
- If you directly remove files from a user’s Maildir instead of properly accessing the mailbox using IMAP then you will
|
||
screw up the quota calculation. In that case let Dovecot recalculate the quota:
|
||
`doveadm quota recalc -u john@example.org`
|
||
|
||
### See also
|
||
|
||
- https://doc.dovecot.org/2.4.2/core/plugins/quota.html#quota-service
|
||
- https://sys4.de/en/blog/postfix-dovecot-mailbox-quota/
|
||
- https://www.postfix.org/SMTPD_POLICY_README.html
|