The Postfix mail queue is where messages wait before delivery. Understanding how to monitor, manage, and troubleshoot the queue is essential for maintaining healthy mail flow. A growing queue typically indicates delivery problems — DNS issues, remote server rejections, or resource constraints. This guide covers queue management commands, monitoring strategies, and common troubleshooting scenarios.
Understanding Postfix Queues
Postfix uses several internal queues:
- incoming — newly received messages awaiting processing
- active — messages currently being delivered (limited to a configurable size)
- deferred — messages that failed delivery temporarily (will be retried)
- hold — messages placed on hold by an administrator
- corrupt — damaged messages that cannot be processed
Basic Queue Commands
# View queue summary
postqueue -p
# Or use mailq (same output)
mailq
# Count messages in queue
postqueue -p | tail -n 1
# Or more precisely:
find /var/spool/postfix/deferred -type f | wc -l
find /var/spool/postfix/active -type f | wc -l
# View a specific message
postcat -q QUEUE_ID
# Flush the queue (retry all deferred messages now)
postqueue -f
# Flush messages for a specific domain
postqueue -s example.com
# Delete a specific message
postsuper -d QUEUE_ID
# Delete ALL messages (DANGEROUS)
postsuper -d ALL
# Delete all deferred messages
postsuper -d ALL deferred
# Put a message on hold
postsuper -h QUEUE_ID
# Release a held message
postsuper -H QUEUE_ID
# Requeue a message (reprocess from scratch)
postsuper -r QUEUE_ID
Advanced Queue Analysis
# Find messages by sender
postqueue -p | grep "sender@example.com"
# Count messages per sender
postqueue -j | python3 -c "
import sys, json, collections
senders = collections.Counter()
for line in sys.stdin:
msg = json.loads(line)
senders[msg.get('sender', '')] += 1
for sender, count in senders.most_common(20):
print(f'{count:6d} {sender}')
"
# Count messages per recipient domain
postqueue -j | python3 -c "
import sys, json, collections
domains = collections.Counter()
for line in sys.stdin:
msg = json.loads(line)
for rcpt in msg.get('recipients', []):
domain = rcpt.get('address', '').split('@')[-1]
domains[domain] += 1
for domain, count in domains.most_common(20):
print(f'{count:6d} {domain}')
"
# Find messages with specific error reasons
postqueue -j | python3 -c "
import sys, json
for line in sys.stdin:
msg = json.loads(line)
for rcpt in msg.get('recipients', []):
reason = rcpt.get('delay_reason', '')
if 'connection timed out' in reason.lower():
print(msg['queue_id'], rcpt['address'], reason[:80])
"
Bulk Queue Operations
# Delete all messages from a specific sender
postqueue -j | python3 -c "
import sys, json
for line in sys.stdin:
msg = json.loads(line)
if msg.get('sender') == 'spammer@example.com':
print(msg['queue_id'])
" | while read id; do postsuper -d "$id"; done
# Delete all messages to a specific domain
postqueue -j | python3 -c "
import sys, json
for line in sys.stdin:
msg = json.loads(line)
for rcpt in msg.get('recipients', []):
if 'broken-domain.com' in rcpt.get('address', ''):
print(msg['queue_id'])
break
" | while read id; do postsuper -d "$id"; done
# Hold all messages from a sender
postqueue -j | python3 -c "
import sys, json
for line in sys.stdin:
msg = json.loads(line)
if msg.get('sender') == 'suspect@example.com':
print(msg['queue_id'])
" | while read id; do postsuper -h "$id"; done
Monitoring the Queue
Simple Monitoring Script
#!/bin/bash
# /usr/local/bin/check-mailqueue.sh
THRESHOLD=500
QUEUE_SIZE=$(find /var/spool/postfix/deferred -type f 2>/dev/null | wc -l)
if [ "$QUEUE_SIZE" -gt "$THRESHOLD" ]; then
echo "WARNING: Postfix deferred queue has $QUEUE_SIZE messages (threshold: $THRESHOLD)" | \
mail -s "Mail Queue Alert on $(hostname)" admin@example.com
fi
# Crontab: */5 * * * * /usr/local/bin/check-mailqueue.sh
Prometheus Monitoring
# Use postfix_exporter for Prometheus metrics
# Key metrics:
# postfix_queue_length{queue="active"}
# postfix_queue_length{queue="deferred"}
# postfix_queue_length{queue="hold"}
# postfix_smtpd_connections_total
# postfix_smtp_deliveries_total
Queue Configuration Tuning
# /etc/postfix/main.cf
# Active queue size (concurrent deliveries)
qmgr_message_active_limit = 20000 # Default: 20000
# Retry schedule for deferred messages
minimal_backoff_time = 300s # First retry after 5 minutes
maximal_backoff_time = 4000s # Maximum retry interval
queue_run_delay = 300s # How often to scan deferred queue
# Maximum time in queue before bouncing
maximal_queue_lifetime = 5d # Default: 5 days
bounce_queue_lifetime = 5d # For bounce messages
# Per-destination concurrency
default_destination_concurrency_limit = 20
smtp_destination_concurrency_limit = 20
# Rate limiting (be a good mail citizen)
default_destination_rate_delay = 1s # Delay between messages to same destination
smtp_destination_rate_delay = 0 # 0 = no delay (default)
Common Queue Problems
Queue Growing Due to DNS Failures
# Check if DNS resolution works
dig MX gmail.com
dig A gmail-smtp-in.l.google.com
# Check Postfix DNS configuration
postconf smtp_dns_support_level
postconf inet_protocols # Ensure ipv4 is included if IPv6 is problematic
# Fix: ensure resolv.conf has working nameservers
cat /etc/resolv.conf
Queue Growing Due to Remote Rejections
# Check mail logs for rejection reasons
grep "status=deferred" /var/log/mail.log | tail -20
grep "status=bounced" /var/log/mail.log | tail -20
# Common rejection reasons and fixes:
# "450 4.7.1 Client host rejected: cannot find your reverse DNS"
# → Set up PTR record for your IP
# "550 5.7.1 Message rejected due to SPF"
# → Fix SPF record: dig TXT yourdomain.com
# "421 Too many connections from your IP"
# → Reduce concurrency: smtp_destination_concurrency_limit = 5
Best Practices
- Monitor queue size with alerts — a growing deferred queue always indicates a problem
- Use
postqueue -j(JSON output) for scripted queue analysis instead of parsing text output - Never blindly flush the entire queue (
postqueue -f) — investigate why messages are deferred first - Set appropriate
maximal_queue_lifetime— 5 days is standard, but adjust for your needs - Check mail logs alongside queue data for the full picture of delivery issues
- Use hold queue to quarantine suspicious messages for review before deletion
- Implement rate limiting to avoid overwhelming destination servers