Source mailbox cleanup moves mail that Bichon has already archived out of the source IMAP folders and into a dedicated folder, bichon_auto_created_trash, on the same server. It exists for archives that become the authoritative copy of a mailbox: once mail is safely in Bichon, the source mailbox no longer has to hold it.
The operation is a move, not a delete. The message leaves the source folder but still exists on the server (in the trash folder) and in Bichon's archive. Bichon never empties that folder: reclaiming the space is a separate, deliberate action you take with your own mail client.
Cleanup is not retention, and not an undo of deletion. Retention purges Bichon's local copy; cleanup moves the server's copy. Message restore pushes archived mail back to its source server — different from moving mail out of it.
What it does
- Chooses, per account, which messages are eligible: archived long enough ago, old enough by message date, not starred, within the configured source folders.
- Verifies each candidate against the server before touching it, using identity metadata the archive recorded.
- Moves it into
bichon_auto_created_trashwith IMAPMOVE. Servers that do not advertiseMOVEcannot run cleanup at all: copying would leave the source copy in place and free no space. - Records what moved and what failed, per folder, in a report you can revisit.
What it never does
- Never permanently deletes a message. Even the fallback path only removes the source copy after the copy has been confirmed visible in the trash folder.
- Never empties
bichon_auto_created_trash. There is no "purge" button and nothing is scheduled; use your mail client when you want the space back. - Never deletes a mailbox (
DELETEis never sent), never renames one, and never issuesEXPUNGEat all — not a plainEXPUNGEthat would flush unrelated messages other clients marked as deleted, and not a UID-scoped one either. The only way a message leaves a source folder isMOVE. - Never moves a message that is not in the archive, and never moves anything out of special-use folders (
\All,\Archive,\Drafts,\Flagged,\Junk,\Sent,\Trash). - Never touches messages under legal hold (Enterprise), a disabled account, or an account that is being deleted.
Requirements
| Requirement | Detail |
|---|---|
| Edition | Community, Pro and Enterprise |
| Account type | IMAP only — NoSync and View accounts have no source server |
| Server capability | Must advertise MOVE (RFC 6851). A server without it cannot run cleanup — copying would not free the source mailbox — and every run is refused as blocked before anything is touched |
| Permission | account:manage to configure, preview or run; account:read_details to read the state/reports |
| Bichon version | The cleanup API is available whenever the account update API accepts remote_cleanup |
Where it lives
All three entry points are in the account row menu (Accounts → ⋯):
| Dialog | Purpose |
|---|---|
| Source cleanup | Configure the feature: schedule, folders, age thresholds, batch size, starred messages |
| Run cleanup now | Preview and execute one batch immediately, using the saved configuration |
| Cleanup reports | The last 30 runs with per-folder moved/failed counts and the failed messages |
The configuration is deliberately outside the account create/edit form. Saving the cleanup dialog sends only remote_cleanup through the account update API, so it cannot overwrite other account settings.
The destination folder
The destination is fixed: bichon_auto_created_trash on the account's own server. It is created on first use if missing, and it cannot be configured as a source folder — the API rejects that with 400 InvalidParameter.
Do not delete or rename the folder while cleanup is configured. The next run re-creates it, but messages left in it are not tracked by Bichon beyond the archive itself.
Which messages are eligible
Eligibility is judged from Bichon's local index first; the server is only consulted to prove identity before a move.
| Condition | Setting | Default | Notes |
|---|---|---|---|
| Source folder in scope | Source folders (optional) | Account's download folders | Comma-separated names; empty = the account's download folders, or every folder when the account has no folder scope |
| Folder is not special-use | — | — | \All, \Archive, \Drafts, \Flagged, \Junk, \Sent, \Trash are always skipped, as is the destination folder |
| Archived long enough | Archive age before moving (days) | 30 | Measured from ingest_at — the moment Bichon saved the message. 0 means "eligible on the next run" |
| Message old enough (optional) | Only move messages older than (days) | empty | Uses the newer of the RFC 5322 Date header and the server's INTERNALDATE, so a message moves only when both are older. Messages with neither date are skipped |
| Not starred | Keep starred messages | on | A \Flagged message never moves, even when old enough |
| UID known | — | — | Candidates with no UID, or with a folder whose UIDVALIDITY is unknown, are skipped |
Both age conditions must hold when the optional message-age floor is set. A message archived yesterday is not eligible even if its Date header is ten years old.
How a batch is selected
Cleanup never looks at every message in a mailbox. Each run builds a bounded window:
- Collect eligible candidates from the in-scope folders, ordered oldest-first by
ingest_at. - Remove starred messages from the window before the batch is sized (they would otherwise occupy slots forever).
- Walk the window oldest-first, verifying each candidate, until the batch is full of messages that can actually move — or the window is exhausted.
The window holds batch size × 8 candidates (minimum 200, maximum 5,000). If the window was truncated, the run reports that the scan budget was reached: there may be more eligible mail than the window could see. The next run continues from the same place, because messages that moved are marked in the index and never offered again.
Skipped candidates do not shrink a batch. If six of the first fifty candidates turn out to be gone from the source, the run keeps walking the window until fifty messages have actually moved. A preview therefore normally shows exactly the configured batch size of ready to move messages; fewer than that means the window is exhausted, not that the run was cut short.
Verification before every move
Every candidate is proven against the server immediately before it is moved. The check is metadata-based: Bichon records a message's identity at ingest, and compares it with what the server reports now.
| Check | What must match |
|---|---|
| Folder identity | The source folder's current UIDVALIDITY equals the archived one |
| Locator | The archived UID still exists in the folder |
| Identity | The remote message behind that UID has the same Message-ID; if the UID vanished or now holds another message, a HEADER Message-ID search locates the real one |
| Uniqueness | The Message-ID search must resolve to exactly one candidate whose size and internal date agree; several indistinguishable copies stop the run |
| Metadata | Message-ID, RFC822.SIZE and INTERNALDATE agree with the archive (when both sides know them) |
| Local archive | The stored copy still reassembles and hashes to the recorded content hash |
The message body is not re-downloaded. A full body hash would add a complete download per message, and — because the move is reversible and the archive keeps its own full copy — it would only catch a server that rewrites content while preserving the Message-ID, size and internal date. Metadata verification is the deliberate default.
How a move is performed
UID MOVEonly. The message is moved intobichon_auto_created_trashwith RFC 6851MOVE; the server must not answerOKuntil the message is in the destination. There is no copy-and-expunge fallback — Bichon never issuesEXPUNGE, plain or UID-scoped — so a server that does not advertiseMOVEcannot run cleanup at all.- Post-condition. Bichon searches the source folder for the UID after the move and counts the message as moved only if it is gone; otherwise the run stops with a failure.
- Index update. Messages moved by a run are marked in the index when the run ends, so they never reappear as candidates.
Runs are intentionally slow: about 300 ms between messages, a 5-second pause every ten messages that also re-checks UIDVALIDITY, and one cleanup at a time per account.
Manual runs
Run cleanup now uses the saved configuration and works in two steps:
- Preview — read-only. Verifies the candidate window and shows how many messages are ready and how much they weigh. Closing the dialog does not cancel it; the job continues on the server and reopening the dialog reattaches to it (finished jobs stay readable for 30 minutes).
- Execute — requires ticking the acknowledgement. The preview issues a single-use token valid for 15 minutes, bound to the exact candidate window and configuration. Any change in between — new mail entering the window, a different folder scope, an edited threshold — invalidates the token and asks you to preview again.
The batch behaves the same as a scheduled batch: it fills to the configured size by continuing past candidates that cannot move.
Scheduled runs
Enable Enable scheduled moves and pick a frequency (daily, weekly, monthly at a chosen hour). The stored expression is a six-field cron (second minute hour day-of-month month day-of-week) evaluated in UTC; the dialogs expose the common presets, and the API accepts any valid expression.
A scheduled run is not one batch — it drains the backlog:
- It repeats full batches — each one a complete, safety-checked run — until a batch moves nothing (everything left is unmovable) or the run has been working for 10 minutes.
- The time budget keeps one schedule from monopolizing the account: whatever is left continues at the next scheduled occurrence.
- Between batches the account is re-read, so disabling the account, removing the configuration, placing a legal hold, or starting a deletion stops the drain immediately.
- Only one cleanup runs per account at a time; a second request is refused while one is active.
| Situation | What happens |
|---|---|
| A sync or manual download holds the account | The batch is deferred and retried on the next scheduler tick (about a minute) |
| A backup capture window is open | Same: deferred, retried shortly |
| Account disabled, deleted, or on legal hold | Skipped; a manual run is blocked and recorded as such |
Server does not advertise MOVE | The run is blocked before anything is touched; this consumes the schedule slot |
| Invalid cron expression | The account is skipped with a warning in the server log |
Reports
Cleanup reports keeps the last 30 finished runs per account, including blocked and failed ones. For each run it shows:
- the outcome (
Success,Failed,Blocked,Cancelled), moved and failed counts, bytes moved and folders touched; - per folder, moved / failed / bytes;
- the failed messages by subject, reason, folder and UID, capped at 200 entries per run.
Routine policy skips — too recently archived, too recent by message date, starred, or no longer in the source mailbox — are counted in the API (skipped, skipped_reasons) but are not listed in the UI. They are expected states that need no operator action; only failures do.
Stop conditions and safety rules
A run stops at the first condition that makes moving more messages unsafe:
| Condition | Result |
|---|---|
| Account on legal hold | Blocked before any move |
| Account disabled or being deleted | Blocked |
Server does not advertise MOVE (RFC 6851) | Blocked |
UIDVALIDITY changed for a folder (before or during the run) | Stop, Failed |
| A message's identity cannot be proven (size, date or Message-ID mismatch) | Stop at that message, Failed |
| Three consecutive local archive integrity failures | Stop, Failed |
| More than one indistinguishable remote copy for a Message-ID | Stop, Failed |
| The source UID is still present after the move | Counted as failed, run stops |
| User cancellation (manual run) | Stop at the next message boundary, Cancelled; the report keeps what moved so far |
Messages that are simply gone from the source (deleted or moved elsewhere since Bichon archived them) are skipped, not failed: there is nothing to move and nothing to repair.
Caveats
- Retention can outrun cleanup. If the account's local retention (
retention_days) is shorter than the cleanup grace period, Bichon may purge its own copy before cleanup moves the server copy. The configuration dialog warns about this; keep local retention comfortably longer than the grace period. - Very short grace periods skip the backup safety net. With a grace under seven days, a message can be moved right after it is archived, before any backup captured it. The move is still reversible, but until a backup runs the local archive is the only verified copy. The dialog warns below seven days and when the last successful backup is old.
- A scan budget can hide mail. The window is bounded; when it was truncated, a run says so. It still makes progress on the next run, but a mailbox with a very large unprocessed backlog drains across several runs.
- Messages gone from the source stay in the window. They are skipped every time they are examined, which costs a little verification work per run but never blocks a batch from filling.
Undo and reclaiming space
- Undo a move: move the message from
bichon_auto_created_trashback to its original folder, with any mail client. Bichon does not need to be involved and its archive is unaffected. - Reclaim space: empty
bichon_auto_created_trashwith your mail client. This is a permanent deletion on the server — Bichon provides it nowhere in the UI or API, precisely because it cannot be undone. - Bring mail back from the archive: message restore pushes archived messages back to the source server. It appends a new copy to the original folder; it does not move anything out of the trash folder.
API
| Method | Path | Permission |
|---|---|---|
GET | /api/v1/accounts/{id}/remote-cleanup/state | account:read_details |
POST | /api/v1/accounts/{id}/remote-cleanup/preview | account:manage |
POST | /api/v1/accounts/{id}/remote-cleanup/execute | account:manage |
GET | /api/v1/accounts/{id}/remote-cleanup/jobs | account:manage |
GET | /api/v1/accounts/{id}/remote-cleanup/jobs/{job_id} | account:manage |
POST | /api/v1/accounts/{id}/remote-cleanup/jobs/{job_id}/cancel | account:manage |
Previews and runs are background jobs: the start call returns a job immediately, and the job's status, preview or run report is polled through the jobs endpoints. Jobs live in memory and stay readable for 30 minutes after they finish; a page reload or a closed dialog reattaches through GET …/jobs, which lists the account's jobs newest-first with running ones first. The durable record is the state/reports endpoint.
The configuration travels in the account update payload as remote_cleanup:
{
"remote_cleanup": {
"enabled": true,
"schedule": "0 0 3 * * *",
"folders": ["INBOX", "Projects"],
"eligible_after_archive_days": 30,
"min_message_age_days": 90,
"max_messages_per_run": 50,
"skip_flagged": true
}
}clear_remote_cleanup: true removes the configuration; messages already in bichon_auto_created_trash stay there.
Limits
| Limit | Value |
|---|---|
| Messages moved per batch | 1–500 (default 50) |
| Candidate window per run | batch × 8, clamped to 200–5,000 |
| Scheduled drain per occurrence | 10 minutes, at most 50 batches |
| Preview token lifetime | 15 minutes, single-use |
| Finished job readability | 30 minutes |
| Run history per account | 30 runs |
| Failed-message detail per run | 200 entries |
| Pacing | ~300 ms per message, 5 s per ten messages |
Related: Accounts, Archive Filtering, Editions, Legal Hold, CLI tools.