When manually uploading or restoring emails into a Dovecot-managed mailbox, you may encounter two common issues:

  1. Incorrect mailbox size or quota reporting because Dovecot’s index files aren’t updated automatically.
  2. Incorrect email dates in the mail client because the file modification time (mtime) is set to the upload time rather than reflecting the original email’s “Date” header.

This guide explains how to rebuild the Dovecot indexes and recalculate mailbox quotas, and then how to correct the email file timestamps so that the original sent/received dates are displayed correctly in clients.


Updating Dovecot Indexes and Recalculating Quotas

When emails are manually added to the mail storage (for example, via direct file upload), Dovecot may not immediately reflect the changes because its internal index files and quota databases remain unchanged. To fix this, you need to run a couple of commands:

1. Rebuild the Dovecot Indexes

Dovecot uses index files to cache metadata for faster mail access. If new emails are added, the indexes must be rebuilt. Run the following command:

doveadm index -A "*"
Bash
  • -A processes all user mailboxes.
  • The quoted "*" prevents shell wildcard expansion from including unintended files (especially if you’re running the command in a directory with extra content).

For a specific user, use:

doveadm index -u user@example.com "*"
Bash

This command forces Dovecot to re-read the mailbox directories and update its internal data structures.

2. Recalculate Mailbox Quotas

After reindexing, the quota system may still show incorrect values. Recalculate the quota usage with:

doveadm quota recalc -A
Bash

Or for a specific user:

doveadm quota recalc -u user@example.com
Bash

These commands ensure that the storage usage is recalculated from the current state of the mailbox.


Fixing Incorrect Email Dates

After a manual upload, email files inherit the upload time as their file modification time (mtime), which Dovecot uses when indexing. As a result, emails may display a date corresponding to the upload rather than the original “Date” header inside the email.

The Fix: Update File Modification Times

You can fix this by updating each email file’s mtime to match its “Date” header. To automate this, we’ve created a Bash script that:

  • Processes both the inbox and sent folders.
  • Extracts the “Date” header using formail (part of the procmail package).
  • Uses the touch command to update the file’s mtime.
  • Provides verbose output for every file processed.

Your Environment

In our setup:

  • Inbox path: /home/vmail/<domain>/<user>/cur
  • Sent folder path: /home/vmail/<domain>/<user>/.Sent/cur

The Script

Save the following script as update_email_dates.sh:

#!/bin/bash
# update_email_dates.sh
# Usage:
#   ./update_email_dates.sh user@example.com   # Process specific user's inbox and sent folder
#   ./update_email_dates.sh "*"                # Process all user mailboxes (inbox and sent folder)

# Function to process email files in a given directory
process_mailbox_dir() {
  local dir="$1"
  if [ ! -d "$dir" ]; then
    echo "Warning: Directory $dir does not exist. Skipping."
    return
  fi
  echo "Processing directory: $dir"
  for file in "$dir"/*; do
    if [ ! -f "$file" ]; then
      echo "Skipping $file: Not a regular file."
      continue
    fi
    # Extract the Date header from the email using formail.
    email_date=$(formail -c -x Date < "$file")
    if [ -z "$email_date" ]; then
      echo "Skipping $file: Date header not found."
      continue
    fi
    # Validate and parse the date using the date command.
    parsed_date=$(date -d "$email_date" +"%Y-%m-%d %H:%M:%S" 2>/dev/null)
    if [ $? -ne 0 ]; then
      echo "Skipping $file: Date header '$email_date' is not parseable."
      continue
    fi
    # Update the file's modification time to the extracted date.
    touch -d "$email_date" "$file"
    echo "Updated $file: set mtime to $parsed_date"
  done
}

if [ $# -ne 1 ]; then
  echo "Usage: $0 <username (user@domain) or '*' for all users>"
  exit 1
fi

# Base directory where mailboxes are stored.
# For this setup:
#   Inbox: /home/vmail/<domain>/<user>/cur
#   Sent:  /home/vmail/<domain>/<user>/.Sent/cur
BASE_DIR="/home/vmail"

if [ "$1" = "*" ]; then
  # Process all domains and users.
  for domain in "$BASE_DIR"/*; do
    [ -d "$domain" ] || continue
    echo "Processing domain: $(basename "$domain")"
    for user in "$domain"/*; do
      if [ -d "$user" ]; then
        # Skip directories that start with a dot (if any)
        if [[ $(basename "$user") == .* ]]; then
          continue
        fi
        echo "Processing inbox for user: $(basename "$user")"
        process_mailbox_dir "$user/cur"
        # Process the sent folder for the user if it exists.
        if [ -d "$user/.Sent/cur" ]; then
          echo "Processing sent folder for user: $(basename "$user")"
          process_mailbox_dir "$user/.Sent/cur"
        else
          echo "No sent folder found for user: $(basename "$user")"
        fi
        echo "-------------------------------------------"
      fi
    done
  done
else
  # Process a specific user.
  # Expect argument in the form user@domain.
  email_addr="$1"
  user_part=$(echo "$email_addr" | cut -d '@' -f1)
  domain_part=$(echo "$email_addr" | cut -d '@' -f2)
  if [ -z "$user_part" ] || [ -z "$domain_part" ]; then
    echo "Error: Invalid email address format."
    exit 1
  fi
  user_inbox="$BASE_DIR/$domain_part/$user_part/cur"
  echo "Processing inbox for $email_addr: $user_inbox"
  process_mailbox_dir "$user_inbox"
  
  user_sent="$BASE_DIR/$domain_part/$user_part/.Sent/cur"
  if [ -d "$user_sent" ]; then
    echo "Processing sent folder for $email_addr: $user_sent"
    process_mailbox_dir "$user_sent"
  else
    echo "No sent folder found for $email_addr"
  fi
fi

echo "Email date correction completed."
Bash

How to Use the Script

  1. Install Dependencies:
    Ensure formail is installed (it’s part of the procmail package).
    • On Rocky Linux/Alma Linux/RHEL: dnf install procmail
    • On Debian/Ubuntu: apt-get install procmail
  2. Save the Script:
    Save the script above as update_email_dates.sh.
  3. Convert Line Endings if Necessary:
    If you created the script on a Windows machine, convert CRLF to LF. For example, using dos2unix: dos2unix update_email_dates.sh
  4. Make the Script Executable: chmod +x update_email_dates.sh
  5. Run the Script:
    • For a specific user: ./update_email_dates.sh user@example.com This will update the inbox at /home/vmail/example.com/user/cur and the sent folder at /home/vmail/example.com/user/.Sent/cur.
    • For all users: ./update_email_dates.sh "*" This command processes every user’s inbox and their corresponding sent folder across all domains.
  6. Review the Output:
    The script provides verbose feedback for every file it processes, indicating whether:
    • The file was updated (with the new modification time printed).
    • The file was skipped (with a reason, such as missing or unparseable Date header).
  7. Rebuild Dovecot Indexes:
    Once the modification times have been updated, force Dovecot to rebuild its indexes:
    • For a specific user: doveadm index -u user@example.com "*"
    • For all users: doveadm index -A "*"

Rebuilding the indexes ensures that Dovecot now uses the corrected mtime values, so your email clients will display the original sent/received dates.


Final Thoughts

By following these steps, you can ensure that manually restored emails display the correct dates and that Dovecot’s indexes and quotas are accurate. This comprehensive approach—from reindexing and quota recalculation to correcting file modification times with a custom script—helps maintain a reliable and consistent mail environment.

Share this guide with fellow sysadmins to help them troubleshoot similar issues