.. This file is part of PEPSI. Copyright (C) 2026 GNUnet e.V. 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-list ================ *Decide which envelope recipients name a mailing list, and route them by role.* Role ==== ``pepsi-stage-list`` is the mailing-list router. For each envelope recipient it asks whether the address names a list on this server and, if so, which of a list's nine addresses it is; the message is then routed to the stage that handles that role. It is the only one of the five list stages that runs on **every** message, so it is deliberately cheap: it loads the envelope and ``state`` and never the headers or the body, and a server that hosts no lists sees every message advance to ``NEXT_STAGE`` untouched. Reference: :manpage:`pepsi-stage-list(1)`. This subsystem is a reimplementation of GNU Mailman 3; see :doc:`../mailing-lists`, which names what was taken from upstream. Features ======== * **Nine addresses per list.** The posting address ``@`` plus the eight role sub-addresses ``-request``, ``-join``, ``-subscribe``, ``-leave``, ``-unsubscribe``, ``-confirm+``, ``-owner`` and ``-bounces`` (which may carry VERP detail, ``-bounces+alice=example.net``). * **Posting address first, then suffixes longest-first.** Both orders are load-bearing. A list may legitimately be called ``foo-request``, and matching suffixes first would make it unreachable the moment somebody created a list called ``foo`` — with mail to a real list silently entering the command path as the symptom. Longest-first is why ``-unsubscribe`` is not read as ``-subscribe`` on a stem ending in ``-un``. * **Three destinations by role.** The posting address goes to ``POST_STAGE``, ``-bounces`` to ``BOUNCE_STAGE``, and every other role — ``-owner`` included — to ``COMMAND_STAGE``, which is the stage that already knows how to find a list's owners and how to suppress an auto-reply loop. * **Mixed recipients split.** A message addressed to a list *and* to an ordinary mailbox, or to two lists, becomes one row per destination, each with its own recipients and its own slice of ``state.dsn.rcpt``. The not-a-list group stays on the original row. * **No staleness window.** The routing table is loaded once per worker and refreshed by the dispatcher retiring its workers when the database announces a change on the ``pepsi_list_changed`` channel, so a list created while the dispatcher runs starts receiving mail with no restart. The stage holds no listener of its own: a worker's pool is one connection, and a listener would hold it forever. Two warnings ============ .. warning:: ``BOUNCE_STAGE`` on this stage means the **opposite** of what it means everywhere else in :doc:`../configuration`. Here it is where somebody else's inbound bounce is *consumed*; everywhere else it is where Pepsi *generates* a DSN. Pointing it at :doc:`pepsi-stage-bounce` answers a bounce with a bounce — a mail loop, not a misrouted message — and ``pepsi-setup`` refuses that configuration. .. warning:: Setting ``[pepsi] RECIPIENT_DELIMITER`` to ``-`` breaks list sub-addressing entirely: every sub-address is ``-``, so the base of ``announce-owner@`` becomes ``announce`` and mail meant for the owners is posted to the list instead. The same option is the delimiter the fan-out writes into each VERP envelope sender and the ``-confirm+`` address, so the router always splits what the lists wrote. Fusion ====== A fusion successor runs in-process only if its ``Load`` is satisfied by what the predecessor loaded. This stage loads metadata only, so it fuses its own advance to ``NEXT_STAGE`` — the not-a-list path, the one that runs on every message — but it cannot fuse into :doc:`pepsi-stage-list-post`, which needs the body. Routing an actual post therefore costs one ordinary stage transition. Configuration ============= ``[stage-]``: ``PROGRAM = pepsi-stage-list``, and ``NEXT_STAGE``, ``POST_STAGE``, ``COMMAND_STAGE`` and ``BOUNCE_STAGE``, all required. Per-list configuration lives in the database rather than in the file; see :doc:`pepsi-list` and :doc:`../mailing-lists`. State ===== * **Inputs:** the envelope ``rcpt_to``. * **Outputs:** a ``state.list`` descriptor on each routed row — the list id, the role, the address the message arrived at, and whatever the sub-address carried (a ``-confirm`` token, a ``-bounces`` VERP address). Deliberately small: everything else a worker stage needs it reads from the database, which it is about to do anyway. * **Transitions:** advance (to ``NEXT_STAGE`` or to a role's stage), and a fan-out when the recipients split. See also ======== :doc:`../mailing-lists`, :doc:`pepsi-stage-list-post`, :doc:`pepsi-stage-list-command`, :doc:`pepsi-stage-list-bounce`, :doc:`pepsi-list`, :manpage:`pepsi-stage-list(1)`.