What it shows
Exim’s own tools (exim -bp, exiqsumm, exigrep) each answer one question. When the queue is at 30,000 and the IP is blacklisted you need all of them at once. The report has eight sections:
Table of Contents
- Queue size, frozen count and age of the oldest message.
- Top senders and top recipient domains currently in the queue.
- Top authenticated senders in the last 24 hours (
A=dovecot_login:lines) — the compromised-mailbox check. - Top sending scripts in the last 24 hours (
cwd=lines) — the hacked-plugin check. - Top local users sending via PHP
mail()/sendmail (U=lines). - Deferred, bounced and completed counts for the last 24 hours.
- The most common delivery error strings, normalised so the same error from different hosts groups together.
Usage
curl -fsSL https://srvscripts.com/get/exim-mail-queue-report/ -o exim-mail-queue-report.sh
bash exim-mail-queue-report.sh # report only
bash exim-mail-queue-report.sh --top=20 # longer lists
bash exim-mail-queue-report.sh --purge-frozen # asks before deleting frozen mail
bash exim-mail-queue-report.sh --purge-frozen --yes
Sample output
== Queue ==
Messages in queue: 18342 Frozen: 11207
Oldest message age: 2d
== Top 10 authenticated senders, last 24h (compromised mailbox check) ==
9412 info@clientdomain.com
31 sales@otherclient.com
== Top 10 scripts sending mail, last 24h (cwd=) ==
2210 cwd=/home/clientb/public_html/wp-content/uploads/2024/03
Two problems, two lines: a phished mailbox and a malicious upload. The outgoing spam guide walks through what to do with each.
Tested on a real server
We ran this script on our lab server on 5 October 2026: AlmaLinux 9.8 with cPanel & WHM 11.138, MariaDB 10.11 and three WordPress test accounts. The screenshot is the real terminal output; only IP addresses are masked.

Notes
Frozen messages are almost always bounces that can never be delivered (spam replies to forged senders); purging them is safe and is the single fastest way to shrink a runaway queue. Everything else in the script is read-only. Log analysis reads /var/log/exim_mainlog; if you rotate logs hourly, the 24-hour window will be shorter than it says.
Exim mail queue report at a glance


Official documentation: Exim documentation, cPanel & WHM documentation, RFC 5321 (SMTP).
Related guides: Incident response after a cPanel root-escalation CVE: rotating keys, hunting .sorry, auditing sessions · CSF after ConfigServer: which fork should you run in 2026 (cPanel, DirectAdmin, Aetherinox, Sentinel)? · Using WP Toolkit Security Risk scores, Smart Update and Vulnerable Components.
The script
#!/usr/bin/env bash
# Exim Mail Queue Report (v1.0.0) - from srvScripts.com
# Source, docs and updates: https://srvscripts.com/scripts/exim-mail-queue-report/
# Copyright (c) 2026 srvScripts.com. MIT licence: if you copy, share or adapt this script, keep this notice and credit srvScripts.com.
# exim-mail-queue-report.sh — find out what is filling the Exim queue on a cPanel server
# https://srvscripts.com/scripts/exim-mail-queue-report/ License: MIT
#
# Read-only by default. Shows queue size, frozen count, top senders, top recipients,
# top authenticated users and top sending scripts (from the last 24h of exim_mainlog).
# bash exim-mail-queue-report.sh # report
# bash exim-mail-queue-report.sh --purge-frozen # also delete frozen messages (asks first)
# bash exim-mail-queue-report.sh --purge-frozen --yes
set -u
[[ $EUID -ne 0 ]] && { echo "Run as root." >&2; exit 1; }
command -v exim >/dev/null 2>&1 || { echo "exim not found." >&2; exit 1; }
PURGE=0; YES=0; N=10
for a in "$@"; do case "$a" in --purge-frozen) PURGE=1 ;; --yes) YES=1 ;; --top=*) N=${a#--top=} ;; esac; done
LOG=/var/log/exim_mainlog; [[ -r $LOG ]] || LOG=/var/log/exim4/mainlog
hr() { printf '\n== %s ==\n' "$*"; }
hr "Queue"
total=$(exim -bpc 2>/dev/null)
frozen=$(exim -bpr 2>/dev/null | grep -c '\*\*\* frozen \*\*\*')
echo "Messages in queue: ${total:-0} Frozen: $frozen"
oldest=$(exim -bpr 2>/dev/null | awk '/^ *[0-9]+[hdm] /{print $1; exit}')
[[ -n "$oldest" ]] && echo "Oldest message age: $oldest"
hr "Top $N senders in queue"
exim -bpr 2>/dev/null | grep -Eo '<[^>]+>' | sort | uniq -c | sort -rn | head -n "$N"
hr "Top $N recipient domains in queue"
exim -bpr 2>/dev/null | awk '/^ +[^ ]+@/{print $1}' | awk -F@ '{print $2}' | sort | uniq -c | sort -rn | head -n "$N"
if [[ -r $LOG ]]; then
since=$(date -d '24 hours ago' '+%Y-%m-%d %H' 2>/dev/null)
recent() { awk -v s="$since" 'substr($0,1,13) >= s' "$LOG"; }
hr "Top $N authenticated senders, last 24h (compromised mailbox check)"
recent | grep -Eo 'A=(dovecot|courier|login|plain)[^ ]*:[^ ]+' | awk -F: '{print $2}' | sort | uniq -c | sort -rn | head -n "$N"
hr "Top $N scripts sending mail, last 24h (cwd=)"
recent | grep -Eo 'cwd=[^ ]+' | grep -v 'cwd=/$' | sort | uniq -c | sort -rn | head -n "$N"
hr "Top $N local users sending via PHP/sendmail, last 24h (U=)"
recent | grep -E '<= ' | grep -Eo ' U=[^ ]+' | sort | uniq -c | sort -rn | head -n "$N"
hr "Deferred / bounced in last 24h"
printf 'Deferred (==): %s Bounced (**): %s Completed: %s\n' \
"$(recent | grep -c ' == ')" "$(recent | grep -c ' \*\* ')" "$(recent | grep -c 'Completed')"
hr "Most common delivery errors, last 24h"
recent | grep -E ' (==|\*\*) ' | sed -E 's/.* (==|\*\*) [^ ]+ //; s/[0-9a-zA-Z.-]+\[[0-9.]+\]//g; s/[0-9]{3}[- ]/ /g' | cut -c1-110 | sort | uniq -c | sort -rn | head -n 8
else
echo "exim_mainlog not readable; skipping 24h analysis."
fi
if (( PURGE )) && (( frozen > 0 )); then
hr "Purge frozen"
if (( ! YES )); then read -rp "Delete $frozen frozen messages? [y/N] " a; [[ "$a" =~ ^[Yy]$ ]] || { echo "Skipped."; exit 0; }; fi
exim -bpr | grep '\*\*\* frozen \*\*\*' | awk '{print $3}' | xargs -r exim -Mrm >/dev/null
echo "Done. Queue now: $(exim -bpc)"
fi
5c9156a4cae34ecd9cbd0e308ce1e3ffd80901cd5f6fb3fb80c81e30cfd4123acurl -fsSL -o exim-mail-queue-report.sh https://scr.srvscripts.com/exim-mail-queue-report/exim-mail-queue-report.sh && curl -fsSL https://scr.srvscripts.com/exim-mail-queue-report/exim-mail-queue-report.sh.sha256 | sha256sum -cInvoke-WebRequest -Uri 'https://scr.srvscripts.com/exim-mail-queue-report/exim-mail-queue-report.sh' -OutFile 'exim-mail-queue-report.sh'; if ((Get-FileHash 'exim-mail-queue-report.sh' -Algorithm SHA256).Hash -eq '5C9156A4CAE34ECD9CBD0E308CE1E3FFD80901CD5F6FB3FB80C81E30CFD4123A') { 'OK: the file is intact' } else { 'MISMATCH: do not run this file' }Copy the whole line. In Windows PowerShell, curl and sha256sum are not the Linux tools, so use the PowerShell line there.Also on GitHub: github.com/srvscripts/scripts
Frequently asked questions
What does the Exim mail queue report show?
Queue size and age, top senders and recipient domains, authenticated senders, sending scripts, local mail() users and the most common delivery errors.
Does it change the queue?
No. It only reads the queue and logs. Deleting or freezing messages is left to you.
Does it work on DirectAdmin?
Yes. It reads standard Exim logs and commands, which cPanel and DirectAdmin both use.