52. pepsi-stage-block-language

Route a message by the human language(s) detected in its body.

52.1. Role

pepsi-stage-block-language is a language-policy filter. It scores the language(s) recorded under state.language (written upstream by pepsi-stage-detect-language) against a configured whitelist and blacklist and routes the message: above the score THRESHOLD it advances to NEXT_STAGE, otherwise ENFORCEMENT decides — hard (the default) reroutes it to BOUNCE_STAGE carrying a failure-DSN state, soft delivers it anyway with a marked Subject:. The body is never loaded (the header block is, because soft mode rewrites a header) and the stage never pauses. Reference: pepsi-stage-block-language(1).

52.2. Scoring

state.language is an HTTP Accept-Language-style string of code;q=quality items, each quality being the detection probability of that language. The stage collapses it to one number:

score = Σ q over languages listed in WHITELIST
      − Σ q over languages listed in BLACKLIST

Languages on neither list contribute nothing. With English whitelisted and French blacklisted, en;q=0.7, fr;q=0.3 scores 0.7 − 0.3 = 0.4. A message whose score is strictly greater than THRESHOLD advances; otherwise it is blocked.

  • Undetected mail. When no language was detected (state.language absent) the score is computed against the pseudo-distribution none;q=1. The literal none may itself appear in WHITELIST/BLACKLIST, so the operator decides the fate of undetected mail (blacklisting none scores it −1, whitelisting +1, listing it nowhere leaves it at 0).

  • Whitelist bypass. A message carrying an explicit state.spam = false verdict — set by an upstream pepsi-stage-check-whitelist match, or by ingress for the reserved <Postmaster> mailbox (RFC 5321 §4.5.1) — skips the language policy entirely and advances to NEXT_STAGE.

  • Never bounce a bounce. A message that is already a bounce (the null envelope sender) advances to NEXT_STAGE regardless of its score (RFC 5321 §6.1).

52.3. Hard and soft enforcement

ENFORCEMENT decides what happens to a message that fails the policy, and both modes select exactly the same set of messages — which is the point: the flag predicts the bounce, so an operator can tune WHITELIST/BLACKLIST/ THRESHOLD against real traffic before any mail is refused.

  • hard (the default) reroutes to BOUNCE_STAGE with a permanent failure-DSN state, so pepsi-stage-bounce emits a bounce only if the sender’s NOTIFY requests FAILURE (the default when absent). No header is touched.

  • soft advances the message to NEXT_STAGE instead, appending SUBJECT_FLAG_LABEL (default [!LANG]) to its Subject: and adding a X-Pepsi-Detected-Languages header. Nothing is bounced, so no BOUNCE_STAGE is needed.

Because ENFORCEMENT reaches the stage through the per-address settings layer, one account can be in training while the rest of the deployment enforces. Rewriting the Subject: breaks the originator’s DKIM signature and our own ARC AMS, which both over-sign it — free on a branch that ends in local delivery, not free on one that relays onward, so soft mode belongs on the former.

52.4. Fusion

The stage defaults to FUSION = yes: under the unified pepsi binary with [pepsi] ALLOW_FUSION on (the default), a predecessor may run it in its own worker process rather than dispatching it separately. See pepsi-dispatch.

52.5. Configuration

[stage-<name>]: PROGRAM = pepsi-stage-block-language, NEXT_STAGE (required — every message the stage does not block advances there, including a whitelisted sender, a bounce, and every message merely flagged under ``ENFORCEMENT = soft``), BOUNCE_STAGE, the WHITELIST/BLACKLIST language lists (at least one must be non-empty, or every message would fail), the numeric THRESHOLD (default 0.0), ENFORCEMENT (hard — the default — or soft) and SUBJECT_FLAG_LABEL (default [!LANG], used only in soft mode). pepsi-setup rejects an invalid language code or an overlap between the two lists, and warns about ENFORCEMENT = hard with no BOUNCE_STAGE (the first blocked message would fail) and about a BLACKLIST with an empty WHITELIST and a non-negative THRESHOLD. See pepsi-stage-block-language(1) and Configuration.

52.6. State

  • Inputs: state.language (treated as none;q=1 when absent) and, for a blocked message, state.dsn and the envelope recipient.

  • Outputs: on the pass path state is untouched; on the hard block path the failure-DSN keys pepsi-stage-bounce consumes (state.bounce) are merged; on the soft path state is untouched and the header block is rewritten instead.

  • Transitions: score above THRESHOLD → advance to NEXT_STAGE; score at or below → reroute to BOUNCE_STAGE (hard) or advance to NEXT_STAGE flagged (soft).

52.7. See also

pepsi-stage-detect-language, pepsi-stage-check-whitelist, pepsi-stage-bounce, Supported Features, pepsi-stage-block-language(1).