Outgoing mail is deferring
When to use: the Mail queue keeps growing, mailctl doctor reports a large mail queue count, users say their mail “is stuck in the outbox” or recipients never receive it, or the Delivery log shows outbound mail as soft (deferred).
Preconditions: shell access to the host; admin access to the console.
A deferred message is not lost: Postfix retries it on a backoff (from 5 minutes up to a little over an hour between attempts) and returns it to the sender only after 5 days in the queue. Find the cause before doing anything to the queue — the cause decides whether a flush helps or hurts.
Not deferral: refused at sending with “Sending is suspended”. If users are refused when they send, with
554 5.7.1 … Sending is suspended: this server's licence has lapsed, nothing is queued and nothing is wrong with
delivery: the licence lapsed on billing more than 90 days ago (the send_suspended stage). Mail to addresses on this
server still goes through. Renew on Licence, or mailctl license activate; sending returns at once
(Licensing).
-
See what is waiting and why. Mail queue in the console lists every message with Postfix’s own delay reason (Mail queue (viewer, hold / release / delete)). On the host, the same queue grouped by reason:
Terminal window docker exec mailserver-postfix-1 postqueue -p | tail -1docker exec mailserver-postfix-1 postqueue -p | grep '^ *(' | sort | uniq -c | sort -rn | headThe first prints the total (
-- 60 Kbytes in 19 Requests.); the second counts each distinct reason. The delivery attempts themselves are in the Postfix log and, classified, in the Delivery log (directionoutbound):Terminal window docker exec mailserver-postfix-1 grep 'status=deferred' /var/log/mail/postfix.log | tail -20Each line names the
relay=Postfix tried and the remote server’s reply. One reason across every destination points at this server (steps 2–4); one destination with many deferrals points at that receiver (step 5); one sender with many messages points at a mailbox (step 6). -
connect to …:25: Connection timed outfor every destination: outbound port 25 is blocked. Most cloud providers block it by default.mailctl doctortests it directly (outbound smtp :25). AWS, Oracle and Hetzner lift the block on request; GCP and Azure never do. The alternative is an outbound relay: Settings → Outbound relay. See Doctor → Common findings. -
A relay is configured and every message defers at it. With a relay, every outbound line names it as
relay=, and the reply is the relay’s. Check what Postfix is using:Terminal window docker exec mailserver-postfix-1 postconf -h relayhostdocker exec mailserver-postfix-1 grep 'SASL authentication failed' /var/log/mail/postfix.log | tail -10SASL authentication failedwith a reply from the relay — the relay refused the username and password. The usual cause is a credential rotated or revoked at the relay provider (for Amazon SES, the SMTP credentials of an IAM user). Generate new ones there and enter them on Settings → Outbound relay, then Validate & apply.SASL authentication failed … no mechanism available— the postfix image lacks the SASL modules; see Doctor.- The relay password cannot be read back — it is stored sealed and is write-only: no screen, API response or command shows it. If Settings → Outbound relay says The stored password can no longer be read,
SECRET_ENCRYPTION_KEYhas changed since it was saved (config.relay.passwordUnreadableinGET /api/v1/server/config). That alone does not stop mail — Postfix keeps the credentials it already has for that host and username — but enter the password again before the next change to the relay (Backup & restore →SECRET_ENCRYPTION_KEYis part of the backup). - With a relay configured Postfix requires TLS to it (
smtp_tls_security_level = encrypt), so a relay host or port that does not offer STARTTLS defers every message too.
mailctl doctorreports the configured relay as anoutbound relayline but does not test it, so a relay that refuses the credentials looks healthy there. -
Host or domain name not foundfor every destination — the server cannot resolve DNS. For one domain only, that domain’s MX is broken and the problem is the recipient’s. -
One receiver defers or refuses: reputation and rate limits.
- The reply names a blocklist (
blocked using zen.spamhaus.org, Spamhaus, SpamCop, Barracuda) or says “spam” or “reputation”: the Delivery log classes itreputation. Follow The server IP is on a blocklist, and check the dashboard’s Server reputation. - The receiver is rate-limiting this server (a 4xx naming rate, volume or “try again later”): the Delivery log classes it
policy. It clears on its own as Postfix’s retries spread out. A new IP is the usual cause: keep IP warm-up on (Outbound rate limits) and see Mail from a domain lands in spam.
- The reply names a blocklist (
-
One sender fills the queue: a compromised mailbox. Hundreds of messages from one address, to recipients that mean nothing to its owner, is the signature. Look for:
- an
outbound_abusealert on the dashboard, which names the mailbox. By default it only alerts — the mailbox keeps sending until somebody acts (Compromised-mailbox containment); - a
rate_limit_burstwarning: the mailbox keeps hitting its own hourly limit.
Stop it by suspending the mailbox (Suspended on its page, which also refuses its sign-ins and its inbound mail; its stored mail is kept), then follow Security incident. Then deal with what it already queued: search the Mail queue for the sender, Hold the messages (reversible; held mail is never delivered), and delete them once you no longer need them as evidence. Deleting is permanent and silent: the sender is not told, and nothing reaches the delivery log.
The outbound rate limits are not what fills the queue. A sender over its hourly budget is refused at submission with
451 4.7.1 Ratelimit "user" exceeded(or"sender_domain","warmup"), so that mail stays in the client’s outbox and never reaches the queue. The Delivery log shows those refusals as Rate limited. - an
-
Once the cause is fixed, let the queue drain. Postfix retries everything deferred on its own schedule, so doing nothing is always safe. To retry now:
Terminal window docker exec mailserver-postfix-1 postqueue -i <queue id> # one messagedocker exec mailserver-postfix-1 postqueue -f # everything deferredThe console deliberately has no flush; Release only returns a held message to
deferredto wait for the next retry. Do not flush:- while the server is listed on a blocklist or a receiver is rate-limiting it — every early retry is another refusal, and flushing a queue into a blocklist is how a server gets listed more widely;
- while the queue still holds a compromised mailbox’s mail — hold or delete that first, or the flush delivers the spam;
- before the fix is confirmed — retry one message with
postqueue -iand read its result in the log first.
Verification
Section titled “Verification”postqueue -p (or the Mail queue tiles) shows the deferred count falling; the Postfix log shows status=sent for new outbound mail; a test message to an outside address (Gmail, say) arrives. With a relay, the status=sent lines name it as relay=.
Rollback
Section titled “Rollback”Retrying, holding and releasing are reversible. Deleting a queued message is not: nothing on the server can bring it back. A relay you configured to work around a blocked port 25 is removed by clearing its host on Settings → Outbound relay (Leave the host blank to deliver directly) and applying; the previous configuration is also in GET /api/v1/server/config/history.