Bounce Manager v2.1
Part of CloudWizard Email Suite
Bounce Manager connects to a bounce-handling mailbox over IMAP, classifies delivery failures as hard, soft, IP-block, or spam-reject, and automatically suppresses addresses that cross a configurable threshold — keeping your suppression list current without manual work.
Requirements
| Requirement | Detail |
|---|---|
| PHP | 8.1 or higher, with the imap extension enabled |
| MariaDB / MySQL | 5.7+ / 10.3+ |
| Web server | Nginx or Apache |
| A bounce mailbox | Any IMAP-accessible mailbox that receives delivery failure notifications — dedicated address, shared inbox, or a folder/filter within one |
| Cron access | A way to trigger a URL or run a PHP script every 5-15 minutes |
Step 1 — Install the Controller
Bounce Manager runs inside the CloudWizard Controller. The controller handles authentication, shared database config, and module detection. Install it first.
Upload the controller
Unzip the download. It holds one folder, CW/, with the controller and Bounce Manager inside. Upload that whole folder to a directory on your server — typically public_html/CW/. The controller part looks like this:
public_html/CW/
bootstrap.php
config_path.php
vendor/ ← bundled PHPMailer, shared across every module
admin/
index.php
login.php
logout.php
settings.php
setup.php
style.css
...
Run the setup wizard
Visit https://yourdomain.com/CW/admin/setup.php to run the wizard. Fill in:
- Database — host, name, username, password, table prefix (default:
cw_) - SMTP — outgoing mail server credentials and sender address
- Admin password — minimum 8 characters
- Site URL — your public domain
Click Complete setup. The config file is written outside your webroot where possible.
Log in
You're redirected to the login page. Enter your admin password to reach the CloudWizard dashboard.
Step 2 — Install Bounce Manager
Upload the module
The module is already inside the zip you just uploaded, at CW/BounceManager/. Adding it to an existing CloudWizard install instead? Don't upload the whole CW/ folder again: copy just this module's BounceManager/ folder into your existing controller directory and reload the dashboard. Its file layout:
public_html/CW/BounceManager/
module.php
bootstrap.php
cron.php
dmarc_import.php ← one-off import of old DMARC report mails
admin/
_bootstrap.php
index.php ← Overview / Bounces / scan panel
patterns.php
settings.php
_dmarc_mailboxes.php ← DMARC mailbox form (shown on Settings)
dmarc.php ← DMARC reports tab
audit.php
lib/
scan.php ← IMAP scanning engine
dmarc.php ← DMARC report reader
Verify detection
Reload the controller dashboard. The Bounce Manager card should show Open →. Tables (accounts, patterns, bounces, audit log, settings) and a set of default classification patterns are created automatically on first load.
Step 3 — Activate your license
Whether you purchase through cloudwizard.eu or via Gumroad, enter your license key under the controller's Settings → License key. See the Audience Manager guide for the full walkthrough if needed.
Step 4 — Add an IMAP account
Go to Bounce Manager → Settings and fill in the Add Account form:
| Field | Notes |
|---|---|
| Label | Internal name for this mailbox, e.g. bounces@example-shop.eu |
| IMAP Host | Your mail server's IMAP hostname |
| Port | 143 (plain/STARTTLS) or 993 (SSL) — pair with the Connection setting below |
| Mailbox | Usually INBOX, or a specific folder if you filter bounces there |
| Username / Password | Full mailbox credentials |
| Connection | STARTTLS (usually port 143), SSL/TLS (usually port 993), or None, plain (only when the mailbox is on the same server, host 127.0.0.1) |
Click Test next to the saved account to confirm the connection before relying on it — it reports the message count in the mailbox or the exact IMAP error if it fails. The Status column shows the real state: Not tested, Connected, Can't connect or Off, plus when the last check ran.
Step 5 — Set up the cron job
Nothing scans automatically without this. Bounce Manager → Settings shows the exact cron key and URL for your install:
| Option | When to use it |
|---|---|
A — URL (wget) | Works on any host with outbound HTTP access from cron; the URL includes a secret key so it can't be triggered by anyone else |
| B — PHP (direct) | Runs the script directly via the PHP CLI — no key needed, since reaching it this way already requires shell access to the server |
Add it under Virtualmin → Scheduled Cron Jobs, running every 5-15 minutes. A single cron run fully drains each account's backlog (not just one batch), looping until nothing remains — so even after a large bounce spike, one run catches everything up rather than trickling in over several cycles.
Overview
The Bounces tab (Bounce Manager's landing page) shows five stat cards — Total, Hard bounces, Soft bounces, IP blocks, Spam rejects — plus the scan panel and your five most recent scans.
Bounces & scanning
Run Scan (left panel) lets you pick which active accounts to scan, then click 🔍 Scan now. The cron job does the same thing automatically (it also fetches new DMARC reports); the button is for when you want the result right away. Scanning runs in chunks over AJAX with a live progress bar — safe for very large mailboxes, since each chunk is a separate short request rather than one long one that could time out.
The Bounce List (right panel) shows every classified bounce, filterable by type, source account, and email search. Select rows with the checkboxes and click 🗑 Delete to remove entries, or use the Export row to download a CSV — Exclude list (hard+soft combined), Hard only, Soft only, or All.
Patterns
Classification runs against a pattern library you can fully edit under Patterns, seeded with sensible defaults on first install:
| Category | Purpose |
|---|---|
| Subject rule | Matched against the bounce email's subject line first — can resolve straight to hard/soft, or defer to a body scan (check) or delete unclassified (skip, e.g. auto-replies/out-of-office) |
| IP Block (body) | Text fragments in the message body indicating the sending IP was blocklisted |
| Spam Reject (body) | Text fragments indicating the message was rejected as spam/policy violation |
Each pattern has a match type (Starts with, Contains, or Regex), a result type, a sort order (lower runs first), and an Active toggle — turn a pattern off without deleting it if it's causing false positives.
Settings
| Field | Notes |
|---|---|
| Hard bounce threshold | Number of hard bounces before auto-suppression. Default 1 — a hard bounce means the address is genuinely dead, so most installs suppress on the first one |
| Soft bounce threshold | Number of soft bounces (mailbox full, temporary failure) before suppression. Default 3 — soft bounces are given more tolerance since they often self-resolve |
| Auto-suppress | Master toggle for the threshold behavior above. Turn off to classify and log bounces without ever touching the suppression list automatically |
| Cron key | The secret protecting the cron URL — editable if you need to rotate it |
IMAP accounts are also managed on this page — see Step 4.
DMARC reports
Big providers (Google, Microsoft, Mail.ru, Yahoo and others) send a daily summary of all mail they received in your domain's name: which server sent it, and whether it passed the DKIM and SPF checks. Bounce Manager → DMARC reads these reports and shows totals per domain and provider, your sending servers and how many messages passed, and a day-by-day list with each provider's decision. Mail that fails here is the usual reason for spam-folder delivery, and unknown server addresses can reveal someone forging your domain.
| Step | What to do |
|---|---|
| 1. Mailbox | Create a separate mailbox for the reports, e.g. dmarc@yourdomain.com. Do not reuse the bounce mailbox: the two kinds of mail are handled differently. |
| 2. DNS | In the domain's _dmarc TXT record, add rua=mailto:dmarc@yourdomain.com. Use rua only (aggregate reports); ruf failure reports are not read and can contain message content. |
| 3. Connect | Settings → DMARC report mailboxes: same form as the bounce accounts. Bounce accounts and DMARC mailboxes are listed separately. |
| 4. Wait | The cron job fetches new reports each run. Reports arrive once a day per provider, so the tab fills within a day or two. Old report mails already in the mailbox can be imported once with php BounceManager/dmarc_import.php. |
Reports are stored in compact form (zip, gzip and plain XML are understood), duplicates are ignored, and entries older than 400 days are removed automatically.
Audit Log
Three panels: an Account Summary (lifetime totals per source account, with scan count and last-scan time), a Scan History of the last 50 runs, and a Processing Failures log — messages that matched a bounce pattern but where no recipient email could be extracted from the body. Both the Scan History and Failures logs have a Clear button to wipe them if they grow large; this only clears the log, never the classified bounces themselves.
How classification works
For each message: the subject is checked against Subject Rules first. If a rule resolves straight to a type, that's used; if a rule says check, the body is scanned next — for known SMTP status codes (5xx → hard, 4xx → soft), quota/mailbox-full phrases (→ soft), and known hard-failure phrasing (user unknown, account disabled, etc.). Every message is also independently checked against the IP Block and Spam Reject body patterns regardless of what the subject rule decided, so those two categories are never missed even if a message also happens to match a generic hard-bounce subject.
The recipient's email address is extracted from the bounce body using a cascade of methods, from most to least reliable: RFC 3464 Final-Recipient/Original-Recipient headers, VERP-style Return-Path addresses, Postfix-style inline addresses, keyword-based patterns ("failed recipient:", "delivered to:"), the X-Failed-Recipients header, the original message's To: header, and finally the first plausible non-system email address found anywhere in the body. If none of these succeed, the message is logged to Processing Failures rather than silently dropped.
Security
- Always use HTTPS. IMAP credentials and bounce data should never travel over plain HTTP.
- The cron URL contains a secret key — don't share it publicly. Running the script directly on the server requires no key, since shell access is already a higher trust level.
- IMAP passwords are stored in the database — restrict database access appropriately, same as any other credential the suite stores.
- Restrict admin access by IP if possible, in Nginx:
allow 1.2.3.4; deny all;
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| "Test" fails on an account | Wrong host/port/SSL combination, or bad credentials | Confirm port 993 pairs with SSL checked, port 143 with it unchecked; verify credentials directly with a mail client if unsure. |
| Scan finds nothing despite bounces sitting in the mailbox | No Subject Rule matches the bounce format your mail server sends | Check Patterns — add a rule matching the actual subject line format, or check Processing Failures for messages that matched but couldn't yield a recipient. |
| Real bounces aren't being suppressed | Auto-suppress is off, or the threshold hasn't been reached yet (e.g. soft bounces under the default threshold of 3) | Check Settings — confirm Auto-suppress is on and thresholds match your intent. |
| Cron runs but nothing changes | No active accounts, or the cron key is wrong | Confirm at least one account is marked Active in Settings, and the cron URL's key matches exactly. |
| Processing Failures log growing steadily | Some bounce format used by your mail server doesn't match any of the recipient-extraction methods | Check the Raw Snippet column for a pattern — this may need a custom pattern rule or is simply an edge case worth ignoring if rare. |
| Module not detected on dashboard | module.php missing or not readable |
Confirm CW/BounceManager/module.php exists. Restart PHP-FPM: systemctl restart php8.3-fpm |
| License key not accepted | Wrong key format or server can't reach cloudwizard.eu | Key format: XXXX-XXXX-XXXX. Check outbound HTTPS: curl -s https://cloudwizard.eu/license/ping.php |
| 500 error on admin pages | DB error, missing table, or file permissions | tail -20 /home/yourusername/logs/php_log |
CloudWizard Email Suite — Bounce Manager v2.1
cloudwizard.eu —
support@cloudwizard.eu