The Bounce Loop Guard watches the SMTP service for a specific failure mode that no rate limit, anti-spam or queue alert catches on its own:
a self-sustaining loop of non-delivery reports (NDRs).
It detects the loop within minutes, contains it automatically, and changes the MTA configuration so that a bounce storm can never again monopolise the delivery queue.
It is available on every SMTP instance managed by the appliance, from the menu SMTP Router > Bounce loop guard .

How a bounce loop happens
A bounce loop needs three ordinary events to line up:
554 5.2.121 The sender has exceeded the limit for the number of messages they can send to this recipient per hour).no-reply mailbox on an internal relay. From that moment the queue feeds itself.
Every NDR sits in the deferred queue and is retried on every queue run, for as long as maximal queue lifetime allows (five days by default).
In a real incident analysed on a production relay, 84,000 undeliverable NDRs were retried about twenty times a day each for five days, producing 1.7 million log events per day while no new mail was entering the system.
The log volume overloaded the SIEM, real deliveries competed with the retries for delivery slots, and the situation only ended when Postfix expired the messages on its own.
What the guard does
The guard is a component of the mail-log tracker of the appliance.
It reads the same log stream as the other SMTP statistics, keeps a ten-minute sliding window per instance, and scores it every minute.
| Stage | What happens |
|---|---|
| Detection | A loop is declared on a recipient address when, in the last ten minutes, enough NDRs were generated towards it, most of them were deferred, and the queue shows the signature of a loop: either a high retry amplification (many error lines per queued message) or a queue dominated by null-sender messages. All four thresholds are configurable and have absolute floors so that a small server never fires on a handful of bounces. |
| Level 1 — notification | An incident is opened and an administrator event is recorded (visible in the Events Center and forwarded to the SIEM when one is connected). The notification names the instance, the address the NDRs are addressed to, the sender that feeds the loop with its client IP and the dominant rejection code, and the measured amplification. |
| Level 2 — hold | Immediately after detection, the null-sender messages addressed to that recipient are placed on hold in the Postfix queue (postsuper -h). Only messages with an empty envelope sender are ever selected, so a real message can never be held by the guard. The queue stops retrying them, nothing is lost, and the list of held messages is recorded so that the guard can release or delete them later. |
| Level 3 — throttle | If the triggering sender is still injecting mail thirty minutes after the hold, the guard asks the appliance policy server to defer its new submissions. It is a temporary DEFER, never a REJECT: the job is misbehaving, its mail is not forged. |
| Release | When NDRs to that recipient are delivered again (the relay is back, the mailbox exists), the held messages are released automatically and the incident is closed. An administrator can also release an incident from the page at any time. |
| Purge | A message on hold never expires by itself. After the configured hold age (72 hours by default), the held NDRs are deleted and a notification is sent. An NDR that Postfix would have discarded after its queue lifetime anyway is not a loss. |
Detection is only half of the answer.
Once the parameters have been saved for an instance, the guard also renders three settings into that instance's Postfix configuration:
bounce-out transport with a concurrency of two and a one-second rate delay. Nothing is rendered until the parameters have been saved once: upgrading the appliance changes no Postfix configuration by itself.
A batch job floods a rate-limited provider
An ETL or reporting job sends thousands of notifications to three mailboxes hosted on Microsoft 365.
The provider rejects them with 554 5.2.121.
The job's sender address is no-reply-etl@corp.example, hosted on an internal relay that was decommissioned last month.
Within ten minutes the guard opens an incident, holds the NDRs, names the job's client IP in the notification, and thirty minutes later defers the job's new submissions if it is still running.
The administrator fixes the job, releases the incident, and adds no-reply-etl@corp.example to the no-reply list so that the next storm is discarded at once.
A monitoring tool with a dead sender mailbox
A monitoring appliance sends alerts from alerts@monitoring.example, a mailbox that nobody created.
Every alert that is rejected by a recipient's server produces an NDR that cannot be delivered, and the deferred queue slowly fills with retries.
Declaring the address in the no-reply list discards those NDRs immediately, and the bounce queue lifetime of four hours keeps the queue clean if the address is ever used elsewhere.
An internal relay outage
The relay that hosts every no-reply mailbox of the company goes down for maintenance.
NDRs from the whole fleet pile up. The dedicated bounce transport keeps them from taking over the delivery slots of real mail,
and when the relay comes back the guard releases every held incident automatically as soon as it sees a successful delivery to the affected address.
Reviewing an old incident
The security team notices a spike of Postfix events in the SIEM a week ago.
The incident history on the statistics tab shows when the loop opened and closed,
the address the bounces were addressed to, the sender that fed it and the outcome, so the analysis takes minutes instead of a day of log archaeology.
The period selector (24 hours, 7 days, 30 days) applies to everything below it.
Open incidents lists the loops being contained right now:
the address the bounces are addressed to, the trigger (sender, client IP and dominant rejection code, or a dash when no sender reached the trigger threshold), the level reached, the number of messages on hold and the time the incident opened.
The Release button hands the held bounces back to the queue; detection of that address is then suspended for one hour so that the same loop is not re-detected while you act on its cause.

Bounces per hour plots the NDRs generated, deferred, delivered, expired and held, per hour.
A loop shows as generated and deferred curves rising together while delivered stays flat.

Null-sender share and retry amplification are the two signatures the detector relies on.
The share is the proportion of queued messages with an empty envelope sender; the amplification is the number of error lines per distinct queued message.
A healthy relay shows a low share and an amplification close to zero; the incident described above showed 95 % and 20.
The two top tables rank, over the selected period, the addresses that received the most bounces and the senders that collected the most hard rejections.
The incident history keeps thirty days of incidents with their outcome: released when the recipient accepted mail again, deleted after the hold age, or released by an administrator.

The tab shows the current values; every row opens the same form. Until the parameters have been saved once, the tab says so, and nothing is rendered into Postfix.

| Parameter | Default | Meaning |
|---|---|---|
| Enable detection | on | Runs the detector on this instance. Disabling it stops new incidents; open incidents keep being managed until they close. |
| Dry run | off | Detects and notifies, but never holds, throttles or deletes anything. Use it for a first week on a busy relay to check the thresholds against real traffic. |
| Minimum bounces per 10 minutes | 200 | NDRs generated towards one address in the window before a loop can be declared. The floor is 10. |
| Deferred share of those bounces | 80 % | Proportion of those NDRs that Postfix could not deliver. A loop is, by definition, made of bounces that cannot leave. |
| Retry amplification | 5 | Error lines per distinct queued message in the window. A queue retrying itself scores far above 1. |
| Null-sender share of queued mail | 60 % | Alternative to the amplification: a queue where most messages have no sender is a queue full of bounces. |
| Minimum hard rejections to name a trigger | 100 | Hard rejections (5.x.x) a sender must collect in the window to be named as the trigger and, thirty minutes later, throttled. The floor is 10. |
| Delete held bounces after | 72 hours | Held messages never expire on their own; after this age they are deleted, with a notification. |
| Deliver bounces through a dedicated, throttled transport | on | Renders the bounce-out transport and its concurrency and rate limits into Postfix. |
| Bounce queue lifetime | 4h | How long Postfix keeps retrying an undeliverable bounce. Postfix's own default is five days. When the guard is disabled, the value is removed only if the queue parameters page of the instance does not define one of its own. |
Percentages are entered as whole numbers. Durations use the Postfix syntax: a number followed by s, m, h, d or w.

Addresses declared here are rendered into the instance's transport map with the discard transport.
All mail sent to them is discarded, non-delivery reports included.
Declare only addresses that never read mail: the sender of a job, a monitoring tool, an automated report. A mailbox that a human reads must never be listed.

Addresses are validated, stored in lower case and de-duplicated. Removing an address rebuilds the transport map at once.
postqueue -p shows held messages with a ! after the queue id; postqueue -j reports them in the hold queue.from=<>) and whose recipient is the address of the incident. postsuper -H when the incident closes,
The page talks to the local daemon over its private socket. The same routes can be used by scripts on the appliance ({instance} is postfix for the primary instance, postfix-instanceN for the others):
curl -s --unix-socket /run/artica-postfix.sock http://x/postfix/maillog/bounceloop/statuscurl -s --unix-socket /run/artica-postfix.sock http://x/postfix/maillog/bounceloop/settings/postfixcurl -s --unix-socket /run/artica-postfix.sock http://x/postfix/maillog/bounceloop/incidents?days=7curl -s --unix-socket /run/artica-postfix.sock -XPOST http://x/postfix/maillog/bounceloop/incidents/12/releasecurl -s --unix-socket /run/artica-postfix.sock http://x/postfix/maillog/bounceloop/stats?instance=postfix&days=7curl -s --unix-socket /run/artica-postfix.sock http://x/postfix/maillog/bounceloop/noreply/postfix
Two command-line switches of the daemon cover the operational needs:
artica-postfix -bounceloop-status # settings, live window and open incidents (JSON); exit 2 if the daemon is unreachableartica-postfix -bounceloop-release 12 # release incident 12; exit 3 if it is not open
Every level change is recorded as an administrator event:
detection, escalation, release and purge.
The text names the instance, the address the bounces are addressed to, the trigger sender with its client IP and rejection code, the window counters and the number of held messages.
When the SIEM export is configured, the same events reach the SIEM as system events.
The guard never repeats a notification for an incident more often than every thirty minutes.
No message content ever reaches the notifications, the statistics or the SIEM:
the guard works on envelope addresses, queue ids and delivery status codes only, and external recipients are reduced to their domain.