.. This file is part of PEPSI. Copyright (C) 2026 Pepsi contributors PEPSI is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. ========================== pepsi-stage-block-language ========================== *Route a message by the human language(s) detected in its body.* Role ==== ``pepsi-stage-block-language`` is a language-policy filter. It scores the language(s) recorded under ``state.language`` (written upstream by :doc:`pepsi-stage-detect-language`) against a configured whitelist and blacklist and routes the message: above the score ``THRESHOLD`` it advances to ``NEXT_STAGE``, otherwise it is rerouted to ``BOUNCE_STAGE`` carrying a failure-DSN ``state``. It reads only ``state`` — the body is never loaded — and never pauses or fails. Reference: :manpage:`pepsi-stage-block-language(1)`. 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: .. code-block:: text 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 bounced. * **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 :doc:`pepsi-stage-check-whitelist` match, or by ingress for the reserved ```` 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). On the block path the stage reroutes to ``BOUNCE_STAGE`` with a permanent failure-DSN ``state``, so :doc:`pepsi-stage-bounce` emits a bounce only if the sender's ``NOTIFY`` requests ``FAILURE`` (the default when absent). 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 :doc:`pepsi-dispatch`. Configuration ============= ``[stage-]``: ``PROGRAM = pepsi-stage-block-language``, ``NEXT_STAGE``, ``BOUNCE_STAGE``, the ``WHITELIST``/``BLACKLIST`` language lists and the numeric ``THRESHOLD``. ``pepsi-setup`` rejects an invalid language code, an overlap between the two lists, or a configuration that can block with no ``BOUNCE_STAGE``. See :manpage:`pepsi-stage-block-language(1)` and :doc:`../configuration`. 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 block path the failure-DSN keys :doc:`pepsi-stage-bounce` consumes (``state.bounce``) are merged. * **Transitions:** score above ``THRESHOLD`` → advance to ``NEXT_STAGE``; score at or below → reroute to ``BOUNCE_STAGE``. See also ======== :doc:`pepsi-stage-detect-language`, :doc:`pepsi-stage-check-whitelist`, :doc:`pepsi-stage-bounce`, :doc:`../features`, :manpage:`pepsi-stage-block-language(1)`.