Shore Up
a wall of pigeonhole mail slots, a few stuffed so full letters spill out, and a hand clipping a warning tag onto the overflowing ones
MailLinux

Report Mail Users Over Quota with Cron

Ketan Aagja9 min read
No ratings yet

Before you run this

This is a read-and-report job. It runs doveadm quota get to list every mail user's quota usage, picks out the ones over a percentage you set, and emails you a summary. It does not change quotas, delete mail, or touch mailboxes — nothing here is destructive.

It needs root (or a user allowed to run doveadm), because doveadm quota get -A iterates all users through Dovecot's userdb, which normally requires privileged access. I run it as root from root's crontab.

Even though it only reads, test it manually first on a non-production or staging mail server, or at least run the raw doveadm command by hand once before you wire it into cron. The one thing that varies between installs is the exact column layout of doveadm output, and the whole script depends on parsing that correctly. Read the script, run it once interactively, confirm the report looks right, then schedule it.

There is nothing to roll back — it writes no state. "Undo" is deleting the cron entry and the script file, covered at the end.

Assumptions for this guide:

  • Debian 12 / Ubuntu 22.04, bash, systemd.
  • Dovecot with the quota plugin enabled and a per-user storage quota configured (a standard iRedMail setup fits this exactly).
  • A working local mailer — mail from the mailutils package, or a sendmail binary (iRedMail gives you Postfix, so sendmail is present).
  • Users are iterable via doveadm ... -A. This works when your userdb supports iteration (SQL or LDAP userdb, as iRedMail uses). A flat passwd-file userdb may not iterate; if -A returns nothing, that is why.

If you are on RHEL/AlmaLinux the logic is identical; only the mailer package name differs (mailx instead of mailutils).

Confirm the command works before scripting anything

Run this by hand as root:

doveadm -f tab quota get -A

The -f tab asks doveadm for tab-separated output, which is far safer to parse than the default aligned table (the default "Quota name" column contains a space — User quota — which breaks naive whitespace splitting). You should get a header row and then rows like:

username	quota name	type	value	limit	%
user1@example.com	User quota	STORAGE	1048576	2097152	50
user1@example.com	User quota	MESSAGE	1200	-	0

Each user gets a STORAGE row (bytes) and a MESSAGE row (message count). We care about STORAGE. The last column is the percentage of the limit used; it shows - when that user has no limit set.

Check your own output before continuing. Count the columns. On a default Dovecot the order is username, quota name, type, value, limit, percent — so STORAGE is field 3 and the percentage is field 6. If your layout differs, adjust the field numbers ($3, $6) in the script to match. Do not assume mine matches yours; verify it.

If % shows - for users you expected to have a limit, their quota simply isn't set — that's a Dovecot config matter, not something this script fixes.

The script

Save this as /usr/local/sbin/mail-quota-report.sh.

#!/usr/bin/env bash
set -euo pipefail

# --- settings you edit ---
THRESHOLD=90                       # report users at/above this percent
REPORT_TO="admin@example.com"      # who gets the report
REPORT_FROM="root@$(hostname -f)"  # From: header
SUBJECT="Mailbox quota report: over ${THRESHOLD}% ($(hostname -s))"
# -------------------------

# Pull quota data as tab-separated text.
# Field 3 = type (we want STORAGE), field 6 = percent used, field 1 = user.
# Adjust $3/$6/$1 if YOUR `doveadm -f tab quota get -A` columns differ.
report="$(doveadm -f tab quota get -A 2>/dev/null | awk -F'\t' -v t="$THRESHOLD" '
  NR == 1 { next }                       # skip header row
  $3 == "STORAGE" && $6 ~ /^[0-9]+$/ {   # STORAGE rows with a numeric percent
    if ($6 + 0 >= t)
      printf "%-40s %s%%\n", $1, $6
  }
' | sort -k2 -n -r)"                      # busiest first

# Nothing over threshold -> stay quiet, exit clean.
if [[ -z "$report" ]]; then
  exit 0
fi

# Build the message body.
body="$(printf 'The following mailboxes are at or above %s%% of quota on %s (%s):\n\n%-40s %s\n%s\n%s\n' \
  "$THRESHOLD" "$(hostname -f)" "$(date '+%Y-%m-%d %H:%M %Z')" \
  "USER" "USED" "$(printf '%.0s-' {1..48})" "$report")"

# Send it. Uses `mail` from mailutils/mailx; swap for sendmail if you prefer.
printf '%s\n' "$body" | mail -s "$SUBJECT" -a "From: $REPORT_FROM" "$REPORT_TO"

A few notes on the non-obvious lines:

  • set -euo pipefail makes the script fail loudly rather than silently sending a half-baked report.
  • The awk uses -F'\t' because we asked doveadm for tab output. $6 ~ /^[0-9]+$/ skips users whose percent is - (no limit).
  • $6 + 0 >= t forces numeric comparison.
  • The script exits 0 and stays silent when nobody is over quota. If you'd rather get a daily "all clear", remove the early exit 0 block and always send.

Replace admin@example.com with your real recipient and change THRESHOLD to taste. example.com and the recipient are placeholders — substitute your own.

The -a "From: ..." header syntax is what GNU mailutils' mail accepts. If your mail is a different implementation, check man mail on the box for its "add header" flag rather than assuming — the option letter is not universal.

Make it executable:

chmod 750 /usr/local/sbin/mail-quota-report.sh

Schedule it in cron

Edit root's crontab:

sudo crontab -e

Add a daily run at 07:00:

# Daily mailbox quota report
0 7 * * * /usr/local/sbin/mail-quota-report.sh

Cron mails root any output a job produces on stdout/stderr. Because the script sends its own report and is otherwise silent, cron will only mail you if the script errors — which is exactly the signal you want. If you don't monitor root's local mail, add a MAILTO="you@example.com" line at the top of the crontab so error output reaches you.

Verify it works

Run it once by hand and check the outcome:

sudo /usr/local/sbin/mail-quota-report.sh; echo "exit: $?"
  • Exit 0 and no email means nobody is over threshold. Confirm that's true by lowering THRESHOLD temporarily (say to 1) and running again — you should now receive a report listing users. Set it back afterwards.
  • Check the report actually arrived in the destination mailbox, and that the percentages match what doveadm -f tab quota get -A shows for a known user:
doveadm quota get -u someone@example.com

Confirm the cron entry is registered:

sudo crontab -l | grep mail-quota-report

If a user's reported usage looks obviously wrong, that's a Dovecot cache issue, not the script. Recalculating quota is a separate, documented operation (doveadm quota recalc) — read the doveadm-quota manual page before running it, and know it changes Dovecot's stored quota accounting.

Undo

Nothing persistent was changed, so removing it is clean:

sudo crontab -e          # delete the "Daily mailbox quota report" line
sudo rm /usr/local/sbin/mail-quota-report.sh

That's the whole footprint.

For the exact meaning of the quota columns and the -f output formats, see the doveadm-quota and doveadm manual pages (man doveadm-quota, man doveadm) on your server, and the Dovecot quota documentation on dovecot.org. Trust the man page on your own box over any layout I've printed here — versions move, and this script only works if the fields it parses match what your Dovecot actually emits.

Written by
Ketan Aagja

Runs enterprise networks and security for a living, and writes Shore Up to turn two decades of hands-on Linux, Windows and mail-server work into guides you can actually use.

More about the author →

Was this article helpful?

Tap a star — no sign-in needed.

Be the first to rate this article.

Automate Dovecot Mailbox Quota Reports with a Shell Script

This guide builds a small shell script that runs doveadm quota get for every mailbox on a Dovecot server, writes a plain-text usage report to a file, and emails you a summary that flags anyone at or above a threshold (90% by default). It is a read-only reporting script. It queries quota figures that Dovecot already tracks; it does not create, resize, recalculate, or delete anything in a mailbox.

9 min read

Audit Mail User Permissions and Find Over-Privileged Accounts

This script is a read-only audit . It walks your virtual mailbox tree and a couple of mail config directories and reports three things: mailbox files or directories that are readable or writable by group/other, mailbox files not owned by the expected mail user, config files that contain secrets but aren't locked down, and the service account's login shell. It changes nothing — no chmod , no chown , no account edits. Remediation is a separate, manual step at the end that you run deliberately, one finding at a time.

11 min read

Correlate Fail2ban, Postfix and Dovecot Logs Into One Report

This is a read-only reporting script . It reads your Postfix/Dovecot mail log and your Fail2ban log, extracts the source IPs behind SMTP SASL failures, Postfix rejects and Dovecot auth failures, cross-references them against the IPs Fail2ban actually banned, and prints one ranked summary. It does not touch your firewall, your jails, your mail queue, or any config. There is nothing to undo except deleting the report file and removing the cron entry you add at the end.

10 min read

Alert on a Growing Postfix Mail Queue with a Cron Script

A mail queue that quietly grows is one of those failures you notice late — usually when a user asks why their mail from three hours ago hasn't arrived. A backlog can mean a dead relay host, a DNS problem, a downstream server rejecting everything, or an outbound spam run from a compromised account. This guide sets up a small cron job that counts the queue and emails you when it crosses a threshold, so you hear about it early.

9 min read