2026.08 / mail queue 042
Docker Mailserver Deferred Queue Not Sending? Diagnose It Safely
Your Docker Mailserver is running, but postqueue -p shows messages marked deferred. Perhaps the queue keeps growing, one destination never clears, or mail moves briefly and returns. Flushing the queue can look like the obvious fix. It usually is not.
A deferred message is one Postfix could not deliver yet. Postfix records the latest reason and retries according to its queue schedule. Your job is to preserve that reason, group failures by cause, repair the responsible layer, and only then request another attempt.
Do not delete or repeatedly flush a deferred queue before reading it. The queue ID, destination, timestamp, and last delivery response are the shortest path to the real fault.
First: decide whether the queue is actually stuck
A non-empty queue is not automatically broken. Temporary remote failures are normal: a receiving server may rate-limit you, return maintenance errors, or temporarily reject a connection. Postfix is designed to retain and retry that mail.
Investigate promptly when the oldest message keeps ageing, the count or byte total rises, every destination fails, one domain accumulates many entries, or the same reason repeats without progress. Start with a read-only snapshot:
# Use the real Compose service name
docker compose exec mailserver postqueue -p
# Machine-readable queue output on supported Postfix versions
docker compose exec mailserver postqueue -j
The standard listing shows queue IDs, arrival times, senders, recipients, sizes, and the most recent failure in parentheses. The JSON form emits one object per queued file, which is useful for local grouping without scraping human-formatted output. Keep either output private because it contains addresses and delivery metadata.
1. Correlate one queue ID with recent logs
Choose one representative queue ID from each failure group. Search the container logs for the complete ID rather than reading thousands of unrelated lines:
QUEUE_ID='REPLACE_WITH_QUEUE_ID'
docker compose logs --since 6h mailserver | grep -F "$QUEUE_ID"
Record the delivery agent, remote hostname, enhanced status code, and exact response. Typical outcomes include:
connect to ... timed out: routing, provider SMTP filtering, firewall, remote reachability, or a bad address record.Host or domain name not found: recipient DNS or your resolver path failed.4.7.xpolicy response: temporary rate, reputation, greylisting, authentication, or sender-policy trouble.mail transport unavailableor local socket errors: an internal transport, content filter, or dependent service is unavailable.delivery temporarily suspended: Postfix has remembered repeated destination failures; find the earlier log line containing the underlying response.
A 4xx remote response normally belongs in the deferred queue. A permanent 5xx normally becomes a bounce, although routing or policy configuration can affect the path. Preserve the SMTP reply instead of treating all queued mail as the same incident.
2. Inspect a queued message without printing it publicly
If the log is incomplete, postcat can show the queue record and message. This may expose the full body, addresses, authentication headers, and application data, so run it only in a private terminal:
docker compose exec mailserver postcat -q REPLACE_WITH_QUEUE_ID
Check the envelope sender, recipient, arrival time, next-hop information, and whether this is legitimate mail you expected. Do not paste raw output into a forum or ticket. Redact message content, personal addresses, IDs, internal hostnames, and authentication data first.
3. Group by failure pattern before changing anything
One queue can contain several incidents. Group entries by destination domain and final error:
- All external domains fail: suspect outbound port 25 filtering, host/network policy, resolver failure, a broken relayhost, or a global Postfix transport problem.
- Only one recipient domain fails: focus on that destination’s MX records, remote response, rate limits, and policy.
- Only application mail fails: compare its envelope sender, From domain, authentication, routing, and submission method with a working mailbox message.
- Local recipients fail: inspect mailbox existence, quota, permissions, Dovecot/LMTP availability, and storage capacity.
- Messages are unexpected: stop nonessential sending and treat this as a possible credential or application compromise before releasing anything.
This grouping prevents a dangerous global “fix” for what is really one remote domain or one compromised sender.
4. Check DNS from the mail container’s point of view
Postfix needs recipient MX records and their address records. Test from inside the same container/network namespace:
docker compose exec mailserver getent hosts example.net
docker compose exec mailserver postconf \
relayhost inet_protocols smtp_host_lookup \
smtp_dns_support_level
If diagnostic DNS tools are already present, query the affected domain’s MX and A/AAAA records. Do not install random packages into the running production container merely for one test; use a temporary diagnostic container on the same Docker network if necessary.
Intermittent SERVFAIL, timeouts, or DNSSEC validation failures need resolver investigation. An empty or nonexistent recipient MX path is different from your own SPF, DKIM, or DMARC records: outbound routing begins with the recipient domain’s DNS.
5. Test the network path without confusing SMTP roles
Server-to-server delivery normally uses destination port 25. Client submission uses 587 with STARTTLS or 465 with implicit TLS. Opening 587 on your own server does not repair blocked outbound port 25.
Use the exact destination hostname from the log and a short timeout. Avoid copying production addresses into public notes:
# Run from a suitable private diagnostic environment
nc -vz -w 10 mx.example.net 25
openssl s_client -starttls smtp \
-connect mx.example.net:25 \
-servername mx.example.net
If every external MX times out, check VPS-provider egress restrictions, host firewall rules, Docker networking, and IPv4/IPv6 routing. If only IPv6 fails, do not blindly disable it until you have proved that Postfix is selecting an unusable route; repair the route and DNS, or make a deliberate protocol policy change with a rollback plan. The SMTP timeout guide provides the full layer-by-layer test sequence.
6. Read remote policy responses literally
Temporary policy responses often tell you what to repair. Follow only official links in the receiver’s SMTP response after verifying the hostname. Common branches are:
- Rate limited: stop duplicate tests, reduce unexpected volume, and let Postfix retry. Do not hammer the recipient with manual flushes.
- Authentication or alignment: verify the actual message’s SPF, DKIM, and DMARC results, not just record existence.
- Reverse DNS or EHLO: align the observed sending address, PTR, forward address record, and Postfix hostname.
- Reputation or blocklist: contain the cause before requesting removal; flushing compromised mail can deepen the incident.
- Greylisting: allow normal retries. Repeatedly creating new messages may restart rather than accelerate the process.
For explicit reputation failures, use the blocklist containment and delisting runbook. For authentication rejection, follow the SPF, DKIM, and DMARC alignment checklist.
7. Check local capacity and service health
A healthy Docker status does not prove every mail transport can write or connect. Check service logs, filesystem capacity, inode availability, and the effective Postfix configuration without exposing environment values:
docker compose ps
docker compose logs --since 1h mailserver
docker compose exec mailserver df -h
docker compose exec mailserver df -i
docker compose exec mailserver postfix status
A full filesystem can stop queue or mailbox writes. A failed content-filter socket can defer mail even while Postfix itself runs. If the incident followed a configuration or image change, compare the deployed configuration with the last known-good Git/Compose state before restarting components at random.
8. Retry only after the cause is repaired
Postfix will retry deferred mail automatically. An immediate manual retry is useful only when you have fixed a concrete fault and want controlled evidence.
# Retry one queue ID on Postfix versions that support it
docker compose exec mailserver postqueue -i REPLACE_WITH_QUEUE_ID
# Or request delivery for one destination
docker compose exec mailserver postqueue -s example.net
# Flush all queued mail only when a global fault is fixed
docker compose exec mailserver postqueue -f
Watch the chosen ID in fresh logs and confirm status=sent plus the remote acceptance response. A flush only schedules delivery; it does not prove success. If the same error returns, stop flushing and continue diagnosis.
Do not delete deferred mail as a first response
postsuper is a privileged housekeeping tool that can delete, hold, release, or requeue messages. Those operations are appropriate only after you understand their scope.
- Delete confirmed abusive or unwanted queue IDs individually after preserving incident evidence.
- Hold suspicious mail if you need to prevent delivery while investigating.
- Do not use broad deletion to make a monitoring graph green.
- Remember that deleting a queue file is irreversible and does not notify the original sender in the normal way.
- Requeueing can apply current cleanup rules again and changes the queue ID, so it is not the same as a simple retry.
The official Postfix postqueue manual documents listing, flushing, and scheduling; the postsuper manual describes destructive and privileged operations. Match commands to the Postfix version inside your pinned Docker Mailserver image.
A safe recovery sequence
- Snapshot
postqueue -pand note the oldest arrival time and total size. - Select one queue ID per destination/error group.
- Correlate each ID with logs and preserve the complete latest response.
- Confirm the messages are legitimate before releasing them.
- Test DNS, route, remote policy, local transport, and storage according to the evidence.
- Repair one identified cause; do not stack unrelated changes.
- Schedule one affected ID or destination for retry.
- Verify remote acceptance in the log and watch queue age/count decline.
- Leave unrelated temporary failures for normal Postfix backoff.
The goal is not an empty queue at any cost. It is a queue whose contents are understood, whose temporary failures retry normally, and whose harmful or misconfigured traffic is contained. When you keep the queue ID and last SMTP response at the centre of the investigation, a vague “mail is stuck” report becomes a small set of testable faults.