---
title: "Dovecot — Expunge Is Permanent; Recovery Requires External Backup"
aliases: [dovecot-expunge, imap-expunge-permanent, dovecot-delete-recovery, mailcow-backup]
tags: [dovecot, email, imap, backup, mailcow, selfhosted, data-loss]
sources:
  - "daily/2026-05-08.md"
created: 2026-05-08
updated: 2026-05-08
---

# Dovecot — Expunge Is Permanent; Recovery Requires External Backup

`doveadm expunge` and client-triggered IMAP EXPUNGE commands permanently remove messages from disk. Dovecot has no server-side recycle bin or soft-delete. The only recovery path is an external backup (Scaleway snapshots, Backrest, or similar). A Mailcow installation without a backup strategy has zero recovery options after accidental expunge.

## Key Points

- **IMAP DELETE** marks a message with the `\Deleted` flag — message still exists on disk.
- **IMAP EXPUNGE** (or `doveadm expunge`) removes the flagged messages from disk permanently.
- Dovecot does not have a server-side trash/recycle bin that persists past EXPUNGE.
- There is no `doveadm undelete` — once expunged, the message is gone from the Maildir.
- Recovery is only possible from:
  1. External filesystem snapshots (Scaleway, Proxmox, ZFS)
  2. Backup tools that copy Maildir (Backrest + restic, rsync off-site)
  3. The sender re-sending the email

## What Triggers Expunge

| Trigger | Who runs it |
|---------|------------|
| `doveadm expunge -u user mailbox Trash 1:*` | Admin / automation script |
| IMAP client "Empty Trash" action | Mail app |
| IMAP agent (`imaplib`, `imapclient`) calling `expunge()` | Custom Python/JS script |
| Apple Mail "Erase Deleted Messages" | macOS Mail |

Any IMAP client that calls `EXPUNGE` after `STORE \Deleted` produces the same result.

## Setting Up Backrest for Mailcow

Backrest (web UI for restic) is the recommended backup solution for self-hosted Mailcow:

```yaml
# docker-compose addition for Mailcow
backrest:
  image: garethgeorge/backrest:latest
  volumes:
    - /var/lib/docker/volumes/mailcowdockerized_vmail-vol-1:/data/vmail:ro
    - backrest-config:/config
    - backrest-cache:/cache
  ports:
    - "9898:9898"
```

Configure a repository pointing to Scaleway Object Storage (or any S3-compatible) and schedule hourly snapshots of the vmail volume.

## Recovery from Scaleway Snapshot

If a Proxmox VM-level or Scaleway block volume snapshot exists:

1. Restore the snapshot to a temporary volume
2. Mount the volume on a rescue instance
3. Locate the Maildir at `/var/lib/docker/volumes/mailcowdockerized_vmail-vol-1/_data/`
4. Copy the missing EML files to the live server's Maildir
5. Run `doveadm index` to make Dovecot aware of restored messages (see [[wiki/concepts/mailcow-maildir-import]])

## Checking if Messages Were Expunged

```bash
# Check Dovecot logs for expunge events
docker exec mailcowdockerized-dovecot-mailcow-1 \
    doveadm log errors | grep -i expunge

# Or search recent mail log entries
docker logs mailcowdockerized-dovecot-mailcow-1 --since 24h | grep expunge
```

## Related Concepts

- [[wiki/concepts/apple-mail-trust-junk-headers]] — how Apple Mail + rspamd headers + an IMAP agent chains into accidental expunge
- [[wiki/concepts/rspamd-mailcow-whitelist]] — prevent the spam mis-scoring that starts the chain
- [[wiki/concepts/mailcow-maildir-import]] — how to restore Maildir files and re-index after recovery

## Sources

- [[daily/2026-05-08.md]] — bookkeeper-agent (IMAP Python script) expunged Trash folder after Apple Mail moved rspamd-tagged emails there; no Scaleway snapshot existed at the time; emails from axilaccountants.co.uk permanently lost; agent was removed post-incident
