Bichon Docs

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:

#RuleValue examinedMessage is skipped when
1SendersThe first address in the From header, display names strippedThe address fails the include/exclude patterns
2SubjectsThe decoded Subject headerThe subject fails the include/exclude patterns
3Size limitThe raw message sizeThe message is strictly larger than the limit
4Spam headersThe raw value of each configured headerAny 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 listExclude listResult
emptyemptyNo restriction — everything passes this block
has patternsemptyAt least one include pattern must match
emptyhas patternsA match on any exclude pattern is rejected
has patternshas patternsInclude 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 typeStored asExample valueMatches
Contains.*VALUE.*billingbilling@shop.example, support@billing.example
Starts with^VALUE.*alertsalerts@example.com
Ends with.*VALUE$@partner.example.combob@partner.example.com
Exact^VALUE$admin@example.comExactly that address
RegexUsed 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.
  • Contains is not anchored. The value @partner.com also matches user@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$.

SenderResult
weekly@news.example.comSkipped
editor@news.example.com.malicious.exampleKept — the address does not end with the domain
alice@example.comKept

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

SubjectResult
Your weekly NewsletterSkipped
Re: UNSUBSCRIBE requestSkipped
Meeting notes for ThursdayKept

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 lineResult
X-Spam-Flag: YESSkipped
X-Spam: YesSkipped
X-Spam-Status: Yes, score=6.2 required=5.0Not skipped — the value is not exactly yes
X-MS-Exchange-Organization-SCL: 5Not 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 20 MB
  • Spam headers X-Spam-Flag
MessageResultWhy
From alice@partner.example.com, “Q3 planning”, 5 MBKeptPasses every block
From alice@partner.example.com, “Invoice #1042”, 5 MBSkippedSubject matches the exclude pattern
From alice@partner.example.com, “Q3 planning”, exactly 20 MBKeptEqual to the limit; only larger is skipped
From alice@partner.example.com, “Q3 planning”, 24 MBSkippedOver the size limit
From alice@partner.example.com, “Q3 planning”, 5 MB, X-Spam-Flag: YESSkippedSpam header matches
From bob@other.example.com, “Q3 planning”, 5 MBSkippedSender 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 / planned plus a separate N skipped by rules line; 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 limitDownload Strategy → Max email size
DefaultNo limit100 MB
Applies toEvery ingestion pathIMAP download and gap-fill only
When it runsAfter the message body is available and parsedBefore the body is fetched, using the server's reported size
EffectMessage discarded at ingestMessage never downloaded
Configured onAccount → Rules → Archive FilteringAccount → 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_than is in bytes. 26214400 is 25 MiB.
  • enabled: false archives everything, even if the rest of the object is populated.
  • An update replaces the whole archive_rules object; omitted sub-fields are reset to their defaults, not merged.
  • To remove the filter entirely, send "clear_archive_rules": true instead. Combining clear_archive_rules with archive_rules in one request is rejected.
  • Empty patterns, whitespace-only patterns and invalid regexes are rejected with 400 InvalidParameter.

Troubleshooting

A message you expected is missing:

  1. Check the sender is the bare address Bichon parsed (display names are stripped) and that only the first From address is tested.
  2. Re-check case sensitivity and anchoring — Contains is a substring match and is case-sensitive.
  3. Check both size limits, remembering that Base64 inflates attachments.
  4. For spam headers, verify the header value is exactly yes/true; X-Spam-Status and SCL do not qualify.
  5. 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.

On this page