Docs / Email Servers / Monitoring and Managing the Postfix Mail Queue

Monitoring and Managing the Postfix Mail Queue

By Admin · Mar 15, 2026 · Updated Apr 23, 2026 · 473 views · 5 min read

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

Was this article helpful?