Deliverability & Infra

The Anatomy Of The Postfix Queue

The Mythic Intel Team · May 5, 2025 · 7 min read

The Postfix mail queue is not one queue but several, each a directory under the spool with a distinct job: maildrop, incoming, active, deferred, hold, and corrupt. A message moves through them in a fixed order, the queue manager (qmgr) decides what gets delivered and when, and two commands, postqueue and postsuper, let you inspect and manage what is sitting there. Understanding which queue a message is in tells you exactly what is happening to it, which is why reading the queue is the first move when mail is piling up.

The queues live under $queue_directory, typically /var/spool/postfix/. Mail in maildrop, incoming, hold, and deferred is safely on disk; the active queue is special because its scheduling state is held in memory so the queue manager can make global decisions about delivery.

The six queues, in order of flow

  • maildrop: where locally-submitted mail lands first, from the sendmail(1) command or postdrop. The pickup(8) daemon reads it and hands it to cleanup(8). Network mail from smtpd does not pass through here; it goes straight to cleanup and into incoming.
  • incoming: new mail that cleanup(8) has fully written and is ready for delivery. The queue manager imports messages from here into the active queue as capacity allows.
  • active: the only queue the queue manager opens messages from for delivery. It is bounded, by default at most 20000 messages (qmgr_message_active_limit), and its envelope state is managed in memory so qmgr can schedule globally and allocate delivery-agent processes. When active is full, qmgr stops pulling from incoming and deferred.
  • deferred: messages that hit a transient (4xx) failure and could not be delivered on a prior attempt. They wait here and are re-imported into active for retry on a schedule.
  • hold: messages an administrator or a policy/content check has parked indefinitely. Postfix never touches a held message on its own; it stays until you release it.
  • corrupt: queue files Postfix could not read or parse. They are set aside here for inspection rather than silently lost.
$ ls /var/spool/postfix/
active  bounce  corrupt  defer  deferred  flush  hold  incoming
maildrop  pid  private  public  saved  trace

Note defer and bounce are not message queues; they store per-message delivery logs (the reasons used to build DSNs). The message queues are the six above.

What qmgr does

The queue manager is the scheduler. It scans incoming and deferred, moves eligible messages into active, and dispatches them to delivery agents (smtp, local, virtual, lmtp, pipe). Because active is in memory and capped, qmgr controls how much mail is in flight at once and avoids loading the whole spool into RAM.

For deferred mail it applies a backoff. A deferred message gets a "cool-off" period before it is eligible to retry, and the next retry time roughly doubles the message's current age, clamped between minimal_backoff_time and maximal_backoff_time. The deferred queue itself is rescanned on queue_run_delay. A message keeps getting retried until it is delivered or it reaches maximal_queue_lifetime (default 5 days), at which point Postfix bounces it back as a failure.

Inspecting the queue: postqueue

postqueue is the unprivileged-friendly tool for listing and flushing.

$ postqueue -p
-Queue ID-  --Size-- ----Arrival Time---- -Sender/Recipient-------
3F2A1C0E89    1834 Mon Mar  2 14:02:11  [email protected]
                 (host mx.example.net[198.51.100.7] said: 451 4.7.1
                  Greylisted, try again later)
                                         [email protected]

-- 1 Kbytes in 1 Request.

The * flag next to an ID (in postqueue -p) marks a message in the active queue; a ! marks one on hold. postqueue -j gives the same listing as JSON for scripting. postqueue -f flushes the deferred queue, asking qmgr to attempt every deferred message now. Use a flush sparingly: flushing a huge deferred queue at a receiver that is deferring you on purpose just hammers them and can worsen reputation.

Managing the queue: postsuper

postsuper is the privileged tool that actually moves, holds, releases, and deletes queue files. It operates on hold, incoming, active, and deferred by default.

# delete one specific message by queue ID
postsuper -d 3F2A1C0E89

# delete every deferred message (use with care)
postsuper -d ALL deferred

# put a message on hold (stop delivery without deleting)
postsuper -h 3F2A1C0E89

# release a held message back for delivery
postsuper -H 3F2A1C0E89

# requeue a message: re-run it through cleanup (re-applies rules, re-resolves)
postsuper -r 3F2A1C0E89

# clean up leftover/temporary files after a crash
postsuper -s

A requeue (-r) moves the message back to maildrop so pickup and cleanup rebuild a fresh queue file, which is how you re-apply changed transport or rewrite rules to mail already stuck in the spool. postsuper -s is the structural-repair pass that removes orphaned and temporary files left after an unclean shutdown, and is run automatically at Postfix start.

For triage on a backed-up server, qshape buckets the queue by age and destination domain so you can see whether one receiver is the cause:

$ qshape deferred
                        T  5 10 20 40 80 160 320 640 1280 1280+
              TOTAL  4213  9 31 88 140 320 612 901 1100 1012   0
   yahoo.com         3980  2 10 40 100 280 560 860 1050 1078   0

That output says the deferred queue is dominated by yahoo.com, with most messages aging past 320 minutes, a textbook "one receiver is deferring us" picture.

Saying it out loud

In an interview: "Postfix has six message queues. New local mail lands in maildrop, network mail and post-cleanup mail goes to incoming, and the queue manager pulls from there into active, which is in memory and capped at 20000 by default so it can schedule globally. Transient 4xx failures drop to deferred and retry on a doubling backoff until maximal_queue_lifetime, about five days, then bounce. hold is admin-parked, corrupt is unreadable files. I inspect with postqueue -p and manage with postsuper: -d to delete, -h and -H to hold and release, -r to requeue through cleanup, -s to clear crash debris. And I lean on qshape to see if one receiver owns the backlog before I touch anything." That answer shows you can read the queue as a diagnostic, not just clear it.

your turn

Stop reading about interviews. Start training for yours.