emailmarketing.net

MTA Delivery Tuning

Outbound queue theory and delivery tuning as a discipline — queue-age diagnosis, per-destination concurrency and rate shaping, connection reuse, adaptive backoff and dead-site detection, scheduler fairness, and bounce-driven traffic-shaping automation.

Operationalesp-operator

How an outbound mail server decides how hard to push each destination — and how operators diagnose and correct delivery congestion. The concepts below are MTA-agnostic; the queue-diagnosis, concurrency-feedback, and scheduler models are drawn from Postfix's documentation, and the shaping-rule and automation models from KumoMTA's. Every serious MTA (commercial or open source) implements some variant of each. For the concrete per-provider numbers that these mechanisms enforce, see Per-Provider Tuning Baselines.

Why this is a deliverability discipline

Mailbox providers enforce per-sender connection caps, message-rate caps, and messages-per-connection caps, and answer violations with 4xx deferrals (often 421) or throttling penalties. An MTA that pushes past those limits converts good mail into deferred mail, ages its queues, and — at providers that score "unusual traffic patterns" — damages IP reputation. Delivery tuning is the practice of keeping outbound pressure just under each destination's tolerance, automatically backing off when the destination signals distress, and diagnosing queue pathology fast when it appears.

Queue-age distribution as diagnosis (the "qshape" model)

The single most informative view of an outbound MTA is a two-dimensional table: destination domain on one axis, message age on the other, with counts in each cell (Postfix ships this as qshape). Age buckets are fine-grained for recent mail — typically 5, 10, 20, 40, 80, 160, 320, 640, 1280, and 1280+ minutes — with a total column per domain. Run it separately against the incoming/active queue (mail being worked on now) and the deferred queue (mail that already tempfailed).

Reading rules:

  1. Problems live in the top-left. Large counts for a single domain in the young buckets mean an active, ongoing problem. Old-age concentration means a historical problem now draining.
  2. Domain concentration identifies the bottleneck. Sort by row total; the dominating domains are where delivery is failing or throttled.
  3. Active-queue backlog outranks deferred backlog. Mail stuck in the active queue is blocking new work; deferred mail is merely waiting for retry.
  4. A full active queue with a non-full incoming queue means one or more destinations drain slower than their input rate.
  5. Sender-axis view detects backscatter: pivoting the same table by sender instead of recipient reveals when the queue is dominated by bounce messages (MAILER-DAEMON), i.e., the load is self-inflicted bounce processing, not fresh mail.

Characteristic patterns:

Pattern Signature in the age table Diagnosis
Healthy Incoming/active nearly empty; small deferred totals spread across old buckets Normal operation
Deferred backlog, old ages Large deferred totals, counts growing toward the oldest buckets; incoming/active short Historical problem already resolved, retries draining; "high volume of deferred mail is not a direct cause for alarm" by itself
Bounce backscatter Same as above, but sender view shows mostly MAILER-DAEMON Earlier bad send (e.g., dictionary-attacked list) still generating bounce work
Active-queue saturation Active count near its cap (Postfix example: 9,996 of a 10,000 limit; 20,000 in later versions), almost all in the youngest bucket, one domain dominating A destination is draining far slower than input; delivery agents tied up; new mail delayed for all destinations
High-volume destination backlog One domain with large counts in every bucket younger than the problem's start time, still growing Destination down or throttling since roughly the oldest affected bucket; congestion ongoing

Scale expectations (Postfix figures, indicative for any store-and-forward MTA): a deferred queue handles roughly 100,000–1,000,000 messages; good performance is unlikely beyond that.

Per-destination concurrency shaping

MTAs open a limited number of parallel connections per destination and adapt that number from delivery outcomes — deliberately analogous to TCP slow start.

  • Initial concurrency is small (Postfix default: 5 simultaneous deliveries) so a new or recovering destination is probed gently.
  • Maximum concurrency caps parallelism per destination (Postfix default: 20). Per-destination overrides are how provider caps (e.g., a provider that tolerates only 2 connections) are honored.
  • Positive feedback raises concurrency on successful deliveries; negative feedback lowers it on connection or handshake failures. The feedback increment is a function of current concurrency N:
    • +1 per success → exponential ramp (5→10→20) — fast but oscillation-prone;
    • +1/N per success → linear ramp, one extra slot after N successes — gentlest;
    • +1/√N → intermediate. Fractional feedback plus integer truncation yields natural hysteresis: concurrency only steps up after 1/g(N) consecutive successes.
  • Asymmetry matters: negative feedback is applied at the beginning of a failure sequence ("reverse hysteresis") so overload is corrected immediately, not after a full window of failures.
  • Measured effect (Postfix scheduler tests against a receiver enforcing concurrency limits with 421 responses): fixed ±1 feedback deferred ~50% of mail; 1/N feedback deferred ~16.5%; 1/√N ~24.5%. Gentler feedback dramatically reduces deferrals at limit-enforcing receivers.
  • Caveat: sub-1 feedback has little effect at low volumes to a destination — deliveries are too infrequent for the counters to move.

Dead-site detection and penalty boxes

Distinct from concurrency feedback: when a whole cohort of delivery attempts (N attempts, N = current concurrency) fails with connection/handshake errors, the destination is declared dead after a configurable number of failed cohorts (Postfix default: 1 cohort). Dead destinations are put in a penalty box — skipped entirely for a period — instead of being hammered at concurrency 1. Separating "site is down" from "site is slow" lets concurrency feedback stay gentle without wasting delivery agents on unreachable hosts.

Rate shaping, connection reuse, and the shaping-parameter vocabulary

Beyond concurrency, modern MTAs shape traffic per destination along several independent axes (names from KumoMTA; every MTA has equivalents):

Parameter (concept) Meaning Typical global default (KumoMTA)
Connection limit Max simultaneous connections to the destination 10
Connection rate Max new connections per unit time 100/min
Message rate Max messages per unit time regardless of connections 100/s
Deliveries per connection Messages sent before closing/reopening an SMTP session 100
Idle timeout How long a cached connection may sit idle before close 60 s
Data timeouts Timeouts for DATA phase and final dot 30 s / 60 s
TLS mode Opportunistic vs. Required per destination Opportunistic
Consecutive connection failures before delay Failure count that triggers backoff 100

Connection reuse (sending many messages per connection) is a big lever: connection setup, TLS negotiation, and the receiver's per-connection accounting all cost more than an extra MAIL FROM. But providers cap messages-per-connection and answer excess with errors like "Max message per connection reached" — so the value is per-provider (50 for Gmail, 20 for Yahoo, 5 for Apple in community baselines). Recipients-per-message-copy is a related knob (Postfix default: 50 recipients per copy; larger recipient lists are split into parallel copies).

Destination grouping (MX rollup): shaping limits should apply per receiving infrastructure, not per recipient domain. Thousands of hosted domains share Google's or Microsoft's MX fleet; rolling up all domains whose MX records match a suffix (e.g., .google.com/.googlemail.com) into one shaping bucket prevents accidentally multiplying your effective connection count by the number of recipient domains. Conversely, a provider whose MX names are regional silos (e.g., Mimecast site names) should be limited per-MX-site, not per-provider.

Retry scheduling and adaptive backoff on 4xx

Tempfailed (4xx) mail is timestamped into the future with exponential backoff between a floor and ceiling (Postfix defaults: minimum 300 s, maximum 4,000 s), and the deferred queue is rescanned periodically (default every 300 s). Messages that cannot be delivered within the queue lifetime (default 5 days; bounces often given their own, also 5 days) are returned as permanent failures.

Operational rules:

  • Do not shorten retry intervals to "clear the backlog." More frequent retries of undeliverable mail saturate the active queue and tie up delivery agents on dead sites, starving deliverable mail. Fixing the cause always beats retrying harder.
  • Timeouts bound worst-case throughput. Example: 100 delivery agents, a destination with 2 MX hosts of which one is down, 30-second connect timeout → ~6 messages/second ceiling, because half the connection attempts burn a full timeout. Shorter connect timeouts (and trying the next MX quickly) restore throughput under partial outages.
  • Isolate problem destinations. Give chronically slow or flaky destinations a dedicated delivery pool with reduced timeouts and adjusted concurrency, or shunt them to a fallback "graveyard" relay with its own retry cadence, so they cannot degrade mainstream delivery.

Scheduler fairness and preemption

Inside the MTA, a scheduler decides which message gets the next free delivery slot:

  • Round-robin across transports/destinations ensures no single destination monopolizes delivery agents.
  • FIFO within a destination, by queue-entry time — except for preemption.
  • Preemption lets small messages slip past bulk ones without starving the bulk mail: as a large message's recipients are delivered, it accumulates "delivery slots" (one per K delivered recipients; Postfix default cost K=5). A candidate preemptor is picked by maximizing enqueue_time / recipient_count (oldest-relative-to-size first) and needs a minimum slot count (default 3). Slot loans (default up to 3 slots, at a 50% discount) let a small message jump immediately and "pay back" the advance from subsequent deliveries — expedited handling for transactional-sized mail without abandoning fairness to bulk campaigns. This is the in-MTA analogue of the ESP-level practice of separating transactional and bulk streams.

Bounce-driven traffic-shaping automation

Static shaping tables set the ceiling; automation adjusts below it in real time by parsing what providers say in their SMTP responses. The model (KumoMTA's Traffic Shaping Automation, run as a separate daemon that watches delivery logs and pushes config back to the MTA — clusterable so all nodes react together):

  • Match: regex rules over bounce/deferral response text, scoped per provider/site, per tenant, or per campaign.
  • Trigger: fire immediately or only past a threshold (e.g., 2 matches/hour — distinguishing a one-off from a pattern).
  • Act: either Suspend (stop sending to that destination from that queue entirely) or SetConfig (tighten a shaping parameter — drop connection_limit to 1, cap max_message_rate at 1/minute, cut max_deliveries_per_connection). Tenant-scoped suspension exists for responses that indicate a sender problem (e.g., missing authentication) rather than a traffic problem.
  • Expire: every action carries a duration (commonly 15 minutes–4 hours); when it lapses, normal shaping resumes automatically. This makes automated throttling self-healing — no operator has to remember to un-throttle.

Representative community rules (see Per-Provider Tuning Baselines for the full table): Gmail "unusual rate" responses → cap at 10 messages/minute for 30 minutes; Yahoo [TSS04] complaint deferrals → suspend 2 hours; Outlook "exceeded the maximum number of connections" → connection limit 1 for 1 hour; a generic default rule matches the cross-provider family of "temporarily deferred / rate limited due to IP reputation / Server busy" texts and simultaneously drops the message rate to 1/minute and connections to 1 for 90 minutes.

Design principles for building your own rules:

  1. Parse the provider's words, not just the code. 421 alone is ambiguous; [TS02] vs [TS03] vs "Max message per connection reached" each warrant different responses (see the provider error-code articles: Gmail, Yahoo, Microsoft).
  2. Match the action to the complaint. Connection complaints → cut connections; volume complaints → cut message rate; reputation/complaint deferrals and blocklistings → suspend and let the reputation cool; content rejections → suspend and alert a human (throttling won't fix content).
  3. Throttle harder for reputation signals than for mechanical limits. Hitting a connection cap is a tuning miss; "deferred due to user complaints" is a reputation event.
  4. Keep durations short and let rules re-fire. A 30–120 minute penalty that renews while the provider keeps complaining converges to the right rate without permanently crippling the queue.

Layering shaping configuration

Maintain shaping data in layers so updates don't destroy local knowledge: a vendor/default layer, a community/shared layer, and a local layer of your own rules, later layers overriding earlier ones (with an explicit "replace everything for this domain" escape hatch). Never edit the distributed layers in place — they are overwritten on upgrade. Most deployments need local rules: the shared baselines are deliberately not all-encompassing.

Related articles

#operations#mta#queues#throttling#concurrency#retry#backoff#traffic-shaping#automation