PowerMTA's bounce classification system converts raw SMTP response codes and text into actionable categories that drive the correct downstream action for each delivery failure. Without correct classification, hard bounce responses suppress valid addresses (if policy rejections are classified as hard bounces), or permanent failures continue generating delivery attempts and reputation damage (if hard bounces are classified as soft bounces). The bounce classification pattern library is one of the highest-impact configuration decisions in a production PowerMTA deployment — and one of the most commonly mis-configured.
Why Bounce Classification Matters for Deliverability
Every SMTP failure the MTA encounters falls into one of two fundamental categories: the address is the problem (invalid, deprovisioned, or non-existent — never send to it again), or something else is the problem (rate limiting, authentication failure, content rejection, blocklist — fix the something else and retry). Bounce classification is the operational mechanism that sorts every failure into the right category and triggers the correct response automatically.
The commercial cost of misclassification has two directions. False positives (classifying a valid address as a hard bounce) remove deliverable contacts from the active list permanently — a contact who would have continued engaging and converting is suppressed because their ISP returned a temporary policy rejection that the bounce classifier misread as a permanent address failure. False negatives (classifying a hard bounce as soft) continue injecting messages to invalid addresses that generate bounce signals damaging IP and domain reputation — each additional send to a hard-bouncing address accumulates a negative ISP reputation signal at zero commercial benefit.
Correct bounce classification eliminates both error types: every permanent address failure is suppressed immediately and never retried; every non-address failure is investigated and retried after the root cause is addressed. The pattern library is the tool that makes this classification accurate at scale, without requiring human review of each individual SMTP response.
PowerMTA Bounce Configuration Basics
PowerMTA's bounce classification is configured in the bounce-cat.php file (or equivalent bounce classification file, depending on the PowerMTA version and configuration approach). The classification file maps SMTP response code patterns and text patterns to bounce type categories. PowerMTA evaluates patterns in order — the first matching pattern determines the bounce type.
# bounce-cat.conf — PowerMTA bounce classification patterns # Format: pattern [bounce_type] [description] # Hard bounces — address does not exist /User unknown/i hard Address does not exist /5\.1\.1/ hard Mailbox not found (RFC enhanced code) /5\.1\.2/ hard Bad destination mailbox address /5\.1\.3/ hard Bad destination mailbox address syntax /5\.2\.1/ hard Mailbox disabled, not accepting messages /Mailbox not found/i hard Mailbox does not exist /No such user/i hard User does not exist /Invalid address/i hard Address invalid /Address rejected/i hard Address rejected by recipient server # Policy rejections — do NOT suppress address /5\.7\.1/ policy Message rejected due to policy /5\.7\.26/ policy DMARC failure /5\.7\.350/ policy Microsoft policy rejection /5\.7\.511/ policy Microsoft IP block /blocked/i policy IP or domain blocked /spam/i policy Spam classification rejection /blacklisted/i policy Blocklist rejection # Domain errors — domain does not exist /5\.1\.2/ bad-domain Domain does not exist /Domain not found/i bad-domain No MX record for domain /NXDOMAIN/i bad-domain Domain DNS does not exist # Greylisting — retry quickly /451.*greylist/i greylist Greylisted — retry in 5 minutes /try again later/i greylist Temporary greylist # Soft bounces — retry normally /4\.\d\.\d/ soft Temporary failure /421/ soft Service temporarily unavailable /452/ soft Insufficient storage # Catch-all soft (unknown temporaries) /^4/ soft Unclassified temporary failure # Catch-all hard (unknown permanents) /^5/ hard Unclassified permanent failure
Bounce Types: Hard, Soft, Policy, Domain, Authentication
Each bounce type drives a different downstream action. The action mapping must be consistent across all components that consume bounce data — the MTA's retry logic, the application's suppression database, and the analytics pipeline that calculates campaign metrics.
| Bounce type | Meaning | MTA action | List action | Analytics |
|---|---|---|---|---|
| hard | Address permanently invalid | Stop all retries immediately | Add to permanent global suppression | Count as hard bounce rate |
| soft | Temporary failure — deliverable | Retry with exponential backoff | No suppression unless persistent (5+ attempts) | Count as deferral |
| policy | Policy rejection — address valid | Hold for investigation, do not retry automatically | Do NOT suppress address — fix config first | Flag for deliverability review |
| bad-domain | Domain does not exist or has no MX | Stop all retries to this domain | Suppress all addresses at the domain | Count per domain, audit acquisition source |
| authentication | Auth failure (DMARC/SPF/DKIM) | Hold for investigation | Do NOT suppress — fix authentication | Alert deliverability team immediately |
| greylist | Greylisting — retry quickly | Retry after 5–10 minutes (shorter than standard soft) | No suppression | Count separately from standard soft bounces |
The critical distinction that most classification systems get wrong: policy rejections (5.7.x codes) are not hard bounces. A 550 5.7.1 response means the ISP's policy rejected the message — the recipient address may be perfectly valid and deliverable once the policy issue is resolved (authentication fixed, IP delisted, content modified). Suppressing the recipient address in response to a 5.7.1 rejection removes a valid contact and hides the actual policy problem that needs to be fixed. Classify 5.7.x responses as "policy" and route them to an investigation queue, not to the suppression database.
Building the Bounce Pattern Library
The initial pattern library above covers the most common bounce scenarios. A production pattern library needs to be extended with the specific response text patterns observed from the ISPs the programme actually sends to. The pattern building process:
Step 1 — Mine the accounting log for common response texts. Query the accounting log database for the most frequent SMTP response texts over the past 90 days, grouped by response text and classified by response code. This produces a ranked list of the actual SMTP responses the programme's sending encounters most frequently.
-- Top 50 unclassified or uncertain SMTP response texts
SELECT dlvStatusMessage, COUNT(*) as freq,
MIN(dlvRc) as min_code, MAX(dlvRc) as max_code
FROM delivery_events
WHERE dlvRc >= 400
GROUP BY dlvStatusMessage
ORDER BY freq DESC
LIMIT 50;
Step 2 — Classify each response text manually. For each of the top 50 response texts, determine the correct bounce classification based on the meaning of the response. Is this response saying the address does not exist (hard), the message was rejected for policy reasons (policy), the domain has no MX (bad-domain), or is it a temporary failure (soft)?
Step 3 — Add patterns for each newly classified response. For each response text classified in Step 2, add a regex pattern to the bounce classification file that will match this and similar responses. Test each new pattern against the accounting log data to verify it matches the intended responses and does not produce false positives.
Step 4 — Verify accuracy of the catch-all rules. The catch-all rules at the bottom of the pattern file (all 5xx → hard, all 4xx → soft) classify responses that no specific pattern matched. After adding patterns for the top 50 responses, check what percentage of bounce events are still being classified by the catch-all rules. If more than 5% of events are reaching the catch-all, there are significant response text patterns that need specific classification rules.
ISP-Specific Bounce Patterns
Major ISPs use characteristic response text patterns that appear consistently across their bounce responses. Building ISP-specific pattern rules improves classification accuracy for the ISPs that generate the highest bounce volumes.
Gmail:
/550 5\.1\.1 The email account that you tried to reach does not exist/ hard Gmail invalid address /550 5\.2\.1 The email account that you tried to reach is disabled/ hard Gmail disabled account /550 5\.7\.26.*DMARC policy/i auth Gmail DMARC rejection /421 4\.7\.0.*Try again/i soft Gmail rate limit
Microsoft (Outlook.com / Microsoft 365):
/550 5\.1\.10 RESOLVER\.ADR\.RecipientNotFound/ hard Microsoft recipient not found /550 5\.4\.1.*Recipient address rejected/i policy Microsoft tenant policy /550 5\.7\.511.*.*Access denied.*HCT/ policy Microsoft IP block (SNDS) /550 5\.7\.520.*Access denied.*sender is blocked/i policy Microsoft domain block /421 4\.16\.55.*MG:B4/i soft Microsoft rate limit
Yahoo:
/554 delivery error.*No such user/i hard Yahoo invalid user /421 4\.7\.0.*Temporarily deferred.*TS01/i soft Yahoo FBL enrollment required /421 4\.7\.0.*Temporarily deferred.*TS02/i soft Yahoo poor reputation throttle /554 5\.7\.9.*Message not accepted.*TS03/i policy Yahoo auth/content rejection
GMX / Web.de (European):
/550 5\.1\.1 User unknown/i hard GMX unknown user /452 4\.3\.1.*Insufficient system storage/i soft GMX over-quota /421 4\.3\.2.*Service temporarily not available/i soft GMX rate limit
Automated Suppression Workflows
The bounce classification system is only useful if its output drives automated suppression with minimal latency. The target suppression latency: hard bounce suppression within 60 seconds of the bounce event; policy bounce investigation queue update within 5 minutes; soft bounce reclassification check every 72 hours.
The automated suppression pipeline in PowerMTA uses the pipe-to-script mechanism: PowerMTA writes accounting log records in real-time to a named pipe that is read by a processing script. The script classifies each event, looks up the bounce type, and calls the suppression API to add hard bounce addresses to the global suppression database:
# In PowerMTA config: <acct-file |/usr/local/bin/bounce-processor.py> records d,b fields timeLogged,type,rcpt,dlvRc,dlvStatusMessage,dlvStatus,vmta </acct-file>
#!/usr/bin/env python3
# bounce-processor.py — reads PowerMTA accounting records from stdin
import sys, json, requests, re
SUPPRESSION_API = 'https://app.brand.com/api/suppressions'
POLICY_QUEUE_API = 'https://app.brand.com/api/policy-queue'
HARD_PATTERNS = [re.compile(p, re.I) for p in [
r'5\.1\.[123]', r'user unknown', r'mailbox not found', r'no such user'
]]
POLICY_PATTERNS = [re.compile(p, re.I) for p in [
r'5\.7\.\d+', r'blocked', r'spam', r'blacklist', r'DMARC'
]]
for line in sys.stdin:
fields = line.strip().split('|')
if len(fields) < 7: continue
_, rec_type, rcpt, smtp_code, smtp_text, status, vmta = fields[:7]
if status == 'success' or rec_type not in ('d', 'b'):
continue
smtp_code = int(smtp_code) if smtp_code.isdigit() else 0
# Classify
if smtp_code >= 500:
if any(p.search(smtp_text) for p in HARD_PATTERNS):
requests.post(SUPPRESSION_API, json={'email': rcpt, 'reason': 'hard_bounce'})
elif any(p.search(smtp_text) for p in POLICY_PATTERNS):
requests.post(POLICY_QUEUE_API, json={'email': rcpt, 'smtp_code': smtp_code, 'smtp_text': smtp_text})
Quarterly Bounce Classification Audit
The bounce pattern library requires quarterly maintenance because ISP response text patterns change when ISPs update their mail systems. A pattern that correctly classified Gmail's "mailbox not found" response in 2024 may no longer match if Gmail updated its response text wording in 2025. The quarterly audit checks that the classification system remains accurate for all response texts the programme currently encounters.
The audit procedure: (1) Query the accounting log for the distribution of bounce events by bounce type classification (hard/soft/policy/bad-domain/auth/greylist) for the past 90 days. (2) Sample 20-30 records from each bounce type bucket and verify that the SMTP response text in the sample is correctly classified as that type — a "policy" bucket that contains response texts clearly indicating invalid addresses has a false positive problem. (3) Query for the most common response texts that are classified by the catch-all rule (not by a specific pattern) and add specific patterns for any that should be classified differently than the catch-all. (4) Update the bounce classification file with the new and corrected patterns. (5) Document the update with the date and the specific patterns changed.
The quarterly audit typically takes 60-90 minutes and produces 2-8 pattern additions or corrections per quarter. The patterns added during quarterly audits represent the drift between the initial pattern library and the evolving ISP landscape — they are the maintenance investment that keeps the classification system accurate as the sending programme's ISP mix and the ISPs' own response text patterns evolve over time.
Bounce Data in Operational Reporting
Classified bounce data in the operational reporting dashboard provides deliverability intelligence that unclassified aggregate bounce rates cannot. The per-classification metrics in the weekly report:
- Hard bounce rate per campaign: Tracks address quality per list segment and acquisition source. Rising hard bounce rate in a specific segment identifies the source of list decay.
- Policy rejection count per ISP: A spike in policy rejections from a specific ISP indicates an authentication or reputation event at that ISP that needs investigation — it does not indicate list quality problems.
- Bad-domain bounce rate per acquisition cohort: B2B lists with high bad-domain rates have significant domain-level staleness from company closures, acquisitions, or domain changes.
- Authentication bounce count (weekly): Any authentication bounce (DMARC or SPF rejection) should be zero or near-zero for a correctly configured programme. Any non-zero count warrants immediate investigation of the authentication stack for the source IP and domain generating the failures.
The classified bounce report converts what would be a single "bounce rate" number into a diagnostic instrument that tells the deliverability team specifically what is going wrong and where. A 1.2% bounce rate is a problem; a 1.2% bounce rate composed of 0.3% hard bounces (acceptable), 0.8% policy rejections from Microsoft (authentication incident at Microsoft needing investigation), and 0.1% bad-domain bounces (B2B list staleness in the most recently imported segment) is an actionable diagnosis. Build the classification system correctly, and every bounce rate becomes a precise delivery quality indicator rather than a vague performance signal.