
Report Mail Users Over Quota with Cron
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 —
mailfrom themailutilspackage, or asendmailbinary (iRedMail gives you Postfix, sosendmailis 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-Areturns 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 pipefailmakes the script fail loudly rather than silently sending a half-baked report.- The
awkuses-F'\t'because we asked doveadm for tab output.$6 ~ /^[0-9]+$/skips users whose percent is-(no limit). $6 + 0 >= tforces 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 0block 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
0and no email means nobody is over threshold. Confirm that's true by loweringTHRESHOLDtemporarily (say to1) 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 -Ashows 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.
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.
Related guides
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.
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.
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.
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.




