WHM/cPanel Tutorials

How to Track Who Deleted Emails in WHM/cPanel (With Ready-to-Run Audit Script)

cPanel WHM email deletion audit script LinuxBear.com
cPanel WHM email deletion audit script LinuxBear.com

Running a cPanel/WHM server means handling customer email issues on a daily basis. One of the most common (and frustrating) problems? A customer suddenly reports that all of their emails are missing. Was it deleted by a user? Did a POP3 client download and remove them? Or was there unauthorized access?

In this guide, we’ll walk you through:

  • How to troubleshoot missing emails in cPanel/WHM
  • How to check Dovecot, Exim, and cPanel logs
  • A ready-to-run Linux shell script that audits email deletions and logins
  • SEO-friendly best practices for system admins

Why Emails Disappear in cPanel

Before blaming the server, it’s important to understand why emails may vanish:

  1. POP3 Download – If a user configures their mail client (Outlook, Thunderbird, etc.) with POP3, it downloads and removes mail from the server.
  2. IMAP Deletion – If a user deletes mail from their phone or client, IMAP sync removes it everywhere.
  3. Webmail (Roundcube/Horde) – Users may delete emails via Webmail.
  4. Filters/Forwarders – Misconfigured filters may silently delete or forward mail.
  5. Compromised Account – If the password was leaked, an attacker might have accessed and deleted emails.

Step-by-Step Troubleshooting

You can check mail logs manually:

# Check if emails were expunged (deleted via IMAP)
grep 'Expunge' /var/log/maillog | grep 'user@domain.com'


# Check POP3 activity
grep 'POP3' /var/log/maillog | grep 'user@domain.com'


# Check Webmail & cPanel access logs
grep 'user@domain.com' /usr/local/cpanel/logs/access_log

But running all these commands repeatedly is painful. That’s why we built a mail audit script.

Mail Audit Script for cPanel/WHM

Here’s a complete script that scans current and rotated logs for IMAP deletions, POP3 downloads, and Webmail logins. It even summarizes the IP addresses involved.

Save as /root/mail_audit.sh, make executable, then run with the target email (and optionally the cPanel username if you know it).

bash /root/mail_audit.sh -e user@domain.com -u CPANELUSER
#!/usr/bin/env bash
# mail_audit.sh β€” Audit who deleted/downloaded emails for a cPanel mailbox
# Works on WHM/cPanel servers using Dovecot/Exim. Scans current + rotated logs.
# Usage:
#   ./mail_audit.sh -e user@domain.com [-u CPANEL_USERNAME]
#
# Examples:
#   ./mail_audit.sh -e info@example.com
#   ./mail_audit.sh -e sales@example.com -u example

set -euo pipefail

EMAIL=""
CPUSER=""

while getopts ":e:u:" opt; do
  case $opt in
    e) EMAIL="$OPTARG" ;;
    u) CPUSER="$OPTARG" ;;
    \?) echo "Invalid option: -$OPTARG" >&2; exit 1 ;;
    :) echo "Option -$OPTARG requires an argument." >&2; exit 1 ;;
  esac
done

if [[ -z "${EMAIL}" ]]; then
  echo "Usage: $0 -e user@domain.com [-u CPANEL_USERNAME]"
  exit 1
fi

DOMAIN="${EMAIL#*@}"
LOCALPART="${EMAIL%@*}"

ts() { date +"%Y-%m-%d %H:%M:%S"; }

log_files=(
  /var/log/maillog
  /var/log/maillog-*
  /var/log/dovecot
  /var/log/dovecot-info.log
  /var/log/dovecot*
  /var/log/exim_mainlog
  /var/log/exim_mainlog-*
  /usr/local/cpanel/logs/access_log
  /usr/local/cpanel/logs/access_log-*
  /var/cpanel/roundcube/log/*
)

# Also search per-account Roundcube logs if CPUSER provided
if [[ -n "${CPUSER}" ]]; then
  log_files+=("/home/${CPUSER}/logs/roundcube/*" "/home/${CPUSER}/etc/${DOMAIN}/logs/roundcube/*")
fi

# De-duplicate existing files
declare -A seen
existing_files=()
for f in "${log_files[@]}"; do
  for m in $f; do
    if [[ -e "$m" ]] && [[ -z "${seen[$m]:-}" ]]; then
      existing_files+=("$m")
      seen[$m]=1
    fi
  done
done

if [[ ${#existing_files[@]} -eq 0 ]]; then
  echo "[$(ts)] No log files found. Are you running on a WHM/cPanel server?"
  exit 1
fi

# Grep wrapper that auto zgreps .gz
g() {
  local pattern="$1"; shift
  local file="$1"
  if [[ "$file" =~ \.gz$ ]]; then
    zgrep -i --no-filename --line-number "$pattern" "$file" 2>/dev/null || true
  else
    grep -i --no-filename --line-number "$pattern" "$file" 2>/dev/null || true
  fi
}

separator() {
  echo "========================================================================"
}

echo
separator
echo "[ $(ts) ] MAIL AUDIT REPORT for ${EMAIL}"
separator
echo

# 1) IMAP Expunge / Delete events (Dovecot)
echo "1) IMAP delete events (Dovecot 'expunge'/'deleted'):"
echo "-----------------------------------------------------------------------"
expunge_tmp=$(mktemp)
for f in "${existing_files[@]}"; do
  g "expunge|deleted|delete" "$f" | grep -i "$EMAIL" || true
done | tee "$expunge_tmp"
if [[ ! -s "$expunge_tmp" ]]; then
  echo "No IMAP delete/expunge lines found for ${EMAIL}."
fi
echo

# 2) POP3 downloads (messages pulled from server)
echo "2) POP3 access (client may download & remove from server):"
echo "-----------------------------------------------------------------------"
pop_tmp=$(mktemp)
for f in "${existing_files[@]}"; do
  g "pop3" "$f" | grep -i "$EMAIL" || true
done | tee "$pop_tmp"
if [[ ! -s "$pop_tmp" ]]; then
  echo "No POP3 activity found for ${EMAIL}."
fi
echo

# 3) IMAP logins (to correlate IPs)
echo "3) IMAP logins/sessions (Dovecot):"
echo "-----------------------------------------------------------------------"
imap_tmp=$(mktemp)
for f in "${existing_files[@]}"; do
  g "imap" "$f" | grep -i "$EMAIL" || true
done | tee "$imap_tmp"
if [[ ! -s "$imap_tmp" ]]; then
  echo "No IMAP login/session lines found for ${EMAIL}."
fi
echo

# 4) Roundcube/Webmail logs
echo "4) Roundcube/Webmail logins/errors:"
echo "-----------------------------------------------------------------------"
rc_tmp=$(mktemp)
for f in "${existing_files[@]}"; do
  case "$f" in
    *roundcube*|*/access_log*|*cpanel* )
      g "$EMAIL|$LOCALPART@$DOMAIN|$LOCALPART%40$DOMAIN" "$f" || true
      ;;
  esac
done | tee "$rc_tmp"
if [[ ! -s "$rc_tmp" ]]; then
  echo "No Roundcube/Webmail entries found for ${EMAIL}."
fi
echo

# 5) Exim deliveries/forwards (to check if mail was delivered/forwarded elsewhere)
echo "5) Exim deliveries/forwards for this address:"
echo "-----------------------------------------------------------------------"
exim_tmp=$(mktemp)
for f in "${existing_files[@]}"; do
  case "$f" in
    *exim_mainlog* )
      g "$EMAIL" "$f" || true
      ;;
  esac
done | tee "$exim_tmp"
if [[ ! -s "$exim_tmp" ]]; then
  echo "No Exim entries found for ${EMAIL}."
fi
echo

# 6) cPanel/Webmail access logs (who logged in via webmail/cPanel UI)
echo "6) cPanel/Webmail access (IPs, timestamps):"
echo "-----------------------------------------------------------------------"
cp_tmp=$(mktemp)
for f in "${existing_files[@]}"; do
  case "$f" in
    */access_log* )
      # Match either full email or percent-encoded form in URLs
      g "$EMAIL|$LOCALPART%40$DOMAIN" "$f" || true
      ;;
  esac
done | tee "$cp_tmp"
if [[ ! -s "$cp_tmp" ]]; then
  echo "No cPanel/Webmail access lines for ${EMAIL}."
fi
echo

# 7) Summaries: IPs involved in deletes / logins
ip_re='([0-9]{1,3}\.){3}[0-9]{1,3}'

summarize_ips () {
  local label="$1"; local file="$2"
  echo "Top IPs for ${label}:"
  echo "-----------------------------------------------------------------------"
  if [[ -s "$file" ]]; then
    # Try to extract IPv4s and count
    grep -Eo "$ip_re" "$file" | sort | uniq -c | sort -nr | head -20
  else
    echo "No data."
  fi
  echo
}

echo "7) IP Summaries"
separator
summarize_ips "IMAP delete/expunge" "$expunge_tmp"
summarize_ips "POP3 access" "$pop_tmp"
summarize_ips "IMAP logins" "$imap_tmp"
summarize_ips "Roundcube/Webmail + cPanel access" "$rc_tmp"
summarize_ips "cPanel/Webmail access_log" "$cp_tmp"

# 8) Hints
echo "Hints:"
echo "- If you see 'expunge' with an IP: that's a delete via IMAP from that IP/client."
echo "- POP3 lines with your email indicate a client likely downloaded mail (often removing from server)."
echo "- Webmail/cPanel access lines show who logged into the UI and from which IP."
echo "- Cross-check unfamiliar IPs with the customer. If unknown, reset the mailbox password and re-scan."
echo
separator
echo "[ $(ts) ] End of report for ${EMAIL}"
separator

How to run

sudo bash /root/mail_audit.sh -e user@domain.com
# If you know the cPanel username for better Roundcube paths:
sudo bash /root/mail_audit.sh -e user@domain.com -u CPANELUSER

What This Script Reports

  • IMAP Deletions (Expunge events) β†’ Confirms if emails were deleted via IMAP, with IP address.
  • POP3 Downloads β†’ Indicates if emails were pulled and removed by a mail client.
  • Webmail & cPanel Access β†’ Shows who logged in via Webmail, from where, and when.
  • IP Summaries β†’ Lists the top IP addresses that accessed the mailbox.

Common Errors

/root/mail_audit.sh: line 10: $'\r': command not found : invalid option nameline 11: set: pipefail

Ah that error ($'\r': command not found) happens because the script was saved with Windows (CRLF) line endings, not Linux (LF).

Linux shell (bash) chokes on those hidden \r carriage return characters

Fix

Option 1: Convert the file

Run:

dos2unix /root/mail_audit.sh

If dos2unix isn’t installed:

yum install dos2unix -y # CentOS/RHEL/AlmaLinux
apt install dos2unix -y # Debian/Ubuntu

Then re-run:

bash /root/mail_audit.sh -e user@domain.com -u CPANELUSER

Option 2: Use sed (no install needed)

If you can’t install dos2unix:

sed -i 's/\r$//' /root/mail_audit.sh

Option 3: When Copy-Pasting

If you paste directly into nano or vim on the server, make sure your editor is set to Unix line endings.

Example:

nano -c /root/mail_audit.sh
# paste script, save with CTRL+O then CTRL+X

After conversion, the errors will disappear and the script will run properly.

More Articles