An archive filter is a per-account set of rules that decides whether an incoming message is stored at all. It runs at ingestion, after Bichon has parsed the message and before the envelope, index document or blobs are written. A message that fails the filter leaves no trace: it is never indexed or stored, and does not appear through search, views or the built-in IMAP server.
With no filter configured, every message the account successfully ingests is archived.
Filtering is not deletion. Bichon never removes the remote copy. A skipped IMAP message stays on the source server; only Bichon's copy is not created.
Where to configure it
Accounts → open an account → Rules → Archive Filtering. When creating an account, it is under Advanced Settings.
The master switch must be on for any rule to take effect. Switching it off disables the rules, and everything ingested from then on is archived. Editing the rules requires the account:manage permission.
Archive filtering is available in every edition. It is distinct from attachment extraction rules, which are Pro-only and control which attachments get their text indexed — not whether a message is stored.
How a message is judged
There are four rule blocks. They are evaluated in order, and all of them must pass:
| # | Rule | Value examined | Message is skipped when |
|---|---|---|---|
| 1 | Senders | The first address in the From header, display names stripped | The address fails the include/exclude patterns |
| 2 | Subjects | The decoded Subject header | The subject fails the include/exclude patterns |
| 3 | Size limit | The raw message size | The message is strictly larger than the limit |
| 4 | Spam headers | The raw value of each configured header | Any configured header is exactly yes or true (case-insensitive, surrounding whitespace ignored) |
The blocks are AND-ed: passing the sender filter does not rescue a message that is over the size limit. Inside a single block, patterns are OR-ed: one matching pattern is enough.
Rules are skipped when the value they examine is missing. A malformed message with no parseable From address is not dropped by sender rules (it can still be dropped by subject, size or spam), and a message with no Subject header is not dropped by subject rules.
Include vs. exclude
Senders and subjects each have two lists:
| Include list | Exclude list | Result |
|---|---|---|
| empty | empty | No restriction — everything passes this block |
| has patterns | empty | At least one include pattern must match |
| empty | has patterns | A match on any exclude pattern is rejected |
| has patterns | has patterns | Include must match and exclude must not match |
Exclude always wins. A sender that matches both lists is skipped.
Both lists use the same pattern syntax, and every pattern must be non-empty and a valid regex — otherwise the account cannot be saved (the API returns 400 InvalidParameter).
Pattern types
The WebUI offers five match types. The simple types are escaped and converted to a regular expression for you:
| Match type | Stored as | Example value | Matches |
|---|---|---|---|
| Contains | .*VALUE.* | billing | billing@shop.example, support@billing.example |
| Starts with | ^VALUE.* | alerts | alerts@example.com |
| Ends with | .*VALUE$ | @partner.example.com | bob@partner.example.com |
| Exact | ^VALUE$ | admin@example.com | Exactly that address |
| Regex | Used verbatim | (?i)^(no-?reply|bounce)@ | No-Reply@…, bounce@… |
Two details matter in practice:
- Matching is case-sensitive by default. Start a Regex pattern with
(?i)to ignore case. The Contains/Starts with/Ends with/Exact types do not have a case-insensitive option — address headers can carry any capitalization (Bob@Partner.Example.com), so use a Regex pattern with(?i)when that matters. Containsis not anchored. The value@partner.comalso matchesuser@partner.com.attacker.example. For addresses, prefer Ends with or Exact.
Patterns use Rust's regex syntax: \d, \w, \b, (?i), alternation and non-capturing groups are supported; look-around and backreferences are not. Patterns are used verbatim, so a stray leading or trailing space becomes part of the pattern.
Subject matching handles encoded headers. Subjects encoded per RFC 2047 (including CJK subjects) are decoded before matching, so you can write patterns such as
发票|促销|退订directly.
Examples
Drop a newsletter sender
Senders → Exclude → Ends with @news.example.com
Bichon stores .*@news\.example\.com$.
| Sender | Result |
|---|---|
weekly@news.example.com | Skipped |
editor@news.example.com.malicious.example | Kept — the address does not end with the domain |
alice@example.com | Kept |
Archive only one business partner
Senders → Include → Ends with @partner.example.com, with no exclude patterns. Every message from any other sender is skipped.
This is an allow-list: it drops internal mail, invoices from other vendors, and everything else. Use it only when the account exists solely for that correspondence. If you also add exclusions, they take precedence — no-reply@partner.example.com would still be skipped by an exclude rule.
Drop automated senders
Senders → Exclude → Regex: (?i)^(no-?reply|do-?not-?reply|bounce|mailer-daemon)@
The ^ anchors the match to the start of the address, so a human named bounce-team@partner.example.com is kept while bounce@partner.example.com is skipped. -? accepts both noreply and no-reply.
Drop marketing mail by subject
Subjects → Exclude → Regex: (?i)\b(?:unsubscribe|newsletters?|webinar|promotion)\b
| Subject | Result |
|---|---|
Your weekly Newsletter | Skipped |
Re: UNSUBSCRIBE request | Skipped |
Meeting notes for Thursday | Kept |
A subject in Chinese works the same way: Subjects → Exclude → Regex 发票|促销|退订.
Skip very large messages
Size limit: 25 MB. Messages of 25 MB or less pass this rule; anything strictly larger is skipped. Leave the field empty for no limit — the WebUI will not accept 0, and through the API a 0 would skip every non-empty message.
The limit applies to the whole raw message, not a single attachment. Attachments are Base64-encoded inside the EML, which adds about a third, so a 20 MB attachment commonly produces a message over 25 MB.
Skip mail flagged as spam
Spam headers: add X-Spam-Flag (written by SpamAssassin) and X-Spam (written by rspamd).
| Header line | Result |
|---|---|
X-Spam-Flag: YES | Skipped |
X-Spam: Yes | Skipped |
X-Spam-Status: Yes, score=6.2 required=5.0 | Not skipped — the value is not exactly yes |
X-MS-Exchange-Organization-SCL: 5 | Not skipped — the value is a number |
Bichon compares the entire header value against yes/true. Headers that carry extra text or a score (typically X-Spam-Status and Exchange's SCL) never match, so adding them has no effect. Use a flag-style header such as X-Spam-Flag; if your gateway only sets X-Spam-Status, filter on the gateway side or use sender/subject rules.
A combined rule set
- Senders → Include → Ends with
@partner.example.com - Subjects → Exclude → Regex
(?i)\b(?:invoice|receipt)\b - Size limit
20MB - Spam headers
X-Spam-Flag
| Message | Result | Why |
|---|---|---|
From alice@partner.example.com, “Q3 planning”, 5 MB | Kept | Passes every block |
From alice@partner.example.com, “Invoice #1042”, 5 MB | Skipped | Subject matches the exclude pattern |
From alice@partner.example.com, “Q3 planning”, exactly 20 MB | Kept | Equal to the limit; only larger is skipped |
From alice@partner.example.com, “Q3 planning”, 24 MB | Skipped | Over the size limit |
From alice@partner.example.com, “Q3 planning”, 5 MB, X-Spam-Flag: YES | Skipped | Spam header matches |
From bob@other.example.com, “Q3 planning”, 5 MB | Skipped | Sender does not match the include list |
Where filtering applies
The same check runs on every ingestion path:
- IMAP sync and gap-fill
- EML, MBOX and PST imports
- SMTP delivery
- Journal deliveries (Pro / Enterprise only)
Two consequences are worth knowing:
- Imports report success for skipped messages. The file was processed; there is simply no envelope for messages the filter rejected.
- A journal archive should normally have no filter. Journaling exists to capture every delivered message; a filter on the journal account silently breaks that completeness. Leave archive filtering off there.
The filter is evaluated independently of folder/date scoping (Download Strategy): scoping decides what Bichon fetches, filtering decides what it keeps.
Gap-fill and filtered mail
Gap-fill compares every remote UID against what exists in the local archive, and fills in anything it considers missing. It runs when you start a download from the account and enable Check new emails and automatically backfill older missing emails. A message skipped by an archive filter has no local record, so gap-fill sees it as missing every time.
Gap-fill therefore re-applies the filter before downloading a candidate:
- It evaluates the same rules before fetching the body. A size limit is checked from the server's reported size; sender, subject and spam-header rules are checked from a header-only fetch. A rejected message has its body skipped entirely.
- Skips are reported separately as skipped by archive rules in the account's running state and gap-fill history, not counted as downloaded.
- This is what makes relaxing a filter recoverable: lower the restriction, run gap-fill, and messages that were skipped before are re-evaluated and now pass.
- The residual cost is header enumeration: every run still lists remote UIDs and re-reads headers for the candidates. If that matters, narrow the remote scope or filter at the mail gateway instead.
- If a header fetch or parse fails, gap-fill falls back to downloading that message and the normal post-parse path decides. Filtering stays correct; only that message loses the bandwidth saving.
Imports and SMTP deliveries have no gap-fill equivalent. To recover messages they skipped, re-import the file or re-deliver the mail after changing the rules.
Seeing what was skipped
The account list's State column opens the running-state dialog, which records archive-rule skips per run:
- Downloads — each folder shows
processed / plannedplus a separateN skipped by rulesline; finished sessions keep the count in history. - Gap-fill — each folder shows backfilled, skipped and failed counts, and finished runs keep the totals in history. Progress includes skipped candidates because they are finished work, even though no body was downloaded.
Two different size limits
Do not confuse the archive filter's size limit with the Max email size setting under Download Strategy:
| Archive filtering → Size limit | Download Strategy → Max email size | |
|---|---|---|
| Default | No limit | 100 MB |
| Applies to | Every ingestion path | IMAP download and gap-fill only |
| When it runs | After the message body is available and parsed | Before the body is fetched, using the server's reported size |
| Effect | Message discarded at ingest | Message never downloaded |
| Configured on | Account → Rules → Archive Filtering | Account → Download Strategy |
For an IMAP account, a message larger than Max email size never reaches the archive filter at all. If you want to keep 150 MB messages but skip only the truly huge ones, raise Max email size first.
Scope and side effects
- Not retroactive, in either direction. Rules are applied when a message arrives. Enabling a filter does not remove mail that was already archived; tightening it later still leaves existing mail alone. Deletion is governed by retention and manual delete, not by filters.
- Filtered mail is not hidden, because it was never there. There is nothing to hide from search, views or the IMAP server.
- Normal sync saves storage, not bandwidth; gap-fill saves both. A normal IMAP sync downloads the body and then applies the rules, so the filter saves storage and indexing work but not network traffic. Gap-fill pre-checks headers and skips the body of rejected candidates. To avoid fetching bodies during normal sync too, lower Max email size or narrow the folder/date scope.
- Gap-fill re-evaluates skipped IMAP mail. A filtered message looks missing to gap-fill, so each run re-checks it against the current rules — useful after relaxing a filter, without re-downloading the bodies it will skip.
API
The field is archive_rules on the account object. It is accepted by POST /api/v1/account (create) and POST /api/v1/account/{account_id} (update).
{
"archive_rules": {
"enabled": true,
"senders": {
"include": [".*@partner\\.example\\.com$"],
"exclude": ["(?i)^no-?reply@"]
},
"subjects": {
"include": [],
"exclude": ["(?i)\\b(?:unsubscribe|newsletters?)\\b"]
},
"skip_larger_than": 26214400,
"spam_headers": ["X-Spam-Flag", "X-Spam"]
}
}- The API receives regex strings — the match-type/value pairs in the WebUI are only a front-end convenience. The pattern above means “ends with
@partner.example.com”. skip_larger_thanis in bytes.26214400is 25 MiB.enabled: falsearchives everything, even if the rest of the object is populated.- An update replaces the whole
archive_rulesobject; omitted sub-fields are reset to their defaults, not merged. - To remove the filter entirely, send
"clear_archive_rules": trueinstead. Combiningclear_archive_ruleswitharchive_rulesin one request is rejected. - Empty patterns, whitespace-only patterns and invalid regexes are rejected with
400 InvalidParameter.
Troubleshooting
A message you expected is missing:
- Check the sender is the bare address Bichon parsed (display names are stripped) and that only the first
Fromaddress is tested. - Re-check case sensitivity and anchoring —
Containsis a substring match and is case-sensitive. - Check both size limits, remembering that Base64 inflates attachments.
- For spam headers, verify the header value is exactly
yes/true;X-Spam-StatusandSCLdo not qualify. - Remember that a failed block is enough to skip the message — all four blocks are AND-ed.
To see the decision, run the server with BICHON_LOG_LEVEL=debug. Messages rejected by the filter are logged with their sender, subject and the account id.