Bichon Docs

Bichon stores an archived message as two things: an envelope (the metadata row that search, filtering and the message list operate on) and one or more blobs (the raw bytes — the message body, plus each attachment, content-addressed and deduplicated).

If a blob goes missing while its envelope survives, the message still shows up in search results and in the list — but it cannot be opened. Message repair fixes exactly that case: it re-fetches the message from its source IMAP server and restores the missing blobs in place. The envelope, its id, its folder placement and its index entry are all kept, so a repair never produces a duplicate.

What it touches

RestoredMissing content blobs — the message body, and any missing attachment
UntouchedThe envelope row, its id, its folder, its Tantivy index entry
Not repairableA blob that is present but corrupt — see below

Not to be confused with

Two other mechanisms fix different kinds of damage, and all three are sometimes called "repair":

  • Gap-fill — diffs each remote folder against the archive and re-downloads messages that were never archived at all. It fixes missing messages. Archive-filtered candidates are re-checked from their headers and skipped without downloading their bodies.
  • Full re-sync — a UID SEARCH ALL pass over a mailbox, for when an incremental download was interrupted. Also message-level.

Neither of them can help with a message you already have but cannot open. That is what this page is about.

How Bichon decides something is broken

The check is an integrity verdict, not a presence check. Bichon re-attaches the message's stored blobs into the stripped EML (attachments are stored detached, with placeholders left in their place) and recomputes the content hash. The message counts as broken only when that recomputation does not match the hash recorded on the envelope.

Two preconditions are checked first, and both fail fast:

  • The envelope must exist — otherwise 404 Not Found.
  • The envelope's recorded attachment_count must agree with the number of attachment blobs. If it does not, the repair is reported as failed without contacting IMAP at all.

How it recovers the content

From the source IMAP server, in order:

  1. Fetch by the stored UID.
  2. If that misses, UID SEARCH HEADER Message-ID over a bounded candidate set — at most 32 candidates, so a forged or duplicated Message-ID cannot turn a repair into an unbounded scan.
  3. A fetched body is accepted only if its computed content hash matches the envelope's. A mismatch means the server is returning different content, and it is discarded rather than stored.

Recovered bytes go through the same first-write-wins blob store as normal ingestion, which is what makes repeating a repair safe: an existing key is never overwritten, and the store never creates a second copy of content it already holds.

Outcomes

Every repair returns one of four statuses:

StatusMeaning
completeThe message was not broken — nothing was written
repairedBlobs were missing and are now restored
partialSomething is still missing after the attempt
failedThe envelope was unusable, or the IMAP fetch/parse failed — nothing was written

A failed repair still returns HTTP 200. The status is a verdict about the record, not a transport error — a caller that retries on a non-2xx response would otherwise retry a case that cannot succeed.

Each report lists the missing objects before and after the attempt, so you can see precisely what changed.

Corrupt blobs are reported, not overwritten

A blob that exists but whose content no longer matches is deliberately left alone. Repairing it would mean deleting the existing object first, and Bichon does not do that as part of a repair — the blob store's no-overwrite rule is what makes every other write path idempotent. Such a message reports partial on every attempt rather than being silently rewritten.

Interrupted backups

Blob writes are refused while a backup capture window is open. If that happens during a repair, the recovery still runs but the write is skipped: the report comes back partial with skipped_due_to_backup set. Re-run the repair once the backup finishes.

Self-healing on read

Repair is not the only way a missing blob is recovered. Reading a message whose content blob is absent runs the same recovery inline, so a message that is merely unread-after-a-disk-event often opens normally without anyone asking for a repair. The difference is that self-healing produces no report and no history entry — it just returns the bytes. The explicit repair endpoint is what gives you a status, a record, and an audit trail.

Triggering a repair

In the WebUI the action appears in the row menu on search results, on attachment rows, and — on Pro — on the integrity check failures table, which is the most convenient starting point because it lists exactly the messages whose blobs went missing. All three open the same dialog, which shows the before/after report. A Repair history button in the search toolbar opens the most recent reports.

MethodPathDescription
POST/api/v1/repair-message/{account_id}/{envelope_id}Repair one message
GET/api/v1/repair-reportsRecent repair reports (limit, account_id); default 50, capped at 200

Repair requires the data:manage permission on the account. The history endpoint returns only records for accounts the caller holds that permission on.

Both endpoints are available in all editions — repairing is a core data-recovery operation, not a Pro feature. What is Pro is the integrity check that surfaces which messages need it in the first place, and the audit/SIEM categorization of repairs.

On this page