Maildir to mdbox Conversion Silently Drops Emails for Date Ranges

5/5 - (1 vote)

If you have ever run a cPanel migration or triggered a mailbox format conversion in WHM and found that users are missing emails from specific date ranges, you are not alone. This is one of those issues that does not announce itself with a clear error. It simply leaves gaps in the mailbox, and unless someone notices that their March 2024 emails vanished while everything else is intact, you may never know it happened.

This post breaks down why Maildir-to-mdbox conversions can silently drop messages for certain date windows, how to detect the problem, and how to recover the missing mail.

Background: What Actually Happens During Conversion

cPanel servers use Dovecot as the IMAP and POP3 backend, and Dovecot supports two primary mailbox storage formats on cPanel: Maildir and mdbox. Maildir stores each email as an individual file inside cur/ and new/ directories. mdbox packs multiple messages into larger container files under a storage/ directory, using Dovecot’s index files as the authoritative record of what exists.

When you convert from Maildir to mdbox (either through WHM’s Mailbox Conversion interface or during a cPanel Transfer Tool migration), the system relies on Dovecot’s dsync utility to read every message from the Maildir tree and write it into the new mdbox structure. In theory, dsync handles this seamlessly. In practice, several conditions can cause it to skip messages without producing a fatal error.

The Silent Drop: How It Happens

The core problem usually manifests during server-to-server migrations using the cPanel Transfer Tool, but it can also appear during local WHM conversions on systems under I/O pressure. Here is the typical sequence:

The Transfer Tool begins copying an account’s mail from the source server. The source server stores mail in Maildir format. The destination server is configured for mdbox. Somewhere during the transfer, the conversion process encounters an interruption or inconsistency. This could be a network timeout during a long transfer, disk I/O stalls on either end (especially common with NFS-backed storage or overloaded SAN devices), the Dovecot process being killed or restarted mid-conversion, or the transfer tool resuming after a crash without replaying from the correct checkpoint.

When dsync resumes or retries after one of these interruptions, it uses UID-to-GUID mappings and index state to determine which messages have already been synced. If the index state is stale or partially written, dsync may conclude that a batch of messages was already processed when it was not. The result is a gap: emails from a specific date range are present on the filesystem in their original Maildir cur/ and new/ directories, but they never made it into the mdbox storage/ files. Since Dovecot only reads from mdbox once the conversion flag is set, those orphaned Maildir files become invisible to IMAP clients and webmail.

Why It Looks Like a Date Range

The reason missing emails tend to cluster around specific date ranges rather than being randomly scattered comes down to how Maildir files are organized and how dsync processes them. Maildir filenames encode a timestamp (the delivery epoch), and dsync processes messages in UID order within each folder. When an interruption kills a batch mid-stream, the messages that were queued for that batch but not yet committed to mdbox all fall within a contiguous time window. From the user’s perspective, it looks like everything from, say, February 12 through February 28 simply vanished, while emails before and after that range are fine.

How to Detect the Problem

The first sign is usually a user reporting missing emails. They may describe it as “all my emails from last month are gone” or “I can see emails from January and from April, but March is completely empty.” Here is how to confirm the issue at the server level.

Check which format the account is currently using:

cat /home/cpuser/mail/mailbox_format.cpanel

If the output is mdbox, then Dovecot is reading from the mdbox storage tree. Next, look for orphaned Maildir files that were never converted:

find /home/cpuser/mail/example.com/user/ -path "*/cur/*" -type f | head -20
find /home/cpuser/mail/example.com/user/ -path "*/new/*" -type f | head -20

If the account is running in mdbox mode but you still find files in cur/ or new/, those are messages that were left behind during the conversion. You can inspect their timestamps to confirm they match the date range the user reported as missing:

find /home/cpuser/mail/example.com/user/cur/ -type f -printf '%T+ %p\n' | sort | head -20

You can also compare message counts. Count the Maildir files versus what Dovecot sees:

# Count orphaned Maildir messages
find /home/cpuser/mail/example.com/user/ \( -path "*/cur/*" -o -path "*/new/*" \) -type f | wc -l

# Count what Dovecot knows about
doveadm mailbox status -u [email protected] messages INBOX

If the Maildir count is significantly higher than what Dovecot reports, you have confirmed the silent drop.

Recovering the Missing Emails

The fix is to re-import the orphaned Maildir messages into the existing mdbox mailbox using doveadm import. This command reads from a Maildir source and writes into the user’s active mailbox without disrupting existing messages:

doveadm import -u [email protected] \
  maildir:/home/cpuser/mail/example.com/user INBOX ALL

The ALL search key tells doveadm to import every message it finds. The import is additive, so it will not duplicate messages that are already present in mdbox (Dovecot deduplicates based on message headers).

After running the import, have the user check webmail or their IMAP client. The previously missing date range should now be populated.

Batch Recovery for All Users on an Account

If the migration affected multiple mailboxes under the same cPanel account, you can script the recovery:

#!/bin/bash
CPUSER="cpuser"
DOMAIN="example.com"
MAILDIR_BASE="/home/${CPUSER}/mail/${DOMAIN}"

for dir in "${MAILDIR_BASE}"/*/; do
    user=$(basename "$dir")

    # Check if orphaned Maildir files exist
    orphan_count=$(find "$dir" \( -path "*/cur/*" -o -path "*/new/*" \) -type f 2>/dev/null | wc -l)

    if [ "$orphan_count" -gt 0 ]; then
        echo "Found ${orphan_count} orphaned messages for ${user}@${DOMAIN}, importing..."
        doveadm import -u "${user}@${DOMAIN}" maildir:"$dir" INBOX ALL 2>&1
    else
        echo "No orphaned Maildir files for ${user}@${DOMAIN}, skipping."
    fi
done

Watch the output carefully. You may see warnings about index rebuilds or errors about nonexistent subfolders (like INBOX.spam or INBOX.Junk). Index rebuild warnings are generally harmless and indicate that Dovecot is repairing its metadata. Subfolder errors typically mean the source Maildir had a folder that does not exist in the destination’s namespace configuration, and those usually require manual handling.

What About Subfolders?

The example above imports into INBOX, but users may have orphaned messages in subfolders too (Sent, Drafts, and so on). In Maildir, subfolders are represented as dot-prefixed directories like .Sent/, .Drafts/, .INBOX.Archives/. You can extend the recovery to handle these:

# Import from a specific subfolder
doveadm import -u [email protected] \
  maildir:/home/cpuser/mail/example.com/user Sent ALL

Or iterate through all subdirectories that contain cur/ or new/:

for subdir in /home/cpuser/mail/example.com/user/.*/; do
    folder=$(basename "$subdir" | sed 's/^\.//')
    if [ -d "${subdir}/cur" ] || [ -d "${subdir}/new" ]; then
        echo "Importing subfolder: $folder"
        doveadm import -u [email protected] maildir:"$subdir" "$folder" ALL 2>&1
    fi
done

Preventing the Problem

Several practices can reduce the likelihood of this happening in the first place.

Before running a migration, ensure that both the source and destination servers have stable disk I/O and network connectivity. Long-distance transfers over unreliable links are particularly prone to this issue. If the transfer is going to take days, consider using rsync to pre-stage the mail data before invoking the Transfer Tool, so the actual conversion step works against local data instead of pulling over the network.

If you are doing a local conversion in WHM (Email > Mailbox Conversion), avoid running it during peak mail delivery hours. High mail volume during conversion increases the chance that dsync’s index state will become inconsistent with the actual messages on disk.

Always verify conversions after they complete. A simple message count comparison between the Maildir tree and Dovecot’s reported totals will catch gaps early, before users notice. You can automate this as a post-migration check:

#!/bin/bash
CPUSER="cpuser"
DOMAIN="example.com"
MAILDIR_BASE="/home/${CPUSER}/mail/${DOMAIN}"

echo "User | Maildir Files | Dovecot Messages | Delta"
echo "-----|---------------|------------------|------"

for dir in "${MAILDIR_BASE}"/*/; do
    user=$(basename "$dir")
    maildir_count=$(find "$dir" \( -path "*/cur/*" -o -path "*/new/*" \) -type f 2>/dev/null | wc -l)
    dovecot_count=$(doveadm mailbox status -u "${user}@${DOMAIN}" messages INBOX 2>/dev/null | awk '{print $2}')
    dovecot_count=${dovecot_count:-0}
    delta=$((maildir_count - dovecot_count))

    if [ "$delta" -gt 0 ]; then
        echo "${user} | ${maildir_count} | ${dovecot_count} | ${delta} ***MISMATCH***"
    fi
done

Do Not Delete the Maildir Files

This is worth emphasizing: after a Maildir-to-mdbox conversion, do not delete the original Maildir directories until you have confirmed that every message was successfully migrated. cPanel’s own documentation notes that you should not delete the /mailboxes directory after a format conversion. The orphaned Maildir files are your safety net. Once you have verified the conversion is complete and the message counts match, then you can clean them up.

The Bigger Picture

The mdbox format offers real benefits for large mail environments: lower inode usage, reduced disk I/O, and smaller backup footprints. But the conversion path is not as bulletproof as it might appear from the WHM interface. The fact that dsync can exit cleanly (or resume silently) after skipping a batch of messages makes this a particularly insidious problem. There is no big red error banner. There is no alert email. There is just a user two weeks later asking where their February invoices went.

If you are managing cPanel servers and routinely migrating accounts between hosts or converting mailbox formats, building automated verification into your workflow is not optional. It is the only reliable way to catch these silent drops before they become support tickets.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top