success no longer includes dedup-skipped mail, so without a
duplicates field ImportPerformed consumers could not tell an
all-duplicates batch from one that silently lost everything. Also
documents that ExtractOutcome::Imported covers mail dropped by
archive rules, which has always counted as a success.
rustfmt +nightly (imports_granularity, group_imports per
.rustfmt.toml) over the five files the fix touched, plus trailing
whitespace cleanup. No behavior change.
A BLAKE3 content-hash hit in extract_envelope_core returned the same
Ok(()) as a real import, so every import surface counted silently
skipped messages as successes and the documented duplicates field
stayed 0 forever.
Make the outcome explicit: extract_envelope_core now returns
ExtractOutcome::{Imported, Duplicate} and every import loop (batch
/import, upload EML, MBOX, PST) counts Duplicate into duplicates
instead of success. total = success + duplicates + failed holds on
every path; duplicates produce no failed_details entries and an
all-duplicates run reports Completed. The batch endpoint status check
treats duplicates as processed work so a duplicates-plus-failures run
keeps reporting Completed as before. The SMTP receiver and IMAP sync
ignore the outcome.
- Make MBOX/PST upload size limits configurable via SETTINGS
(bichon_web_mbox_upload_limit_mb defaults to 1 GB,
bichon_web_pst_upload_limit_mb defaults to 2 GB)
- Switch memdb durability from Full to Batch(100) with 10s flush worker
- Offload BlobManager fjall writes to spawn_blocking
- Wrap Tantivy commit operations in block_in_place
- Flush memdb WAL on graceful shutdown
The download-message REST endpoint returned 404 [30000] "Original email
content not found" whenever an indexed message's raw content blob was
missing from the blob store, leaving the message permanently
unrecoverable even though it still existed on the IMAP server.
Make the endpoint self-healing: when the content blob is absent, fetch
that one message on demand from the IMAP server via
UID FETCH <uid> (BODY.PEEK[]), repopulate the detached blob, and return
the content. The 404 is now only produced when the on-demand fetch
itself fails (mailbox gone, UID gone, connection failure, or the fetched
bytes no longer match the archived content_hash).
- Add ImapExecutor::fetch_single_message_body: examines the mailbox
read-only and fetches one message by UID, reusing the existing
BODY.PEEK[] fetch command.
- Add reattach_eml_content_self_healing / recover_message_blob in the
envelope extractor: fast-path delegates to reattach_eml_content when
the blob exists; otherwise recovers it via IMAP and re-stores the
stripped EML + attachments through the existing blob queue.
- Make store::blob::get_reader async and route it through the
self-healing path; update the single caller (download_message).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add 10-minute cache after fetching mailbox list from mail accounts
- Display progress during initial mailbox retrieval
- Prevent timeouts when handling large mailbox lists