Passive folder and label sorting
Passive sorting evaluates saved folder and label rules in background work. A run is one saved batch of selected messages. Each message has a work record.
See background-work terms for durable admission, outbox, leases, and fencing. It evaluates folder and label targets in parallel. Provider mutations use the ordered mailbox operation path. Prepare Graph access or Google access for the mailbox.
Configure and start sorting
Open Sorting in a folder or label editor. Configure positive and negative conditions. Enable the target. Then save the sorting rules. Legacy users_folders regex fields, descriptions, Qdrant similarity, and the old folder-tree fallback do not participate in this rule evaluator.
The sidebar Sorting page separately controls folder sorting, label sorting, and automatic arrival processing. Automatic mode requires an allowed model. Administrators set the tenant model, reasoning, and context defaults. Personal overrides require separate permission. See sorter defaults.
A manual run with only cheap conditions needs no model. A semantic condition always needs one.
Lower folder priority numbers run first. Target ID breaks a tie. This does not change sidebar order. Only the first matching folder wins. All matching labels apply to the whole conversation while preserving existing labels.
For manual work, select messages in a folder, search, all-folder, or label view, then start sorting. A conversation row expands only to messages within that view's folder/date boundary. Explicit search or label message selection does not expand to other conversation members. Empty selection never starts mailbox-wide work.
Rule semantics
| Field | Conditions |
|---|---|
| sender, cc | Positive and negative contact lists with saved contact email addresses. Any negative contact match vetoes the target |
| subject, body | Literal text, regular expressions, and natural-language meaning conditions |
| date | Open absolute date bounds or relative calendar days |
Trim removes whitespace from the pattern's edges. It does not truncate mail. Pattern controls separately set case, accents, whole-word matching, and regex mode. Literal matching treats the pattern as text. Whole-word boundaries include Unicode letters, numbers, and underscore.
Regex uses Node Unicode syntax with a 50 ms execution timeout. Saving reports syntax errors. A timeout fails the work rather than returning “no match.”
Lists default to OR. Empty lists impose no condition. Positive and negative lists can have different lengths. Configured fields default to AND.
| Operator | Meaning |
|---|---|
| Positive list OR | At least one item must match |
| Positive list AND | Every item must match |
| Negative list OR | One matching item blocks the target |
| Negative list AND | All items must match to block the target |
| Positive pattern/meaning OR | Either configured condition type can satisfy the positive side |
| Negative pattern/meaning AND | Both configured types must pass their exclusion checks |
| Negative pattern/meaning OR | At least one configured type must pass its exclusion check |
For example, a subject that matches “invoice” still fails if its body matches the excluded term “gas.” This is a global veto: another positive field cannot override the exclusion.
The positive result and successful exclusion check always combine with AND. A computed exclusion is a global veto for that target. For example, subject || body cannot bypass a negative body result. The negative type operator differs from the negative list operator.
Advanced mode accepts sender, cc, subject, date, body, &&, ||, and parentheses. Example: sender && (subject || body). The parser accepts at most 512 characters and does not use eval. Every configured field must appear.
Unconfigured fields are invalid. Disabling Advanced restores default operators.
OR broadens positive selection. If subjects are vague, retain a required body condition. The editor warns about this choice.
Dates use Europe/Budapest calendar days and the run's fixed start-day value. On October 11, “older than 5 days” means before October 6 at 00:00. The newer direction starts at that boundary. Absolute after and before exclude their boundary days. Daylight-saving changes do not use simple 24-hour subtraction.
For example, a server-fee positive match still fails when a streaming exclusion matches. A combined electricity/gas invoice also fails if gas is excluded. These exclusions are intentional, not model overrides.
Evaluation and errors
Cheap checks run in this order: sender, cc, subject, date, body. Only unresolved subject/body meaning checks call the model. Within a field, the shorter cheap list runs first. AND wins equal-length ordering. Literal patterns run before regex.
Short-circuit evaluation avoids unnecessary inference.
Body read failures, regex errors, invalid model output, and required inference failures never count as successful exclusions. Work fails or blocks. An uncertain higher-priority folder does not silently yield to a lower match. A folder/label evaluation error prevents partial mutation for that message. Other messages' completed operations remain.
Transient model/provider errors allow at most five attempts, with waits of 2, 10, 30, and 60 seconds. Rate limits honor at least the provider's Retry-After. Configuration and permission errors do not receive automatic transport retries.
Durable work and confirmation
The transaction that stores a new non-draft arrival records only a deduplicated arrival intent. It does not run inference. Delta replay and the application's own moves do not create another arrival. EmailScheduler performs admission and recovery in the existing background service.
Admission means accepting the run and saving signed proof of its allowed action. It checks current mailbox ownership, mail.sorting.manage, mail.crud, and model access when required. Automatic admission needs saved opt-in and a fresh authorization snapshot. The saved signed proof identifies selected messages, rules, model, start time, and mailbox. Switching mailboxes cannot transfer a run.
Per-message sorting_work records hold state, attempts, result, error, retry time, and sync operation IDs. The work lock and operation/outbox creation share a transaction. Recovery reconciles an accepted mutation before considering another submission.
| Work state | Meaning |
|---|---|
prepared | Durable work awaiting execution. Arrival can be queued before this |
running | Worker evaluates the message |
retry-wait | Waiting after a transient provider failure |
awaiting-sync | Evaluation finished, provider confirmation remains outstanding |
completed | No change needed, or all requested remote operations confirmed |
no-match | No target matched |
failed / blocked | Visible failure, not success or no-match |
cancelled | Work stopped before execution |
Run states include running, paused, cancelled, and finished. Finished means all work reached a terminal state. It does not mean every item succeeded. The interface separately shows errors and pending confirmation.
Pause preserves remaining work. Resume continues the same run. Cancel closes work that did not start and retains tracking for submitted operations. Retry failed starts a new cycle of inference attempts and preserves confirmed remote effects. Uncertain or superseded effects need reconciliation first. One mailbox permits at most one running/paused run, including retries.
A heartbeat reports that the worker remains active. Attempt fencing rejects changes from an expired worker or earlier attempt. Together they prevent an expired worker from submitting another mutation. Recovery uses a 120-second stale threshold. Fan-out accounts for available container memory: 128 MiB reserve and 256 MiB per worker, with at least one worker. Folder/label evaluation remains parallel.
Conversation category fan-out preserves per-message successes. An equal state with a Graph timestamp in the same second can confirm an intent. A differing state at that timestamp cannot supersede a newer intent. Apply can issue a targeted GET for checkpointed or unchanged categories. It does not issue another PATCH for that check.
API and interface refresh
BFF /api/sorting?accountId=… proxies backend /sync/sorting.
| Request | Result |
|---|---|
| GET overview | Settings, allowed models, rules, target names, latest 50 runs, at most 50 queued/blocked arrivals |
GET with runId, optional cursor | Run summary and 100 work items per page |
| POST action | rule, settings, start, pause, resume, cancel, retry |
Legacy autonomousSort and single-message sortEmail starts also use durable runs. Empty selection is rejected. Read and manage permissions differ. Read-only mailboxes reject mutations.
The interface reads state on process.progress events and reconnection. The active mail-list sorting control reads durable run state every 3 seconds. Overview refresh uses 5 seconds. Detail uses 3 seconds. Reconnection also reads the API. These paths keep durable state authoritative after a lost completion event.
Automatic admission errors appear on Sorting. After correcting access or settings, save enabled automatic settings again to requeue blocked arrivals.