85.1.18. pepsi-stage-aliases

expand message recipients through an alias mapping file

Manual section:

1

85.1.18.1.1. Name

pepsi-stage-aliases - the alias / mailing-list expansion stage of the Pepsi pipeline.

85.1.18.1.2. Synopsis

pepsi-stage-aliases [GLOBAL-OPTIONS] worker

85.1.18.1.3. Description

pepsi-stage-aliases is a stage program run by pepsi-dispatch(1). It loads a pepsi.workqueue row (refusing to act unless its status is running), reads its [stage-<stage>] section, and rewrites the message’s envelope recipients through the alias map named by ALIASES. Every recipient that matches an entry in the map is replaced by that entry’s target addresses; recipients that match nothing are left unchanged. The resulting recipient list is de-duplicated case-insensitively so each address is delivered to at most once, and the message is advanced to NEXT_STAGE.

The stage only loads the envelope and state — never the headers or body. When no recipient matches the map (and no duplicate is removed) the message is advanced unchanged.

85.1.18.1.4. Alias file

ALIASES names a human-readable mapping file parsed like a Postfix virtual(5) table. Each non-blank line maps one key to one or more target addresses:

# comments begin with '#'; blank lines are ignored
postmaster@example.com   alice@example.com
staff@example.com        bob@example.com, carol@partner.example
@example.net             ops@example.com
sales-*@example.org      sales@example.com

The key (left-hand side) is the first whitespace-delimited field: a full address, an @domain catch-all, or a * wildcard pattern (a *-glob such as *@example.org or sales-*@example.org). The targets (right-hand side) are separated by commas and/or whitespace and are kept verbatim — they may be local or remote, but may not contain a wildcard. A line with a key but no targets is ignored (a recipient is never silently deleted). If the same key appears twice, the later line wins.

As in virtual(5), a line whose first character is whitespace continues the previous mapping rather than starting a new one, so a long target list may be wrapped over several lines; blank and all-comment lines in between do not break such a continuation. A # begins a comment only at the start of a field (at the start of the line, or after whitespace), so bug#42@example.com is an ordinary key rather than a truncated one.

Warning

Wildcards are globs, not regular expressions. * matches any run of characters and every other character — a . included — is a literal. To alias a whole domain write *@example.net (or the @example.net catch-all); .*@example.net is a regular expression and, read as a glob, demands a leading dot, so it matches no real address and every message to that domain falls through unaliased. (The pepsi.whitelist patterns of pepsi-whitelist(1) are POSIX EREs; this map is not.) Both pepsi-setup’s syntax check and the stage’s own map parser reject/flag a regex-looking key and name the glob you probably meant.

The file is read into memory and re-read only when its identity changes — modification time, size and inode, so an mtime-preserving replacement (cp -p, a restored backup) is noticed too — and a busy worker therefore does not re-parse it for every message. A missing file is an empty map (no aliases configured). A file that exists but cannot be read (permissions, an I/O error) is an error: the message is paused and retried with back-off, like any other fault of the host, rather than delivered unaliased – a postmaster@ or catch-all entry silently not applying would misdeliver or bounce it. The unreadable file is not cached, so the next attempt reads it again. pepsi-setup syntax-checks the ALIASES file at install time when it exists, and writes a commented starter template when it does not (see pepsi-setup(1)); a malformed line is reported there as a warning before deployment rather than only at run time, never as a fatal error.

85.1.18.1.5. Matching and expansion

A recipient is lower-cased and looked up verbatim; on a miss the @domain catch-all key for its domain is tried, and finally any * wildcard key — the most specific matching wildcard wins (a narrow sales-*@example.org beats a broad *@example.org beats a bare *; ties are broken deterministically). Sub-address (+tag) detail is not stripped, so a specific list address such as list+announce@example.com can be aliased on its own.

Expansion is transitive: when an alias target is itself a key it is expanded in turn, so nested distribution lists resolve to their leaf addresses. A loop — an alias that refers back to itself directly or through a chain — is broken by resolving the repeated key to its literal address rather than recursing forever. A chain deeper than 32 hops is stopped the same way (the address is delivered to literally, with a warning), so a generated map cannot overflow the worker’s stack.

85.1.18.1.6. Delivery Status Notifications

state.dsn.rcpt is parallel to the recipient list, so it is rebuilt in lockstep with the rewritten recipients. An expanded target inherits the NOTIFY of the original recipient it came from with any SUCCESS keyword removed (RFC 3461 §5.2.7.3(c): otherwise one submission to a five-member alias would draw five positive DSNs for one recipient, and would disclose the alias’s membership; a NOTIFY that was only SUCCESS becomes NEVER), and, following the same section, carries an ORCPT pointing at the original recipient (its existing ORCPT if it had one, otherwise rfc822;<original-recipient>). A recipient that was not expanded keeps its DSN entry unchanged. Message-level RET/ENVID and all other state keys are preserved.

85.1.18.1.7. Configuration

Options live in the stage’s own [stage-<name>] section (PROGRAM = pepsi-stage-aliases): the ALIASES map-file path and the NEXT_STAGE the expanded message advances to. Both are required — the stage advances every message it sees, so pepsi-setup rejects a section missing either. They are documented in pepsi.conf(5).

85.1.18.1.8. State

Inputs: the envelope rcpt_to and the per-recipient state.dsn.rcpt.

Outputs: a rewritten rcpt_to and a matching state.dsn.rcpt, written only when the recipient list actually changed (an alias matched, or a duplicate was removed); no other state is touched. The state layout is described in pepsi.state(7).

Transitions: advances to NEXT_STAGE (the only transition); the stage never pauses, fails, reroutes or finishes.

85.1.18.1.9. Commands

worker

Run as a persistent pepsi-dispatch(1) worker, reading message ids on standard input. This is how the dispatcher runs the stage in production.

85.1.18.1.10. Global Options

-c FILE, –config FILE

Read the configuration from FILE instead of searching the default locations.

-L LOGLEVEL, –log LOGLEVEL

Set the logging verbosity (default info).

-v, –verbose

Show log messages from all sources.

-h, –help; -V, –version

Print a usage summary / the version and exit.

85.1.18.1.11. Exit Status

0

The message was processed (advanced to NEXT_STAGE).

1

An error occurred (message not found or not running, a misconfigured stage — e.g. a missing ALIASES or NEXT_STAGE — or a database error). The reason is written to the log. A missing alias file is not an error: it is treated as an empty map.

85.1.18.1.12. Examples

Reprocess message 42 through a one-off worker (it must be running):

echo 42 | pepsi-stage-aliases -c /etc/pepsi/pepsi.conf worker

A pipeline section placed just before local delivery, expanding recipients and forwarding to the local-delivery stage:

[stage-aliases]
PROGRAM = pepsi-stage-aliases
NEXT_STAGE = local
ALIASES = /etc/pepsi/aliases

85.1.18.1.13. See Also

pepsi-config(1), pepsi-stage-relay-to-maildir(1), pepsi-stage-if(1), pepsi-dispatch(1), pepsi.conf(5), pepsi.state(7), pepsi-setup(1)

85.1.18.1.14. Bugs

Report bugs to the Pepsi issue tracker.