Sieve interpreter

RFC 5228 Sieve interpreter that walks the AST produced by b.safeSieve.parse(script) and emits an ordered action list the delivery agent applies to the inbound message. Runs under a gas counter (default 10 000 ops) so a hostile or runaway script can't stall the delivery thread. The interpreter is synchronous and pure — every test reads from the operator-supplied env object; the interpreter never touches the mail store, never opens a socket, never executes operator-supplied code. Side-effects (file- into / redirect / discard) materialize as result entries the caller dispatches against the store.

Tests implemented at v0.9.55: - address — header-address-list test with :all / :localpart / :domain address-parts and :is / :contains / :matches match-types - header — header-value test with the same match-types - envelope — RFC 5228 §5.4; reads env.envelope.{from,to} - exists — header-presence test - size — :over N / :under N byte-count test - not / allof / anyof / true / false

Actions implemented at v0.9.55: - keep — implicit default per §2.10.2 - fileinto "Folder" — RFC 5228 §4.1 - discard — RFC 5228 §4.4 - redirect "addr" — RFC 5228 §4.2; tagged with the address - stop — RFC 5228 §3.2; halts further command execution

Comparators: i;octet (default, exact byte) + i;ascii-casemap (case-insensitive ASCII). Other comparators refused at script parse time (require 'comparator-NAME' not in KNOWN_CAPABILITIES).

Match-type wildcards: :matches uses * (any sequence) and ? (one byte), per RFC 5228 §2.7.1. The pattern is matched directly against the value rather than translated into a regular expression, so a script author cannot spend the delivery thread's CPU by writing a pattern that a backtracking engine explores.

The interpreter does NOT execute multi-script chains, sieve includes (RFC 6609), notify actions (RFC 5435), or vacation responses (RFC 5230); each of those will land with the corresponding extension RFC slice. Until then, scripts declaring them via require are refused at parse time.

b.mail.sieve.implementedNames() #

stable0.20.32

The test and action names this interpreter resolves. b.safeSieve.parse reads them so a script naming an action the interpreter does not implement is refused at upload, where the author can still fix it, rather than at delivery, where the throw costs the whole script.

A consumer that needs the same answer reads it here instead of keeping its own copy, which would rot at the next extension.

b.mail.sieve.implementedNames().actions;
// → ["keep", "fileinto", "discard", "redirect", "stop"]

b.mail.sieve.run(ast, env, opts?) #

stable0.9.55
{
  maxGas: number,    // default 10000; cap 1_000_000
}

Walk a parsed Sieve AST against the message environment + return the ordered action list. The interpreter is pure — it reads only from env and never mutates it; every action surfaces as an entry in the returned list for the caller to dispatch.

var ast = b.safeSieve.parse('require ["fileinto"];\r\n' +
  'if header :contains "X-Spam" "yes" { fileinto "Junk"; }');
var rv  = b.mail.sieve.run(ast, {
  headers:    [{ name: "X-Spam", value: "yes" }],
  envelope:   { from: "sender@example.com", to: "rcpt@example.com" },
  sizeBytes:  1024,
});
// → { actions: [{ kind: "fileinto", folder: "Junk" }], gas: 5, stopped: false }

b.mail.sieve.runScript(script, env, opts?) #

stable0.9.55
{
  profile:           "strict" | "balanced" | "permissive",
  compliancePosture: "hipaa" | "pci-dss" | "gdpr" | "soc2",
  maxGas:            number,
}

Parse + run in one call. Most call sites — JMAP SieveScript/validate, MX delivery hook — want this shape.

var rv = b.mail.sieve.runScript(
  'require ["fileinto"];\nif header :is "From" "boss@x.com" { fileinto "Important"; }',
  { headers: [{ name: "From", value: "boss@x.com" }] }
);
rv.actions[0].folder;                              // → "Important"

b.mail.sieve.create(opts?) #

stable0.9.55
{
  maxGas:            number,
  profile:           "strict" | "balanced" | "permissive",
  compliancePosture: "hipaa" | "pci-dss" | "gdpr" | "soc2",
  audit:             { safeEmit: function },
}

Returns a stateful Sieve handle the delivery agent + JMAP SieveScript/validate method compose. Distinct from the bare b.mail.sieve.run(ast, env) entry — the handle carries operator- supplied opts (maxGas, profile, compliancePosture, audit) so every invocation runs with the same posture.

var sieve = b.mail.sieve.create({ profile: "strict", audit: b.audit });
sieve.validateScript(operatorScript);
var rv = await sieve.runScript(operatorScript, mailEnv);

Last updated 2026-10-12T14:21:56.879Z by seeder.