.. 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-check-whitelist =========================== *Mark mail from trusted senders as non-spam.* Role ==== ``pepsi-stage-check-whitelist`` consults a named whitelist of trusted senders and, when the message's ``From:`` header matches, records ``state.spam = false`` so a later :doc:`pepsi-stage-anti-spam` lets the message through without payment. Place it ahead of the anti-spam stage. It only annotates the state and advances — it never drops, bounces or pauses a message. Reference: :manpage:`pepsi-stage-check-whitelist(1)`. Features ======== * **Database-backed whitelists:** the ``pepsi.whitelist`` table groups rows under a ``whitelist_name``; the stage's ``WHITELIST_NAME`` option selects the group. * **POSIX ERE matching:** each ``whitelist_regex`` is an extended POSIX regular expression matched **case-insensitively** against the ``From:`` header (evaluated by PostgreSQL's ``~*`` operator, in a single round-trip). The expression is unanchored; anchor it with ``^``/``$`` to match the whole value. * **Optional signature conditions per row:** * ``dkim_required`` → matches only if the ``From:`` domain is authenticated: ``state.auth.dkim = pass``, or an ARC pass that DMARC also confirms (``state.auth.arc = pass`` **and** ``state.auth.dmarc = pass``). ARC pass alone is only chain integrity (a one-hop chain can be self-minted over a forged ``From:``), so it does not satisfy the row by itself. * ``signature_required`` → matches only if ``state.signature_verified`` is ``true``, which :doc:`pepsi-stage-decrypt` sets for a message whose end-to-end signature verified against a **trusted** key. A ``valid-untrusted`` verdict deliberately does not set it, so such a row is a genuine "this correspondent signs, verifiably" gate rather than a "someone signed this" one. Place a decrypt stage before this one, or the row never matches. * **Non-destructive:** on a match it merges ``spam: false`` and advances to ``NEXT_STAGE``; on no match it advances unchanged. A message with no ``From:`` header matches nothing. Configuration ============= ``[stage-]``: ``PROGRAM = pepsi-stage-check-whitelist``, ``WHITELIST_NAME`` (required — the ``whitelist_name`` group to consult) and ``NEXT_STAGE``. See :manpage:`pepsi-stage-check-whitelist(1)`. State ===== * **Inputs:** ``state.auth.dkim`` / ``state.auth.arc`` / ``state.auth.dmarc`` (for ``dkim_required`` rows) and ``state.signature_verified`` (for ``signature_required`` rows; written by :doc:`pepsi-stage-decrypt`). The ``From:`` header comes from the ``from_header`` column. * **Outputs:** on a match, ``spam: false`` merged into ``state``; otherwise ``state`` is unchanged. See also ======== :doc:`pepsi-stage-anti-spam`, :doc:`pepsi-stage-arc`, :doc:`../features`, :manpage:`pepsi-stage-check-whitelist(1)`.