Table of Contents

Class MailRuleEvaluationPass

Namespace
MailFathom.Application.Rules.Evaluation
Assembly
MailFathom.Application.dll

Runs one account's rules over the mail that has arrived, and over its whole mailbox where somebody asked.

public sealed class MailRuleEvaluationPass
Inheritance
MailRuleEvaluationPass
Inherited Members

Remarks

A step of the account's synchronization run rather than a schedule of its own. That run already has per-account isolation, a jittered backoff, a slot count that stops one account starving another, and a failure path that defers the account instead of the process; a rule pass needs every one of those and none of them differently, so it takes the ones that exist. It follows that only one pass per account is ever in flight, structurally rather than by a lock.

It runs after the mail and its content are committed and never inside the synchronization transaction, so a provider redelivery or a synchronization retry cannot produce a different processing boundary than a clean run — and nothing an MCP read does waits on a rule.

The two walks reach different mail and different rules. The arrival walk reaches mail no pass has evaluated, and recording an evaluation is what takes an email out of it, which is what makes a rule apply to mail arriving from now on; it runs the rules declaring the Arrival trigger and passes over the rest. The whole-mailbox walk is the only way mail already evaluated is evaluated again — reprocessing under a newer rule set is something an owner asks for or a rule's own schedule brings round, never something an edit sets off. Which rules it runs is read from what started it: a run somebody asked for runs every rule of the set, because asking for a run is the request itself rather than an occasion a rule opts into, and a run a schedule started runs the rules that declared the Schedule trigger.

Both walks are bounded per batch and commit each batch with the position it reached, so an interrupted pass resumes at the email nobody read rather than replaying one or stepping over one. What a batch budget leaves behind is the next account run's, which is the same answer synchronization itself gives to a folder it could not finish.

What a match asks the mailbox for is written down in the batch's own transaction, as the durable mutation record every requester uses, and never issued from here: no IMAP command a rule asks for leaves this pass, and the account's convergence pass is what carries each record to a completed or a dead-lettered ending. Committing the requests with the evaluations is what makes the pair atomic — an email is never recorded as evaluated while the change its rules asked for was lost, and a rolled-back batch asks again under the same identity.

The one thing a pass does reach a mail server for is finding a destination folder the account maps and does not mirror, which no run of its own ever binds. That happens between evaluating a batch and committing it, deliberately: a listing is a round trip, and holding the batch's transaction open across one would keep local rows locked for the length of somebody else's network. Everything the pass reads about the mail itself was already stored.

The history of what each rule concluded is written in that same transaction and for the same reason. An explanation committed apart from the decision it explains is one that can outlive a rolled-back batch or go missing from a committed one, and either way an operator reading it would be told something that did not happen.

Constructors

MailRuleEvaluationPass(IMailRuleSetSource, MailRuleSetEvaluator, IMailRuleEvaluationStore, IMailRuleEvaluationRunStore, MailRuleActionRecorder, MailboxDestinationResolver, IMailRuleExecutionStore, IMailFolderMappingReader, OptimisticConcurrencyRetryPolicy, MailRuleEvaluationOptions, TimeProvider)

Initializes the pass from the rule set it applies and the local state it walks.

Methods

RunAsync(MailAccountId, CancellationToken)

Takes one bounded pass over the account's arrivals, and over its requested run where it has one.