| last_edited | 2026-09-30 |
|---|---|
| title | Deleting Email |
| description | Staging messages for deletion, reviewing manifests, and executing deletes from Gmail, IMAP, or Microsoft Graph mail. |
Remove unwanted mail from Gmail, IMAP, or Microsoft Graph mail while keeping the archived message, raw content, and downloaded attachments. Deletion has three separate steps:
- Stage a precise set of messages in a pending manifest.
- Review that manifest and check the archive.
- Execute with
delete-stagedand explicit remote-deletion consent.
Executing a batch records which messages were deleted at the source. It does not purge their archived content. You can still open and export those messages:
msgvault search "old receipt" --deletion-scope deleted
msgvault search "old receipt" --deletion-scope anydeleted selects source-deleted messages; any includes active and
source-deleted messages together. Removing an account or purging local data is
a different operation and can remove archive content.
Preview matching mail before creating a batch:
msgvault stage-delete 'from:newsletter@example.com before:2024-01-01' --dry-run
msgvault stage-delete 'from:newsletter@example.com before:2024-01-01'
msgvault show-deletion BATCH_IDQuery staging uses the same search language as msgvault search and considers
active messages only. It reports and skips matches that this staging path
cannot delete, such as chats, meetings, and mail from IMAP or file imports.
The remaining targets must belong to one source. Use --source-id to narrow a
query when several accounts match:
msgvault stage-delete 'label:Newsletters' --source-id 3 --dry-runIf you already have internal message IDs, stage them directly:
msgvault stage-delete --ids 123,456,789 --dry-run
msgvault stage-delete --ids 123,456,789--ids accepts positive internal message IDs, not Gmail provider IDs. It cannot
be combined with a query or --source-id. Missing, source-deleted, and
unsupported targets are skipped and reported. The explicit-ID path works
without a ready analytical or full-text cache; query staging waits for a
complete search index and requires daemon API schema 2.18.0 or newer.
These CLI staging paths resolve Gmail and Microsoft Graph mail targets. Graph
mail accounts are the ones added with add-o365 --graph. Creating a manifest never executes it.
In Everything, select individual rows or all rows matching the current
canonical filter, then press d or D to open the Deletions workspace. The UI
runs a server-side preflight before it enables staging and requires a separate
confirmation to create the manifest. The Deletions workspace also lists,
inspects, and cancels staged manifests. It never executes remote deletion;
that final step remains the explicit CLI command described below.
Preflight validates the selection itself (that it still matches, that the
cache/search revision hasn't moved) but does not check which accounts the
matched messages belong to. Each deletion manifest executes against exactly
one mailbox, so if you stage "all matching" across an unfiltered or
multi-account view, confirming the stage fails with a multi_account_selection
error even though preflight succeeded. Filter to one source/account
(the A key in the TUI, or the equivalent source filter in the Web UI) before
staging an all-matching selection that could span more than one account.
The fastest way to clean up your inbox is through the TUI's aggregate views. Navigate to the Senders, Domains, or Labels view, find the group you want to remove (e.g., a prolific spam sender or an unwanted mailing list), and press D to stage every message in that group for deletion at once.
For finer control, drill into any group and use Space to select individual rows, then press d to stage only the selected messages.
You can also stage deletions through the MCP server by asking an AI assistant like Claude to find and stage messages for you. For example:
- "Stage all messages from noreply@linkedin.com for deletion"
- "Stage all promotional emails older than 2024-01-01"
The MCP stage_deletion tool creates a manifest through the selected daemon, the same format as TUI-staged deletions. Nothing is deleted until you run msgvault delete-staged from the CLI. See MCP Server for details.
Web dashboards and automation scripts can stage deletion manifests through the
web API without constructing a manifest
themselves. POST /api/v1/deletions accepts structured filters and/or
internal message IDs, resolves the provider IDs on the server, and supports
"dry_run": true to preview the count and a sample before writing anything.
IDs that do not resolve to live deletable Gmail or Microsoft Graph mail messages
with provider message IDs are omitted, and the reported count is the number of
targets resolved by the daemon.
Actually staging depends on the request shape. An explicit message_ids list
stages directly. Filter-based staging requires a preflighted selection: run
the reviewed predicate through
POST /api/v1/explore/preflight
to get a single-use operation_token (valid for five minutes), then stage
with the same selection and token. A non-dry-run filter request without a
preflighted selection is rejected with 428 preflight_required.
The API can also list and cancel staged manifests:
curl -H "Authorization: Bearer $MSGVAULT_API_KEY" \
http://localhost:8080/api/v1/deletions
curl -X DELETE -H "Authorization: Bearer $MSGVAULT_API_KEY" \
http://localhost:8080/api/v1/deletions/20260706-153000-old-newsletters-a1b2API staging still only creates or cancels local manifests. Remote deletion
continues to require the explicit delete-staged execution step and invoking
CLI consent, configured durably or supplied for one command as described
below.
Staged deletions create manifests that record exactly which messages will be affected. Review before executing:
# List pending deletion batches
msgvault delete-staged --list
# Execute deletion for a Gmail account (moves to trash by default)
msgvault delete-staged --account you@gmail.com
# Move staged IMAP messages to the server's Trash folder
msgvault delete-staged --account you@fastmail.com
# Move staged Microsoft Graph mail messages to Deleted Items
msgvault delete-staged --account you@company.com
# Permanently delete Gmail messages instead of moving them to trash
msgvault delete-staged --account you@gmail.com --permanentGmail and IMAP both default to moving messages to Trash. Gmail retains trash
for 30 days; IMAP recovery and retention depend on the server. IMAP uses the
server's discovered Trash folder, falling back to a folder named Trash.
If that move fails, the batch records the failure rather than switching to
permanent deletion. Microsoft Graph mail moves messages to Deleted Items, where
you can restore them in Outlook. A later sync keeps them marked as deleted
while they stay in Deleted Items.
--permanent requests irreversible deletion: Gmail uses its batch API; IMAP
uses UID STORE \Deleted followed by UID EXPUNGE for each message. Permanent
IMAP deletion requires UIDPLUS and is refused when the server lacks it.
Microsoft Graph mail uses permanentDelete for each message, and you cannot
restore the message in Outlook. Dry-run mode works for all providers.
Remote deletion is opt-in. Enable it in the invoking CLI's configuration, or provide consent for one command. Staging and review do not require this setting.
Durable consent belongs in the invoking CLI's config.toml:
[deletion]
remote_enabled = trueAs a one-command alternative:
MSGVAULT_ENABLE_REMOTE_DELETE=1 msgvault delete-staged --account you@gmail.comWhen remote mode is configured, the invoking CLI forwards its effective
consent for that operation. The remote daemon's own [deletion] section is
not server policy for a command invoked elsewhere. Staging, listing,
inspecting, and dry-running deletion batches remain ungated, so you can always
review exactly what is staged before enabling execution.
When you first run delete-staged --permanent, your OAuth token likely only has read and modify permissions. Permanent Gmail batch deletion requires full Gmail access (the mail.google.com scope). msgvault detects this automatically and prompts you to upgrade:
======================================================================
PERMISSION UPGRADE REQUIRED
======================================================================
Batch deletion requires elevated Gmail permissions.
Your current OAuth token was granted with limited permissions that
don't include batch delete. To proceed, msgvault needs to:
1. Delete your existing OAuth token
2. Re-authorize with full Gmail access (mail.google.com scope)
This elevated permission allows msgvault to permanently delete
messages in bulk. You can revoke access anytime at:
https://myaccount.google.com/permissions
Upgrade permissions now? [y/N]:
Answering y opens your browser for re-authorization. Answering N cancels cleanly with no side effects. You can revoke the elevated permission at any time from your Google Account permissions page.
If you have a legacy token from before scope tracking was added, msgvault falls back to detecting the insufficient scope from the API response and shows the same prompt.
Gmail trash deletion does not require this elevated scope.
Microsoft Graph mail accounts sync with the Mail.Read permission. The first
delete-staged for one of these accounts asks to upgrade the token to
Mail.ReadWrite, for trash and for --permanent. If you answer y, msgvault
opens your browser to grant it. Your existing token keeps working until the new
grant succeeds. The Entra app registration must list the delegated
Mail.ReadWrite permission.
On a host without a browser, use device-code sign-in:
msgvault delete-staged BATCH_ID --headlessOpen the printed sign-in URL on another device and enter the code. The
--headless flag applies to Microsoft Graph permission upgrades only.
With a local daemon, the CLI completes the Gmail or Microsoft Graph permission
upgrade before starting the deletion worker. With a remote daemon, sign-in runs
on the daemon host because that host owns the token; --headless is forwarded
to that worker for Microsoft Graph accounts.
IMAP accounts do not require a permission upgrade. IMAP credentials already have full mailbox access, so delete-staged works without re-authorization.
Deletions are resumable. If execution is interrupted (network error, Ctrl+C, scope error), run the same command again to pick up where it left off. Failed messages are tracked individually and retried automatically on the next run before continuing with remaining messages.
Cancel a specific batch or all pending batches:
# List available batches
msgvault cancel-deletion
# Cancel a specific batch
msgvault cancel-deletion <batch-id>
# Cancel all pending batches
msgvault cancel-deletion --all!!! danger
Permanent deletion cannot be undone on the remote mail server (Gmail or IMAP provider). Always verify your local archive is complete before permanent deletion. Use msgvault verify to check integrity. Executing a remote-deletion manifest preserves the archived copy.
Use gc only when you also want to discard local copies of messages already
marked deleted at their source. It purges those messages across the entire
SQLite archive, compacts the database, and removes attachment files that no
remaining message references. Active messages and messages hidden only by
deduplication remain in the archive.
gc does not contact providers or execute pending deletion batches. It has no
account filter, date filter, or dry-run flag, and it is not available for
PostgreSQL archives.
-
If you need a recoverable copy, back up the archive database and attachment storage before purging. The automatic GC backup contains only SQLite data; it cannot restore attachment bytes removed during cleanup.
-
Run the command and confirm the local purge:
msgvault gc
-
After a purge, rebuild each cache you use:
msgvault build-cache --full-rebuild msgvault embeddings build --full-rebuild
The second rebuild is needed only if you use embeddings. By default, GC writes
msgvault.db.gc-backup-<timestamp> beside the database before deleting rows.
--yes skips the confirmation prompt; --no-backup explicitly skips that
SQLite backup. A backup failure stops deletion.
If attachment cleanup fails after rows have been deleted, the command reports the partial result. Rerunning GC retries unreferenced loose files even when no source-deleted messages remain.