A standalone cleanup tool for Cyrus IMAP when native expiration mechanisms (cyr_expire) are unavailable. Offers direct filesystem-level cleaning of old messages from specific folders with full logging, safety validation, and automatic index reconstruction.
✅ Multi-Mode Operation
- Single user cleanup (
--user) - All users cleanup (
--all) - Dry-run simulation (
--dry-run) - Automated mode for cron (
--auto) - Show all users including those with 0 files to clean (
--show-all)
✅ Easy Installation
- One-command setup (
--install) - Automatic cron scheduling with interactive frequency selection
- Auto-detection of script path
- Safe uninstall with log archiving (
--uninstall)
✅ Production-Ready Security
- Multi-layer path validation
- Symlink protection
- Username sanitization (alphanumeric +
.,-,_only) - Empty variable guards
- Recursive safe deletion with regex filtering
✅ Comprehensive Logging
- Session-based tracking with unique IDs
- Timestamped entries with severity levels (INFO/WARNING/ERROR)
- Execution time tracking
- Automatic log rotation (12 weeks retention)
✅ Battle-Tested
- Used in production mail environments
- Handles edge cases (invalid usernames, missing folders, symlinks)
- Automatic Cyrus index reconstruction after cleanup
Note: All commands must be run as root or with sudo privileges.
# Download the script
git clone https://github.com/RyzuOPs/cyrus-mailbox-cleaner.git
cd cyrus-mailbox-cleaner
# Set executable permissions
chmod +x cyrus-mailbox-cleaner
# Initialize log, logrotate, and optionally setup cron
./cyrus-mailbox-cleaner --install
# During installation you'll be asked:
# - Create log file: /var/log/cyrus_clean.log
# - Setup logrotate (12 weeks rotation)
# - Add to cron? (optional - choose frequency: daily/weekly/monthly/custom)# Test with dry-run (safe, nothing deleted)
./cyrus-mailbox-cleaner --user johndoe --dry-run
# Clean specific user
./cyrus-mailbox-cleaner --user johndoe
# Clean all users (with confirmation prompt)
./cyrus-mailbox-cleaner --all
# Automated cleanup (no prompts, for cron)
./cyrus-mailbox-cleaner --all --auto- OS: Debian 7+ (compatible with most Linux distributions)
- Cyrus IMAP: 2.x or newer
- Permissions: root/sudo access
- Dependencies:
find(GNU findutils)awkbash4.0+cyrus-imapd(reconstruct binary)
Edit the following variables in the script (lines 15-19):
SPOOL_DIR="/cyrus/imap/user" # Cyrus user directory
RECONSTRUCT_BIN="/usr/cyrus/bin/reconstruct" # Path to reconstruct
LOG_FILE="/var/log/cyrus_clean.log" # Log file location
DAYS_OLD=30 # Age threshold (days)
TARGET_FOLDERS=("Trash" "Spam" "Junk" "Quarantine") # Folders to clean# See what would be deleted (dry-run)
./cyrus-mailbox-cleaner --user alice --dry-run
# Output:
# ==========================================
# [DRY-RUN MODE] Simulation for: alice
# Files to delete: 150
# Space to reclaim: 45.2 MB
# ==========================================
# --- [DRY-RUN] Simulating cleanup: alice (older than 30 days) ---
# [DRY-RUN] Trash: Would delete 120 files (40.5 MB)
# [DRY-RUN] Spam: Would delete 30 files (4.7 MB)
# ...
# Review the preview, then execute
./cyrus-mailbox-cleaner --user alice# Dry-run for all users (shows only users with files to delete)
./cyrus-mailbox-cleaner --all --dry-run
# Show all users including those with 0 files or validation errors
./cyrus-mailbox-cleaner --all --dry-run --show-all
# Execute cleanup
./cyrus-mailbox-cleaner --all --autoOption 1: During Installation
The easiest way is to let --install set it up for you:
./cyrus-mailbox-cleaner --install
# You'll see:
# ==========================================
# CRON SETUP (OPTIONAL)
# ==========================================
# Add automatic cleanup to cron? (y/n): y
#
# Select cleanup frequency:
# 1) Daily (every day at 2:00 AM)
# 2) Weekly (Sunday at 2:00 AM)
# 3) Monthly (1st day of month at 2:00 AM)
# 4) Custom cron expression
#
# Choice [1-4]: 1
#
# [OK] Cron job added successfully!
# Schedule: Daily at 2:00 AM
# Command: /path/to/cyrus-mailbox-cleaner --all --auto >> /var/log/cyrus_clean.log 2>&1Option 2: Manual Setup
Add to /etc/cron.d/cyrus-cleanup or root's crontab:
# Daily at 2:00 AM
0 2 * * * root /usr/local/bin/cyrus-mailbox-cleaner --all --auto >> /var/log/cyrus_clean.log 2>&1
# Weekly on Sunday at 2:00 AM
0 2 * * 0 root /usr/local/bin/cyrus-mailbox-cleaner --all --auto >> /var/log/cyrus_clean.log 2>&1
# Monthly on 1st at 2:00 AM
0 2 1 * * root /usr/local/bin/cyrus-mailbox-cleaner --all --auto >> /var/log/cyrus_clean.log 2>&1Note: The >> /var/log/cyrus_clean.log 2>&1 ensures all output (including cron errors) goes to the main log file.
Managing Cron Jobs
# View installed cron jobs
crontab -l
# Edit cron jobs manually
crontab -e
# Remove cron job during uninstall
./cyrus-mailbox-cleaner --uninstallEach log entry follows this structure:
TIMESTAMP [SESSION_ID] SEVERITY: message
Example session log:
2026-03-11 15:01:34 [9350-1773237694] INFO: SESSION_START | Mode: --user alice --dry-run
2026-03-11 15:01:34 [9350-1773237694] INFO: START: Cleaning alice | DRY-RUN
2026-03-11 15:01:34 [9350-1773237694] INFO: USER: alice | FOLDER: Trash | DELETED: 120 files | SIZE: 40.5 MB | DRY-RUN
2026-03-11 15:01:34 [9350-1773237694] INFO: USER_FINISH: alice | TOTAL: 150 files | SIZE: 45.2 MB | DRY-RUN
2026-03-11 15:01:34 [9350-1773237694] INFO: SESSION_END | User: alice | Files: 150 | Size: 45.2 MB | Time: 0s | DRY-RUN
# View last session
tail -50 /var/log/cyrus_clean.log
# Find specific session by ID
grep "\[9350-1773237694\]" /var/log/cyrus_clean.log
# Show only errors
grep "ERROR" /var/log/cyrus_clean.log
# Daily statistics
grep "SESSION_END" /var/log/cyrus_clean.log | grep "$(date +%Y-%m-%d)"
# Total files deleted today
grep "$(date +%Y-%m-%d)" /var/log/cyrus_clean.log | grep "SESSION_END" | \
awk -F'Files: ' '{print $2}' | awk '{sum+=$1} END {print sum " files"}'| Option | Description |
|---|---|
--user <name> |
Clean folders for specific user |
--all |
Clean folders for all users |
--auto |
Automatic mode (no confirmation prompts, for cron) |
--dry-run |
Simulation mode - shows what would be deleted without actual deletion |
--show-all |
Show all users in --all mode, including those with 0 files or errors |
--install |
Create log file, logrotate config, and optionally setup cron automation |
--uninstall |
Archive log and remove configuration and cron job |
--help |
Display help message |
Only alphanumeric characters, dots (.), hyphens (-), and underscores (_) are allowed. Invalid usernames are logged and skipped.
Rejected examples:
"user with spaces"(contains spaces)"user'name"(contains apostrophe)"user@domain"(contains @ symbol)
Directories that are symbolic links are automatically detected and skipped to prevent symlink escape attacks.
All paths must match the pattern: /cyrus/imap/user/<user>/<folder>
Any attempt to escape this structure is blocked.
Empty values for $user or $folder trigger immediate error and return, preventing accidental deletion of parent directories.
After file deletion, the script automatically runs:
/usr/cyrus/bin/reconstruct -rf user.<user>.<folder>(executed as cyrus user)
This ensures Cyrus IMAP database consistency.
Cause: Incorrect SPOOL_DIR path
Solution: Edit script line 16 to match your Cyrus installation path
Cause: reconstruct binary in different location
Solution:
# Find reconstruct location
which reconstruct
find /usr -name reconstruct 2>/dev/null
# Edit RECONSTRUCT_BIN in script (line 17)Symptom: [WARNING] Trash: Index reconstruction failed!
Cyrus reconstruct may report warnings even when successful (e.g., folder was already consistent). Usually safe to ignore, but check Cyrus logs if persistent.
Example: ERROR: Invalid username: _invalid=user=name
This is normal behavior. Cyrus may contain directories with invalid names from migrations or errors. The script safely skips these.
The --uninstall command handles everything automatically:
# Archive log and remove all configuration (log, logrotate, cron)
./cyrus-mailbox-cleaner --uninstall
# Output:
# ==========================================
# UNINSTALLATION: Cyrus Mailbox Cleaner
# ==========================================
# Archiving log to ./log/cyrus_clean_20260318_150234.log... [OK]
# Removing /var/log/cyrus_clean.log... [OK]
# Removing logrotate configuration... [OK]
# Removing cron job... [OK]
#
# ==========================================
# UNINSTALLATION COMPLETED
# ==========================================Manual removal (if needed):
# Remove cron job manually
crontab -e # Remove line with cyrus-mailbox-cleaner
# Remove script
rm /usr/local/bin/cyrus-mailbox-cleaner- Discovery: Scans
/cyrus/imap/user/for user directories - Validation: Validates usernames, paths, and checks for symlinks
- Analysis: Uses GNU
findwith regex to locate Cyrus mail files ([0-9]+\.$) older than threshold - Deletion: Removes matching files (if not dry-run)
- Reconstruction: Rebuilds Cyrus IMAP indexes for cleaned folders
- Logging: Records session with unique ID, timestamps, and statistics
The script uses POSIX Extended regex to match Cyrus mail files:
.*/[0-9]+\.$This matches paths ending with one or more digits followed by a period (e.g., 123., 4567.).
The script cleans recursively through all subfolders. For example:
Trash/123.✓Trash/Important/456.✓Trash/2024/Archive/789.✓
Contributions are welcome! Please:
- Test changes with
--dry-runextensively - Maintain existing security validations
- Update documentation for new features
- Follow bash best practices (shellcheck compliant)
MIT License - see script header for details
- Łukasz Ryszkiewicz - Initial development and maintenance
- GitHub: @RyzuOPs
Battle-tested in production mail environments serving thousands of users. Special thanks to the Cyrus IMAP community for the robust mail server foundation.
--dry-run before production deployment. While the script has multiple safety layers, email deletion is permanent and irreversible.