Optional — Quotas

 Published on 16 Feb 2025 .  Filed in Projects .  1056 words

Quotas 1 are size limits in terms of disk space for users. You can ensure that users do not waste arbitrary amounts of disk space, but are forced to clean up old emails from time to time.

The magic happens in two places:

  1. Postfix needs to reject new emails if the user’s mailbox is over quota.
  2. Dovecot needs to keep track of the quota and how much the user has already used up of it.

Enable Quota Plugin

Uncomment or add the following line to /etc/dovecot/conf.d/10-mail.conf file to enable the quota plugin 2:

  mail_plugins = quota

Quota Policy Service

Let’s start with Dovecot. Edit the file /etc/dovecot/conf.d/90-quota.conf. There are several plugin {…} sections. Take one and make it look like:

plugin {
  quota = count:User quota
  quota_vsizes = yes

  quota_status_success = DUNNO
  quota_status_nouser = DUNNO
  quota_status_overquota = "452 4.2.2 Mailbox is full and cannot receive any more emails"
}

The first line defines that you want to calculate the used space in a user’s maildir. There are several backends but the count 3 is the best choice in this context. The string User quota is just an arbitrary string that may be queried from a mail user agent.

The lines starting with quota_status_… set return values for the service that you will set up in a minute. It will tell Postfix that it will not interfere (DUNNO – colloquial way to say "I don’t know"). And it will return a string with a return code 452 if the user is over quota. Codes starting with 4 mean temporary errors. It will tell the sending party that it is worth retrying at a later time. However if the user does not resolve the issue it will lead to a bounce error email after three days.

In the same file (90-quota.conf) add another section:

service quota-status {
  executable = /usr/lib/dovecot/quota-status -p postfix
  unix_listener /var/spool/postfix/private/quota-status {
    user = postfix
  }
}

This creates a new Dovecot service responding to requests from other processes. You surely recognize that we put it into the jail that Postfix runs in (/var/spool/postfix), so that Postfix can access it.

Restart Dovecot:

sudo systemctl restart dovecot

Take a look at the /var/spool/postfix/private directory. If all went as intended you will find a socket file called quota-status there. Otherwise please check the /var/log/mail.log file for errors.

Configure Postfix to Reject When Over Quota

If we stopped here, then Dovecot would reject emails for users who have no space left. However Postfix would still happily receive new emails and attempt to forward them to Dovecot via LMTP. Dovecot however will deny that. It will then keep the email in its queue and retry for a while. In the end it will send a bounce back to the sender telling them about the problem.

Why is this bad ?

  1. The sender will assume that the email was delivered while it is stuck in the queue for up to three days.
  2. Spam emails use forged senders. So at the time that Postfix generates the bounce email it will likely send it to an innocent person. This is called backscatter and considered a mail server misconfiguration. Such a problem may get your mail server blacklisted.

So the next logical step is to make Postfix check whether a mailbox is over quota whenever a new email arrives. Let’s hook up into the RCPT TO phase of the SMTP dialog when a new email comes in. Postfix checks its smtpd_recipient_restrictions configuration at this stage. Run this command in the shell:

  sudo postconf smtpd_recipient_restrictions=reject_unauth_destination,"check_policy_service unix:private/quota-status"

This adds two checks:

  1. reject_unauth_destination checks whether the mail server is the final destination for the recipient’s email address. This is pretty much the default behavior if you do not define any restrictions.
  2. check_policy_service connects to the socket file at /var/spool/postfix/private/quota-status that was put there by Dovecot. It will use it to ask Dovecot whether the user is over quota in which case the email would get rejected.

Test Quota

Les's set user1’s mailbox quota to 5 KB:

update virtual_users set quota=4000 where email='user1@example1.com';

Send him a few emails using the swaks tool:

  swaks --server localhost --to user1@example1.com

After a few emails you will see the rejection message:

-> RCPT TO:user1@example1.com
 <** 452 4.2.2 user1@example.org: Recipient address rejected: Mailbox is full and cannot receive any more emails

TIPS: 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 user1@example1.com

Send Warning Email

The last step is to inform the poor users if they went over quota. So that they do not recognize that on their own. Let’s do that by sending them an email with a warning. We will make sure that the warning email gets through even if the quota is reached.

Edit the /etc/dovecot/conf.d/90-quota.conf file and add this section to the file:

plugin {
   quota_warning = storage=95%% quota-warning 95 %u
   quota_warning2 = storage=80%% quota-warning 80 %u
}
service quota-warning {
   executable = script /usr/local/bin/quota-warning.sh
   unix_listener quota-warning {
     user = vmail
     group = vmail
     mode = 0660
   }
}

This section defines two automatic quota warnings. The first (quota_warning) is triggered if the user reaches 95% of the quota. The second (quota_warning2) at 80%. These lines follow this schema:

  • Trigger (e.g. storage=95%). The % sign needs to be used twice if you want to emit a literal percent sign. So this is not a typo.
  • The socket you want to call in that case. Our socket is the service quota-warning that calls a shell script.
  • Additional parameters that are passed to the shell script in our case. They tell the script the percentage that has been reached (e.g. 95) and the address of the user who should get the warning.

No we create a script file at /usr/local/bin/quota-warning.sh:

  #!/bin/sh
  PERCENT=$1
  USER=$2
  cat << EOF | /usr/lib/dovecot/dovecot-lda -d $USER -o "plugin/quota=maildir:User quota:noenforcing"
  From: postmaster@webmail.example.org
  Subject: Quota warning - $PERCENT% reached

  Your mailbox can only store a limited amount of emails.
  Currently it is $PERCENT% full. If you reach 100% then
  new emails cannot be stored. Thanks for your understanding.
  EOF

Make this file executable:

  sudo chmod +x /usr/local/bin/quota-warning.sh

Restart Dovecot:

sudo systemctl restart dovecot

Footnotes