Safely Expunge Matching Dovecot Messages with doveadm
You will preview a Dovecot search, confirm its scope, and permanently remove the matching messages from one mailbox or a deliberately chosen set of users. Allow about fifteen minutes for a single-user cleanup, longer if you need to audit a batch. You need shell access to the mail host, Dovecot's doveadm command, and enough privilege to run mail administration commands. Use a maintenance window if the mailbox is busy.
The route
Jump straight to the step you need, or tick off Done means at the end.
Warning
Expunge is destructive. It removes messages rather than moving them to Trash, and this command has no undo operation. Take a backup or snapshot that you have actually tested before acting on mail you may need to recover.
1. Check the installed command
Confirm the binary and package version first. These are read-only checks:
$ command -v doveadm
/usr/bin/doveadm
$ dpkg-query -W -f='${Package} ${Version}\n' dovecot-core
dovecot-core 1:2.3.21+dfsg1-2ubuntu6.5
$ doveadm help expunge
Your package version will differ. The installed manpage is labelled Dovecot v2.3 and documents -d for deleting an empty mailbox after expunging. On this host, the installed binary's help uses -m in the expunge synopsis instead. Treat the command's own help and a controlled test as authoritative for the exact binary you will run; do not copy an option from one version into another without checking it.
Checkpoint
You know which doveadm is in your path, which package supplied it, and whether the local binary and manpage agree about optional flags.
2. Build the narrowest possible query
A search query is made from keys such as mailbox, savedbefore, before, subject and all. Multiple expressions are combined with AND by default. The mailbox name is not a shell option; it is part of the query and must be followed by its value.
For example, this targets messages saved more than two weeks ago in one user's Spam mailbox:
mailbox Spam savedbefore 2w
Use the exact mailbox spelling and namespace used by your deployment. A folder displayed as Junk is not automatically the same as Spam. Dates in a search can refer to a message's internal date or its Date header, depending on the key: before and since use the internal date, while sentbefore and sentsince use the Date header.
Keep the first query conservative. Do not begin with ALL, a wildcard user mask, or a query that omits the mailbox unless you have inspected the resulting match set and deliberately want that scope.
3. Preview matches without changing mail
Run doveadm search with the same user and query. This is the key safety checkpoint: it lists matching mailbox GUID and message UID pairs without expunging them.
$ doveadm search -u '[email protected]' mailbox Spam savedbefore 2w
8aad0f0a30169f4bea620000ca356bad 18751
8aad0f0a30169f4bea620000ca356bad 18756
The output is host-specific. An empty result means there is nothing matching that exact query for that user. Do not treat an empty preview as permission to broaden the query casually. If you need more context, fetch selected fields for the same query:
$ doveadm fetch -u '[email protected]' 'mailbox.guid uid flags internaldate' mailbox Spam savedbefore 2w
Compare the preview with the intended account, mailbox, age boundary and approximate message count. If the result is surprising, stop and inspect the query before proceeding.
4. Expunge one user's matches
Only after the preview is correct, run the matching expunge as an administrator or as the authorised Dovecot system user:
# doveadm expunge -u '[email protected]' mailbox Spam savedbefore 2w
There may be no success output. Check the exit status immediately:
# printf 'exit status: %s\n' "$?"
exit status: 0
# doveadm search -u '[email protected]' mailbox Spam savedbefore 2w
A status of zero means the command completed; it does not make the messages recoverable. The final search should produce no rows for the same query, unless new matching mail arrived during the operation. Re-run the preview if you need to distinguish that case.
5. Understand the scope switches
Without -u, -A or -F, the manpage says the command uses the environment of the currently logged-in system user. That is easy to misread as "all users". It is not. Use -u for one user or a user mask, for example -u '*@example.org', and preview that same mask with doveadm search first.
-A applies the query to all users returned by the user database. This depends on userdb iteration settings. The manpage specifically warns about system users with the passwd userdb and calls out SQL iterate_query and LDAP iterate_attrs/iterate_filter. A broken iterator can make an apparently successful batch incomplete.
-F USER_FILE reads one username per line and is often easier to review than a broad wildcard. Create the file outside the mail store, inspect it, and remove it after the run if it contains sensitive account names:
$ sed -n '1,20p' /tmp/dovecot-users.txt
[email protected]
[email protected]
$ sudo doveadm expunge -F /tmp/dovecot-users.txt mailbox Spam savedbefore 2w
Do not combine broad scope with a first attempt. Preview each user where practical, or run a small canary file and compare its results before expanding the list.
6. Avoid accidental mailbox deletion
The supplied v2.3 manpage documents -d as deleting a mailbox when it is empty after expunging. That is a separate, more disruptive state change. Leave it out unless deleting the mailbox itself is part of the approved operation. Because this installed binary advertises a different short option in its help, verify the local syntax before using any empty-mailbox option.
If you need to delete an empty mailbox, record its name, confirm that it is not a shared or system mailbox, and check your backup or mailbox recreation procedure first. Expunging messages and deleting the mailbox are different actions, and a successful expunge does not provide a rollback for either.
Done means
- You confirmed the installed
doveadmbinary and Dovecot package version. - Your query names the intended mailbox and a clear age, flag or message condition.
doveadm searchordoveadm fetchshowed the matches before any deletion.- You chose one user, a reviewed file, or an intentionally configured all-user iteration.
- The expunge returned status zero and a repeat search confirms the expected result.
- You did not use an empty-mailbox deletion option without a separate approval and recovery plan.