← Back home

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:

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:

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:

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.

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

  1. Snapshot postqueue -p and note the oldest arrival time and total size.
  2. Select one queue ID per destination/error group.
  3. Correlate each ID with logs and preserve the complete latest response.
  4. Confirm the messages are legitimate before releasing them.
  5. Test DNS, route, remote policy, local transport, and storage according to the evidence.
  6. Repair one identified cause; do not stack unrelated changes.
  7. Schedule one affected ID or destination for retry.
  8. Verify remote acceptance in the log and watch queue age/count decline.
  9. 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.