Knowledge base

The bounce category in accounting — configurable, not vendor-fixed

critical

bounce category patterns
The vendor’s category dictionary can be extended with your own

Every accounting record about a bounce has a bounceCat field — the category. Until this section of the manual it looks like a closed dictionary of seventeen values: bad-mailbox, quota-issues, spam-related and others. It is not.

Rules choose the category, and you can write your own rules. An unnamed <bounce-category-patterns> block is evaluated before the vendor defaults from the defaults file; a named block can be tied to a specific receiver, and it is evaluated before both. The order is: the receiver’s rules, then your own general ones, then the vendor’s.

A rule has three parts. An optional session stage, a regular expression and a category name: rcpt-to /invalid recipient/ recipient-related. There is currently only one stage — rcpt-to; it limits the match to replies to the command that carries the recipient address. Without a stage, replies from any stage match.

Your own category names are explicitly allowed. The dictionary is not closed: you can create a category the vendor does not have, and it lands in accounting as is. This is exactly what makes the mechanism valuable — classification moves to where the reply arrives, before the accounting record is written.

Matching works the same way as in SMTP pattern lists — the manual refers to <smtp-pattern-list> for the details.

The main trap is thinking that a vendor category means what its name says. Categories are assigned by the text of the receiving server’s reply, and those texts differ everywhere and change. no-answer-from-host, by the vendor’s definition, is “the host did not answer after connecting, a 4xx or 5xx error”; that is a server’s silence, not a missing address. Whoever reads such a category as “the address does not exist” makes a decision about the address based on the state of someone else’s server.
What the Hub does. It writes an unnamed <bounce-category-patterns> in the policies template — a set from Protosender, several dozen patterns — but with the vendor’s category names. Then the Hub maps the categories onto its own axis of “hard or soft”: hard means “the address does not exist”, and on it the address goes to the blocklist; soft means “the mailbox exists, the message just was not accepted right now”, and on it the rate is lowered.

The list of hard categories is one for the whole Hub — bad-mailbox, inactive-mailbox, bad-domain (PmtaEventNormalizer::HARD_CATEGORIES). It used to lie in copies in four places and diverged exactly on no-answer-from-host; now that category is soft everywhere.

This mechanism is a way to remove the translation step entirely. If the rules are written by us, the node can write our category, taken from the receiver’s words, straight into accounting, rather than the vendor’s one that then has to be interpreted. One step between a bounce and a decision about the address disappears.

Recommended values

What is worth creating as your own rules:

Category What to catch Why
“no such address” no such user, unknown recipient, 5.1.1 at the recipient stage direct grounds to remove the address
“mailbox full” mailbox full, over quota the mailbox is alive, the address must not be removed
“held for reputation” spam, blocked, spamhaus not about the address at all — about us
“server is silent” timeouts and dropped connections neither about the address nor about reputation

The rcpt-to stage is worth specifying almost always where the address is concerned. A “no such user” reply given at the data stage refers to the whole message, not to a single recipient, and must not be treated as a verdict on the address.

Your own category name is better than the vendor’s when the meaning matches none of the seventeen vendor ones: a name of your own is more honest than someone else’s stretched to fit.

The category table and the advice about the recipient stage are ours. From the manual here are the evaluation order of the three rule levels, the form of a rule, the only available stage and the explicit permission to use your own category names.
Example configuration
# your own rules are evaluated before the vendor's
<bounce-category-patterns>
    /mailbox +(is +)?full/ quota-issues
    rcpt-to /[45]\.\d+\.\d+/ recipient-related</bounce-category-patterns>
<bounce-category-patterns custom>
    /no longer available/ quota-issues
</bounce-category-patterns>
<domain hotmail.com>
    bounce-category-patterns custom
</domain>
An example from the manual. The highlighted line is limited to the recipient stage — it will match only a reply to the command that carries the address. The named block below is tied to one receiver and is evaluated before the general one.
yellow — — directive names, blue — blocks. They are highlighted the same way in the text of the card.
▲

Common mistakes and problems

what people get wrong and how it ends3 mistakes▶

Reading the category name as a statement about the address

§ 12

What it costsThe category was assigned by a pattern based on the text of the receiving server’s reply. The name no-answer-from-host means “the server did not answer,” not “the address does not exist”: the mailbox may be alive, the host simply stayed silent. Deciding to remove an address on such a category deletes a live contact.
How to spot itThe address base shrinks after days when the receiving server had problems, not after cleanup on real bounces.
How to do it rightCheck the vendor’s definition of the category before building a decision on it. What “the address does not exist” means is better defined by your own rules than by a guess from the name.

Writing a rule without the recipient stage

§ 11.7.1

What it costsWithout a stage, replies from anywhere in the session match. A “no such user” reply given at the data stage refers to the whole message, yet it is counted against one address.
How to spot itMessages sent to mailboxes known to be alive land in the “no such address” category.
How to do it rightPut rcpt-to in every rule where the category speaks about an individual address. It is the only available stage, and this is exactly what it is for.

Creating a rule and expecting it to override the vendor's

§ 11.7.1

What it costsIt overrides only if it is evaluated earlier. The unnamed block comes before the vendor’s, the named one before both — but only for receivers that reference it. A rule in a named block without a reference is not evaluated at all.
How to spot itThe category in accounting is still the vendor’s, even though your own rule is written and matches the text.
How to do it rightCheck whether the receiver has a line referencing the list. Write general rules into the unnamed block — it applies to everyone.
Compatibility
PowerMTA 6.0
described in the manual of this version · platform: any
The vendor does not state the first version for every directive — there is no note here. The name also appears in the 5.0 edition of the manual.
Inheritance
What is inherited is not the list itself but the reference to it: the bounce-category-patterns line in the receiver block merges by the usual rule — from the general pattern to the exact one, and across the levels node, pool, sending address. The rule blocks themselves live at the root of the file. The evaluation order, meanwhile, is fixed and not subject to inheritance: first the receiver’s named list, then the unnamed general one, then the vendor’s from the defaults file.
How to check on the node
Look not at the configuration but at what actually got into accounting:
pmtastats bounce-categories --bounceCat all /var/log/pmta/acct-*.csv
grep -n -A6 '^<bounce-category-patterns' /etc/pmta/config
The first command prints the breakdown of bounces by category — it shows right away whether messages fall into the intended categories or everything piles up in other. The second shows which rules have been created at all; empty output means only the vendor ones are in effect.
In the Hub
Cost of a mistake
reversible: harms only while it is set
Scenarios
Bounce classification · Address suppression
Priority
critical