Skip to content

Outbound Shaping

Christopher Holloway edited this page Oct 18, 2026 · 6 revisions

Outbound Shaping

Written on 24 September 2026 from hmailserver/docs/OutboundShaping.md, IpWarmup.md, BounceSuppression.md, Complaints.md and ReputationWatch.md, and brought up to 6.3.4 (27 September 2026, schema 6104), on 26 September 2026. Everything on this page is new in 6.3.4, except MaxOutboundPerDestinationPerMinute, a fixed per-destination rate that 6.3.3 already has. The remote domain policies are schema 6045, the back-off and the ceilings have no schema step, and the IP warm-up is schema 6093. 6.3.5 (29 September 2026, schema 6105), 6.3.6 (6 October 2026, schema 6108) and 6.3.7, the latest release (17 October 2026, schema 6108), are not yet reflected here.

How this server slows itself down for a destination that asked, keeps one sender from using every delivery thread, and warms a new outbound address up the way the large providers expect - without touching mail to anybody else, and without spending a message's retries on a wait this server chose. And, beside those, the three things that read how the world is answering what it sends: bounces, complaints and blocklists.

flowchart TD
    Q["A batch of recipients<br/>for one target"] --> A{"Remote domain policy:<br/>largest message?"}
    A -->|"too big"| X["5.3.4, permanent"]
    A --> B{"The destination's<br/>back-off window open?"}
    B -->|"yes"| H["Held, 4.4.5 -<br/>not counted as an attempt"]
    B --> C{"Policy's messages a minute,<br/>connection ceiling,<br/>tenant share, per-minute rate"}
    C -->|"over"| H
    C --> D{"MX lookup, MTA-STS,<br/>then IP warm-up's<br/>daily ceiling"}
    D -->|"over today's ceiling"| W["Held until the next UTC day"]
    D --> E["Gateway encryption,<br/>where the policy asks"]
    E --> F["Connect: DANE, TLS, deliver"]
    F -->|"421 or 'slow down'"| O["Open or double<br/>the back-off window"]
Loading

Contents

The back-off: a destination said slow down

On by default since 6.3.4 - the one part of this page that changes behaviour on upgrade without a switch. When a destination answers a delivery with a request to slow down, it gets a back-off window: nothing is sent to it until the window ends, one delivery then probes it, and the same answer doubles the window. Deliveries to every other destination are untouched.

A reply is read as "slow down" when it is:

  • a 421, at the banner or anywhere after it;
  • a 4xx whose enhanced status is 4.7.0, 4.7.28, 4.7.500, 4.7.650, 4.7.652 or 4.3.2 - the codes the providers decorate their deferrals with;
  • a 4xx whose text contains rate limit, too many messages, too many connections, unusual rate, unexpected volume, temporarily deferred, throttl, try again later, server busy, back off or slow down (the full fixed list is in the document; it is English, and cannot be widened).

And never a 5xx, never 4.7.1 (greylisting), never a reply that says greylist, never a 4.2.x (one mailbox, not the destination), 4.5.3 or 4.4.x, never a refused connection or a DNS failure.

Setting Default Meaning
OutboundBackoffMinutes 2 The first window. 0 turns the back-off off
OutboundBackoffMaxMinutes 60 The ceiling a doubling window stops at

A relay is one destination. With SMTPRelayer set, or a route through a smart host, one slow-down reply from it holds every message that goes through it for the window - which is what the relay asked for, and worth knowing before the window is made long.

Connection ceilings and the tenant share

Both off by default (0), both 0 to 1000:

  • MaxOutboundConnectionsPerDestination - the most sessions open at once to one target that no remote domain policy gives a max_connections of its own (a policy's value wins).
  • MaxOutboundConnectionsPerTenant - the most deliveries one tenant may have in flight. The tenant is the hosted domain of the envelope sender; mail relayed for outsiders, forwarded external mail and the null sender are one tenant, (external), so relayed mail cannot starve the hosted domains either.

Over a ceiling the batch is deferred one to five minutes. The tenant share is a share of connections, bounded by MaxDeliveryThreads; it is not a volume control - the per-account sending limits are (Security Hardening §17.5). The four settings are on the Control Panel's Delivery of e-mail page beside MaxOutboundPerDestinationPerMinute, the Deck's Settings view, /api/v1/settings and COM.

A deferral this server chose is not counted

The back-off, the two ceilings, a remote domain policy's messages a minute and MaxOutboundPerDestinationPerMinute each defer with 4.4.5 ("mail system congestion") and a sentence naming the rule. When every failure of a delivery pass was such a deferral, the message's next try is set to the end of the wait with its tries counter untouched:

Delivery deferred by this server's own shaping, not by a remote server.
Scheduling it for later delivery in N minute(s); the attempt is not counted against the retry limit.

Before this, a deferral this server inflicted on itself spent the message's retries, and with a small retry count a message could be bounced having never been offered to anybody. The hold is bounded: a message queued longer than its retry schedule spans is counted from then on, so a destination that says "slow down" for ever cannot hold mail for ever. Only a failure at a remote spends the retry budget.

IP warm-up

Schema 6093. A large provider judges a sending address by its history, and a new address that starts sending thousands of messages a day looks exactly like a spammer's. A schedule holds one outbound address to a daily ceiling of recipients per large provider, rising day by day along a fixed plan, and the queue sends up to the ceiling and holds the rest - usually until the next UTC day - without spending a retry.

  • One address: the local address a delivery binds - SMTPDeliveryBindToIP (Bind outbound connections to IP on Delivery of e-mail), or a global rule's bind to address action. A schedule does nothing until the address is on the host and one of those binds it; the Deck warns when the setting does not.

  • Per provider, decided from the mail host the delivery connects to, never the recipient's domain - a company on Google Workspace is Google's:

    Provider Mail hosts
    Google google.com, googlemail.com, aspmx.l.google.com
    Microsoft outlook.com, hotmail.com, protection.outlook.com, olc.protection.outlook.com
    Yahoo yahoodns.net, yahoo.com, aol.com
    Apple icloud.com, apple.com
    Proton protonmail.ch, proton.me
    Everyone else counted together, at five times the named providers' ceiling
  • Three fixed plans, recipients per named provider per day:

    Plan Day 1 Day 7 Last day Then
    standard 50 5,000 day 30: 100,000 no ceiling
    gentle 25 250 day 60: 50,000 no ceiling
    fast 100 40,000 day 15: 200,000 no ceiling
  • Recipients, not messages - a message to forty people at Gmail is forty to Google.

Schedules are made on the Deck's queue page or with GET/POST /api/v1/warmup (PUT/DELETE /api/v1/warmup/{id}, and /history for the day-by-day counts), and are in the configuration backup. Nothing is held until a schedule is made. The plans and the provider table are fixed; there is no pool of addresses to spread a day across, and the ceilings rise by the calendar, not by how the providers are answering - a provider already deferring the address is slowed for by the back-off, not the warm-up.

Where to see it

  • GET /api/v1/queue/shaping (the queue's permission, server-wide): the open back-off windows - destination, since, until, the window's minutes, the reply and the host that gave it, how many deliveries it has held - and the connections open under each ceiling and each tenant. DELETE /api/v1/queue/shaping/{destination} clears one window once you have fixed the cause, and is recorded on the audit trail.
  • The Control Deck's Delivery queue shows the same as Slowed destinations, with a Resume button, the connection counts, and the warm-up schedules with their counts and a small editor.
  • The application log says, for each batch held, which rule held it and until when.

The windows and the connection counts live in memory: a restart forgets them, which costs at most one more slow-down reply per destination. Nothing of it is shared between the nodes of a warm-standby pair.

Bounces, complaints and blocklists

Since 6.3.4. Three things read how the world is answering the mail this server sends; each is its own switch.

Feature What it does Off until
Bounces and the suppression list Every refusal and every RFC 3464 report for one of this server's senders is classified by its enhanced status code and counted per recipient and per sending domain; an address that hard-bounces as many times in a row as the threshold says goes on that domain's suppression list, and the next message to it is refused with a sentence (or dropped, or only observed). Visible and removable by hand on Monitoring & troubleshooting → Suppression list, the Deck, hmctl and PowerShell BounceProcessingEnabled
Complaints: the feedback loops The abuse reports the large providers post when their readers press this is spam (RFC 5965) are parsed, recorded against the sending domain and account, counted, and past a threshold put the address on the same suppression list; a complaint rate per domain is in the metrics and raises the complaints.rate.high alert ComplaintProcessingEnabled and a ComplaintMailbox registered with the providers
The reputation watch Once a day, asks the blocklists about the addresses this server sends from, and raises reputation.listed when one is listed On in a new installation, off after an upgrade (reputation_watch_enabled); with no bind address and none named it asks nothing

The relay setting profile switches bounce processing on together with a per-destination ceiling, and sender adds the feedback loops and a per-tenant ceiling (Profiles and Environment). A mailing list keeps bounce counts of its own beside these (Mailing Lists).

What it does not do

  • The back-off's phrases and the warm-up's providers and plans are fixed, and cannot be edited.
  • The tenant share is not a volume control; the per-account sending limits are.
  • The back-off reads the first non-fatal reply a recipient got, so a genuine 421 from a second MX host after a plain 4xx from the first is not seen.
  • The warm-up counts are not exported as metrics; they are on the Deck and the routes.
  • Nothing here is a reputation service. It slows down when asked, holds to a plan, and tells you what came back; it does not ask the providers how they rate this server.

See also: Routes and Relays · Monitoring and Health · Troubleshooting · Encryption and Certificates · Stopping Spam

Home — full index

Start here

1. Install and run

2. Secure it

3. Operate it

4. Extend it

5. Contribute to it

Look it up — from any journey


About this wiki

Clone this wiki locally