85.1.19. pepsi-stage-list

route mail addressed to a mailing list

Manual section:

1

85.1.19.1.1. Name

pepsi-stage-list - the mailing-list router of the Pepsi pipeline.

85.1.19.1.2. Synopsis

pepsi-stage-list [GLOBAL-OPTIONS] worker

85.1.19.1.3. Description

pepsi-stage-list is a stage program run by pepsi-dispatch(1). It looks at each envelope recipient, decides whether it names a mailing list on this server, and routes the message accordingly. It is the only one of the five list stages that runs on every message, so it is cheap by design: it loads the envelope and state and never the headers or the body.

Pepsi’s mailing-list subsystem is a reimplementation of GNU Mailman 3: the address vocabulary this stage recognises – the posting address and the eight role sub-addresses – is upstream’s, copyright the Free Software Foundation and its contributors, so that a site migrating from Mailman keeps every address its subscribers already use. Files in Pepsi that came from GNU Mailman remain under the GNU General Public License; see pepsi-list(1) for the fuller statement and vendor/PEPSI-VENDORING.md for the list.

A server that hosts no lists is unaffected by it. With no lists configured the routing table is empty and every message advances to NEXT_STAGE, which is what makes dropping the section into an existing pipeline harmless.

85.1.19.1.4. Addresses recognised

For each recipient the stage matches, in this order:

  • the posting address <list>@<host>;

  • then the eight role sub-addresses -request, -join, -subscribe, -leave, -unsubscribe, -confirm+<token>, -owner and -bounces (optionally carrying VERP detail, -bounces+alice=example.net).

Posting addresses are matched before the suffixes, and the suffixes are tried longest-first. Both orders matter. A list may legitimately be called foo-request; matching suffixes first would make it unreachable the moment somebody created a list called foo, and the symptom would be mail to a real list silently entering the command path. Longest-first is why -unsubscribe is not read as -subscribe with a stem ending in -un.

Warning

Setting [pepsi] RECIPIENT_DELIMITER to - breaks list sub-addressing entirely. Every sub-address is <list>-<role>, so with - as the delimiter the base of announce-owner@ is announce — the posting address — 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+token address, so the router always splits what the lists wrote.

85.1.19.1.5. Routing

A recipient that names a list is routed to one of three stages by role:

Role

Stage option

the posting address

POST_STAGE

-bounces

BOUNCE_STAGE

every other role

COMMAND_STAGE

-owner goes to the command stage even though it carries no command: it is the stage that knows how to look up a list’s owners and how to suppress an auto-reply loop, and giving it a fourth stage of its own would duplicate both.

A message addressed to a list and to an ordinary mailbox — or to two lists — is split, one row per destination, each carrying its own recipients and its own slice of state.dsn.rcpt. The not-a-list group, when there is one, stays on the original row.

Warning

BOUNCE_STAGE here means the opposite of what it means on every other stage in pepsi.conf(5). On this stage it is where somebody else’s inbound bounce is consumed; everywhere else it is where Pepsi generates a DSN. Pointing it at pepsi-stage-bounce(1) answers a bounce with a bounce — a mail loop rather than a misrouted message — and pepsi-setup refuses that configuration.

85.1.19.1.6. Freshness

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. A list created while the dispatcher is running therefore starts receiving mail with no restart and no staleness window. The stage itself holds no listener: a stage worker’s connection pool is exactly one connection, and a listener inside the worker would hold it forever.

85.1.19.1.7. Fusion

A fusion successor may run in-process only if its Load is satisfied by what the predecessor already loaded. This stage loads metadata only, so it can fuse its own advance to NEXT_STAGE — the not-a-list path, the one that runs on every message — but it cannot fuse into pepsi-stage-list-post(1), which needs the body. Routing an actual list post costs one ordinary stage transition.

85.1.19.1.8. Configuration

NEXT_STAGE, POST_STAGE, COMMAND_STAGE and BOUNCE_STAGE, all four required, in the stage’s own [stage-<name>] section. Per-list configuration is in the database, not here; see pepsi-list(1).

85.1.19.1.9. State

Inputs: the envelope rcpt_to.

Outputs: a state.list descriptor on each routed row — the list’s id, the role, the address the message arrived at, and whatever detail the sub-address carried (a -confirm token, a -bounces VERP address). Deliberately small: everything else the worker stage needs it reads from the database, which it is about to do anyway.

Transitions: advances (to NEXT_STAGE or to a role’s stage), and fans out when the recipients split.

85.1.19.1.10. Commands

worker

Run as a persistent pepsi-dispatch(1) worker, reading message ids on standard input.

85.1.19.1.11. Global Options

The usual set — -c/–config, -L/–log, -v/–verbose, -h/–help and -V/–version — behaving as they do for every Pepsi program; see pepsi-config(1).

85.1.19.1.12. Exit Status

0

The message was routed.

1

An error occurred (a misconfigured stage, or a database error).

85.1.19.1.13. Examples

[stage-list]
PROGRAM = pepsi-stage-list
NEXT_STAGE = aliases
POST_STAGE = list-post
COMMAND_STAGE = list-command
BOUNCE_STAGE = list-bounce

85.1.19.1.14. See Also

pepsi-list(1), pepsi-stage-list-post(1), pepsi-stage-list-deliver(1), pepsi-dispatch(1), pepsi.conf(5), pepsi.state(7), pepsi-setup(1)

85.1.19.1.15. Bugs

Report bugs to the Pepsi issue tracker.