CloudWizard IMAP Bounce Manager

Version 2.0 PHP 8.1 / 8.2 / 8.3 MariaDB / MySQL Apache & Nginx

This guide covers installation, configuration, and daily use of the CloudWizard IMAP Bounce Manager. The tool scans your email inboxes for bounce messages, classifies them, and builds a clean exclude list for your mailing campaigns.

๐Ÿ’ก

Single-file tool. The entire application is one PHP file (imapbm.php) plus one optional helper script (update_hard_bounces.php). There are no dependencies, no Composer packages, and no build steps.

Requirements

ComponentRequirementNotes
PHP8.1, 8.2, or 8.38.0 may work but is unsupported
PHP extensionphp-imapSee install instructions below
DatabaseMariaDB 10.3+ or MySQL 8+Tables are created automatically
Web serverNginx or ApacheIncluding shared cPanel/Plesk hosts
IMAP accessPort 143 or 993Must be accessible from your server

Installing the PHP IMAP extension

Ubuntu / Debian (Nginx + PHP-FPM)

apt install php8.3-imap
systemctl restart php8.3-fpm

cPanel shared hosting

Go to cPanel โ†’ Select PHP Version โ†’ Extensions and enable imap. Save and apply.

Plesk

Go to Tools & Settings โ†’ PHP Settings, select your PHP version, and enable the imap extension.

Verify the extension is loaded

php -m | grep imap

Should output: imap

Database Setup

Create a dedicated database and user for the bounce manager. Log into MySQL/MariaDB as root:

mysql -u root -p
CREATE DATABASE bounce_handler CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE USER 'bounce_user'@'localhost' IDENTIFIED BY 'YourStrongPassword';
GRANT ALL PRIVILEGES ON bounce_handler.* TO 'bounce_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;
โœ…

All database tables are created automatically when you load the tool for the first time. You do not need to run any SQL schema files.

Upload & Configure

Upload imapbm.php

Upload imapbm.php to a directory on your server. We recommend a protected subdirectory such as /var/www/yourdomain.com/tools/ or a subdomain like bounce.yourdomain.com.

Edit the DB credentials

Open imapbm.php in a text editor and update the four constants at the top of the file:

define('DB_HOST', 'localhost');
define('DB_NAME', 'bounce_handler');
define('DB_USER', 'bounce_user');
define('DB_PASS', 'YourStrongPassword');

Protect the tool

Before accessing the tool in a browser, add HTTP authentication so it is not publicly accessible. See the Nginx or Apache sections below.

Nginx Configuration

Add a location block with HTTP basic authentication. Create a password file first:

apt install apache2-utils   # provides htpasswd
htpasswd -c /etc/nginx/.htpasswd admin

Then add to your Nginx server block:

location /tools/ {
    auth_basic           "CloudWizard";
    auth_basic_user_file /etc/nginx/.htpasswd;

    # Increase upload limit for the DB updater CSV
    client_max_body_size 20M;
}
nginx -t && systemctl reload nginx
โš ๏ธ

The tool stores IMAP passwords in your database. Always run it over HTTPS and behind authentication.

Apache Configuration

Create a .htaccess file in the same directory as imapbm.php:

Options -Indexes
AuthType Basic
AuthName "CloudWizard"
AuthUserFile /path/to/.htpasswd
Require valid-user

# Increase upload limit for the CSV updater
php_value upload_max_filesize 20M
php_value post_max_size 20M

Create the password file:

htpasswd -c /path/to/.htpasswd admin

Make sure AllowOverride AuthConfig is set in your Apache virtual host, or AllowOverride All. On cPanel hosts this is typically already enabled.

PHP upload limits on shared hosting

If you cannot use .htaccess to set PHP limits, look for a php.ini or user.ini file in your home directory:

upload_max_filesize = 20M
post_max_size = 20M

First Run

Open the tool in your browser. On first load it will:

You should see the Bounces tab with empty stats and a prompt to add your first account in Settings.

โœ…

If you see the interface with zero counts, installation is complete. Proceed to adding your IMAP accounts.

Overview

The IMAP Bounce Manager has four tabs:

TabPurpose
BouncesMain view โ€” scan accounts, view and filter the bounce list, export CSV
PatternsView and edit the subject rules, IP block patterns, and spam reject patterns
SettingsAdd, edit, test, and delete IMAP accounts
Audit LogScan history and processing failure log

Adding IMAP Accounts

Go to Settings and fill in the form:

FieldDescription
LabelDisplay name, typically the email addressRequired
IMAP HostHostname of the mail server, e.g. mail.yourdomain.comRequired
Port143 for plain/STARTTLS, 993 for SSLRequired
UsernameUsually the full email addressRequired
PasswordIMAP password for the accountRequired
MailboxFolder to scan, defaults to INBOXOptional
Use SSLCheck for port 993 (SSL/TLS). Leave unchecked for port 143Optional
ActiveUncheck to temporarily disable without deletingOptional

Click Test after saving to verify the connection. A successful test shows the message count in the mailbox.

Common IMAP settings

ProviderHostPortSSL
Your own Postfix/Dovecotmail.yourdomain.com143 or 993Depends on server
Gmailimap.gmail.com993Yes
Outlook / Office 365outlook.office365.com993Yes
Yahooimap.mail.yahoo.com993Yes
๐Ÿ’ก

For Gmail, you must enable IMAP in Gmail settings and use an App Password (not your regular password) if 2FA is enabled.

Scanning

On the Bounces tab:

  1. Check the accounts you want to scan in the left panel
  2. Click Scan & Process
  3. The tool fetches up to 500 messages per account, classifies bounce emails, logs them to the database, and deletes matched messages from the IMAP server
  4. A summary appears below the button showing counts and a per-message log
โš ๏ธ

Deletion is permanent. Matched bounce emails are expunged from the IMAP server after processing. Non-bounce emails are never touched. Run the Test connection first if you are unsure about an account.

What gets deleted vs. what stays

Message typeAction
Hard bounceLogged + deleted from IMAP
Soft bounceLogged + deleted from IMAP
IP blockLogged + deleted from IMAP
Spam rejectLogged + deleted from IMAP
Auto-reply / Out of OfficeDeleted from IMAP, not logged
No pattern matchLeft in inbox, untouched
Failed to parseDeleted from IMAP, logged to Failure Log

Bounce Types

TypeMeaningExclude from sends?
Hard Permanent failure โ€” address doesn't exist, domain invalid, account permanently closed Yes โ€” always
Soft Temporary failure โ€” mailbox full, server busy, quota exceeded. Soft count increments on each bounce After repeated bounces
IP Block Your sending IP was blocklisted. The recipient address is valid โ€” the rejection is about your IP, not them No โ€” address is valid
Spam Reject Message rejected as spam by the receiving server. Address is valid, content or reputation is the issue No โ€” address is valid

How addresses are detected

The tool uses a priority chain to extract the bounced email address from each message:

  1. RFC 3464 Final-Recipient header โ€” the most reliable method, used by all standard DSN-compliant mail servers
  2. Original-Recipient header โ€” fallback DSN header
  3. VERP pattern โ€” decodes bounce+user=domain.com@sender.com Return-Path addresses
  4. Postfix body pattern โ€” matches <email>: in the Postfix NDR body
  5. X-Failed-Recipients header
  6. To: header of the original message
  7. First non-system email address in the body โ€” last resort

If no address can be extracted, the message is logged to the Failure Log and deleted from IMAP.

Patterns

The Patterns tab shows all rules used to detect and classify bounce messages. There are three categories:

CategoryWhere it matchesDefault count
Subject rulesEmail subject line23
IP BlockEmail body text15
Spam RejectEmail body text13

Match types

TypeBehaviour
starts_withPattern must appear at the start of the subject (case-insensitive)
containsPattern can appear anywhere in the subject or body
regexFull PHP regular expression, e.g. /^Delivery (Failure|Error)/i

Result types for subject rules

ResultMeaning
hardClassify as hard bounce immediately
softClassify as soft bounce immediately
checkInspect the message body to determine hard/soft/ip_block/spam_reject
skipDelete from IMAP without logging (auto-replies, OOO messages)

Adding a custom pattern

Click Add Pattern, choose category and match type, enter your pattern text, and set the result type. Use Sort Order to control which rules are checked first โ€” lower numbers run first.

Exporting the Bounce List

The export buttons at the bottom of the Bounces tab produce a CSV file with these columns:

email, bounce_type, soft_count, reason, source_account, bounce_date
ExportContentsUse for
Exclude list (hard+soft)All hard and soft bouncesYour main suppress list for sending
HardHard bounces onlyPermanent removes
SoftSoft bounces onlyReview and retry decisions
AllEvery entry including IP blocks and spam rejectsFull audit export

Using the exclude list in PHP

// Load the exclude list from CSV into an array
$exclude = [];
if (($fh = fopen('bounces_exclude_2026-06-15.csv', 'r')) !== false) {
    fgetcsv($fh); // skip header
    while (($row = fgetcsv($fh)) !== false) {
        $exclude[] = strtolower(trim($row[0]));
    }
    fclose($fh);
}

// Filter your subscriber list before sending
$to_send = array_filter($subscribers, function($sub) use ($exclude) {
    return !in_array(strtolower($sub['email']), $exclude);
});

Using the exclude list with a SQL JOIN

If you imported the CSV into a bounces table on the same server, you can exclude at query time:

SELECT s.* FROM signups s
WHERE s.hard_bounce = 0
  AND s.email NOT IN (
    SELECT email FROM bounce_handler.bounces
    WHERE bounce_type IN ('hard', 'soft')
  );

Database Updater Tool

The included update_hard_bounces.php is a standalone web tool that reads an exported hard bounce CSV and sets hard_bounce = 1 in your subscriber table.

Configure the script

Open update_hard_bounces.php and set the DB credentials and table name at the top:

define('DB_HOST', 'localhost');
define('DB_NAME', 'your_database');
define('DB_USER', 'your_user');
define('DB_PASS', 'your_password');
define('SIGNUPS_TABLE', 'signups');

Export from the Bounce Manager

In the Bounce Manager, click Export CSV โ†’ Hard to download the hard bounce list.

Upload the CSV

Open update_hard_bounces.php in your browser, drag and drop (or click to select) the CSV file, and click Upload & Update.

Review results

The tool shows each email address with a โœ“ Updated or โ€” Not found result, plus totals. Addresses not found in your subscriber table are ignored.

Customising for your schema

If your subscriber table uses different column names, edit these two lines in the script:

// Change 'email' to match your email column name
$email_col = array_search('email', array_map('strtolower', $header));

// Change 'hard_bounce = 1' to match your flag column
$stmt = $pdo->prepare("UPDATE " . SIGNUPS_TABLE . " SET hard_bounce = 1 WHERE email = ?");

Audit Log

The Audit Log tab shows two tables:

Scan History

Every scan run is recorded with:

Processing Failures

Messages that matched a bounce subject rule but could not be fully parsed are logged here with the subject line, a body snippet, and the failure reason. Review these occasionally to check if you need to add a new pattern for an unusual bounce format.

Troubleshooting

ErrorCauseFix
Cannot connect to account Wrong host, port, or credentials Use the Test button. Check that IMAP is enabled on the mail server and the port is reachable from your web server
Unable to negotiate TLS Server doesn't support STARTTLS on port 143 Uncheck the SSL option and use port 143, or switch to port 993 with SSL checked
SECURITY PROBLEM: AUTH=PLAIN notice in logs PHP IMAP library warning on plain-text auth Harmless โ€” connection still works. Use SSL (port 993) to eliminate the warning
0 bounces found after scanning Subject patterns don't match your bounce format Check the Failure Log for clues. Go to Patterns and add a rule matching your bounce subjects
โš ๏ธ No recipient found Body parsing couldn't extract an email address Check the Failure Log snippet. The message may be in an unusual format โ€” email a sample to support
413 Request Entity Too Large Upload limit too low for CSV file Add client_max_body_size 20M to Nginx, or upload_max_filesize = 20M to php.ini
Incorrect string value (MariaDB) Non-UTF8 characters in bounce reason Fixed in v2.0 โ€” the tool sanitizes all text before inserting
You have an error in your SQL syntax (ssl column) Old version used ssl which is a reserved word in MariaDB Fixed in v2.0 โ€” column renamed to use_ssl

Security Recommendations

Changelog

v2.0

v1.x

CloudWizard IMAP Bounce Manager ยท cloudwizard.eu ยท hello@cloudwizard.eu