Anatomy of an email stuck in queue: mail server logs, domain typos, and Exim smarthost mechanics
Back to blog

Anatomy of an email stuck in queue: mail server logs, domain typos, and Exim smarthost mechanics

10/9/2026 · 6 min · Email

Anyone managing mail servers regularly encounters outbound messages stuck in the delivery queue without an obvious root cause. Very often, what seems like network flakiness or relay server instability is simply an extra keystroke made by the sender when typing the recipient's address.

However, the way the local Mail Transfer Agent (MTA) and the upstream relay server (smarthost) react to that small mistake exposes an interesting sequence of operations: process forks, C-level DNS lookups, intermediate spam scanners, and local retry databases.

In this guide, we will break down a real log excerpt from Exim to understand the complete lifecycle of a message sent to a mistyped domain, why it does not bounce immediately with a permanent error, and how to clean up that backlog from the terminal.


1. Event log inspection in Exim#

When investigating spooled messages on Linux servers running cPanel, DirectAdmin, or standalone Exim installations, the first place to look is /var/log/exim_mainlog.

The excerpt below shows the trail of a message sent to several valid recipients, plus one mistyped address ([email protected] instead of .com.br):

2026-04-23 10:30:51 1wFu8p-000000062Hd-1Bob <= [email protected] H=(ADMCON037) [186.237.144.42]:58230 P=esmtpsa X=TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256 A=dovecot_login:[email protected] S=49523 [email protected] T="RES: Listagem de cobranca" for [email protected] [email protected] [email protected] [email protected] [email protected] [email protected]
2026-04-23 10:30:51 cwd=/ 13 args: exim -bm -oem -oi -oMr spam-scanner -oMm 1wFu8p-000000062Hd-1Bob -oMai [email protected] -f [email protected] <[email protected]>
2026-04-23 10:30:51 1wFu8p-000000062NQ-2GiD <= [email protected] R=1wFu8p-000000062Hd-1Bob U=mailnull P=spam-scanner S=50322 [email protected] T="RES: Listagem de cobranca" for [email protected]
2026-04-23 10:30:51 1wFu8p-000000062Hd-1Bob <[email protected]>: imunifyemail_spamfilter_transport transport output: action=no action score=-4.359000/6.000000
2026-04-23 10:30:51 1wFu8p-000000062Hd-1Bob => [email protected] R=imunifyemail_spamfilter_router T=imunifyemail_spamfilter_transport
2026-04-23 10:30:52 1wFu8p-000000062NQ-2GiD == [email protected] R=smarthost_auth T=dkim_remote_smtp defer (-44) H=smarthost.domain.com [216.55.99.57]: SMTP error from remote mail server after RCPT TO:<[email protected]>: 450 4.1.2 <[email protected]>: Recipient address rejected: Domain not found
2026-04-23 10:43:06 1wFu8p-000000062NQ-2GiD == [email protected] routing defer (-52): retry time not reached
2026-04-23 10:58:06 1wFu8p-000000062NQ-2GiD == [email protected] R=smarthost_auth T=dkim_remote_smtp defer (-44) H=smarthost.domain.com [216.55.99.57]: SMTP error from remote mail server after RCPT TO:<[email protected]>: 450 4.1.2 <[email protected]>: Recipient address rejected: Domain not found

2. Breaking down the execution flow line by line#

To understand why this message stayed in the spool directory, let us review the four critical stages recorded by the log.

Step 1: Ingress and sender authentication (<=)#

2026-04-23 10:30:51 1wFu8p-000000062Hd-1Bob <= [email protected] H=(ADMCON037) [186.237.144.42]:58230 P=esmtpsa ... A=dovecot_login:[email protected]

In this initial entry, Exim accepts the inbound TCP connection:

Step 2: Spam inspection and the child process fork#

2026-04-23 10:30:51 cwd=/ 13 args: exim -bm -oem -oi -oMr spam-scanner ...
2026-04-23 10:30:51 1wFu8p-000000062NQ-2GiD <= [email protected] R=1wFu8p-000000062Hd-1Bob U=mailnull P=spam-scanner ...

Before dispatching the message outward, the mail system passes it through an antispam scanner (such as ImunifyEmail or SpamAssassin):

  1. Exim launches a child process via execve() passing -oMr spam-scanner.
  2. The message is inspected and re-injected into the spool under the system user mailnull.
  3. This new delivery cycle receives its own identifier: 1wFu8p-000000062NQ-2GiD, recording its parent message ID with R=1wFu8p-000000062Hd-1Bob.
  4. The scanner returns a score of -4.359000 (well below the discard threshold of 6.0), allowing delivery to proceed.

Step 3: Upstream deferral at the smarthost (defer -44)#

2026-04-23 10:30:52 1wFu8p-000000062NQ-2GiD == [email protected] R=smarthost_auth T=dkim_remote_smtp defer (-44) H=smarthost.domain.com [216.55.99.57]: SMTP error from remote mail server after RCPT TO:<[email protected]>: 450 4.1.2 <[email protected]>: Recipient address rejected: Domain not found

Here is where the delivery stalls:

  450 4.1.2 Recipient address rejected: Domain not found

Step 4: Exponential backoff and the retry database (defer -52)#

2026-04-23 10:43:06 1wFu8p-000000062NQ-2GiD == [email protected] routing defer (-52): retry time not reached

Roughly 12 minutes later, a scheduled queue runner (exim -q) re-evaluates spooled messages. Exim inspects its local retry database (retry.db) and logs routing defer (-52): retry time not reached.

This confirms that the calculated exponential backoff window has not yet elapsed, preventing useless CPU overhead and redundant network requests to the relay.


3. Why the smarthost replies with 450 instead of 550#

A recurring question in email diagnostics is: if the domain clearly does not exist, why didn't the server reject it outright with a 550 permanent failure?

The explanation lies in RFC 5321 conventions and how relay MTAs implement recipient verification.

Internal DNS check logic in c#

Many smarthosts run Postfix with reject_unknown_recipient_domain enabled inside smtpd_recipient_restrictions.

In Postfix's C source code (smtpd_check.c), domain verification calls standard resolver routines:

/* Simplified DNS lookup routine in smtpd_check.c */
int check_domain_dns(const char *domain) {
    unsigned char reply[1024];
    int len;

    /* 1. Query MX records */
    len = res_search(domain, C_IN, T_MX, reply, sizeof(reply));
    if (len >= 0) return DNS_FOUND;

    /* 2. Fallback to A record (IPv4) */
    len = res_search(domain, C_IN, T_A, reply, sizeof(reply));
    if (len >= 0) return DNS_FOUND;

    /* 3. Evaluate library error codes */
    if (h_errno == HOST_NOT_FOUND) {
        return DNS_NXDOMAIN;
    }
    return DNS_RETRY;
}

When this lookup fails, the relay server must choose between:

  1. Permanent failure (550): the domain definitely does not exist globally.
  2. Temporary failure (450): the query failed, but it could be caused by temporary packet drops on UDP port 53, resolver timeouts on the relay host, or a newly registered domain still propagating.

Many corporate relays deliberately configure this check to respond with 450 to prevent wrongful permanent bounces during transient DNS hiccups. The side effect is that obvious typos (like .copm.br or .gmial.com) do not bounce back to the sender right away; instead, the email sits in queue retrying for hours or days.


4. Ruling out edge cases before making changes#

Even when a typo appears obvious, it is good practice to rule out two edge cases:

Case 1: Local or upstream DNS resolver failure#

Verify whether the domain is truly non-existent or if your resolvers are failing to reach authoritative nameservers:

# 1. Direct query to global public DNS
dig MX domain.copm.br @8.8.8.8 +trace +nodnssec

# 2. Check for leftover local entries in /etc/hosts
grep -i "domain.copm.br" /etc/hosts

If the response returns status: NXDOMAIN, the domain is definitively unresolvable.

Case 2: Greylisting or rate limiting disguised as a routing error#

Certain enterprise filters return generic recipient rejection strings when a sender exceeds volume thresholds or hits a greylist policy. You can simulate the raw SMTP exchange with netcat:

nc -vv smarthost.domain.com 25 << 'EOF'
EHLO my-server.local
MAIL FROM:<[email protected]>
RCPT TO:<[email protected]>
QUIT
EOF

If the response remains 450 Recipient address rejected, the relay's recipient verification rule is operating as designed.


5. Practical commands to inspect and clear the queue#

Here are the commands to inspect spooled items in Exim and purge them safely.

Inspecting metadata, body, and message logs#

To read the header file in the spool (/var/spool/exim/input/<ID>-H):

exim -Mvh 1wFu8p-000000062NQ-2GiD

To view the raw message body stored on disk (/var/spool/exim/input/<ID>-D):

exim -Mvb 1wFu8p-000000062NQ-2GiD

To list all log entries recorded for this specific message ID:

exim -Mvl 1wFu8p-000000062NQ-2GiD

Checking the Exim retry database#

Exim stores past delivery failures in a binary database at /var/spool/exim/db/retry. To dump its contents:

exim_dumpdb /var/spool/exim retry

To override the backoff timer and force an immediate delivery attempt:

exim -M -v 1wFu8p-000000062NQ-2GiD

The -v flag streams the live SMTP session directly to your terminal.

Purging malformed messages from the queue#

Once you have verified that the address contains an error and other recipients already received their copies, purge the message:

exim -Mrm 1wFu8p-000000062NQ-2GiD

This issues unlink() syscalls on the header (-H) and data (-D) files, freeing disk space and inodes.

If multiple messages are backed up for the same mistyped domain, combine exiqgrep with xargs:

exiqgrep -i -r 'domain.copm.br' | xargs -r exim -Mrm

6. Tracing delivery connections with strace#

If you need to observe the exact network exchange between Exim and the smarthost (including the RCPT TO command and the subsequent 450 reply), attach strace while forcing delivery:

strace -f -s 512 -e trace=network,write,read exim -M 1wFu8p-000000062NQ-2GiD

The flags used here:


Practical routine to prevent queue congestion from mistyped domains#

To prevent your mail queue from accumulating dead weight without requiring manual daily cleanup:

  1. Tune queue expiry parameters: review ignore_bounce_errors_after and timeout_frozen_after in your Exim configuration so unresolvable messages are automatically discarded after 24 to 48 hours.
  2. Monitor recurring deferred domains: set up a cron check that flags destinations accumulating dozens of deferred entries in exiqsumm.
  3. Notify senders promptly: when users make common typos (.copm.br, .con.br, .gmial.com), alert them early so they update their email clients rather than repeatedly resending messages that saturate the retry database.

Was this article helpful?

Leave a quick reaction to help prioritize future technical guides:

CC BY-NC

This post is licensed under CC BY-NC.

Comments

Join the discussion below.

0 comments