85.1.33. pepsi-stage-edit-settings¶
change per-address settings from a control e-mail
- Manual section:
1
85.1.33.1.1. Name¶
pepsi-stage-edit-settings - the settings-by-e-mail stage of the Pepsi pipeline.
85.1.33.1.2. Synopsis¶
pepsi-stage-edit-settings [GLOBAL-OPTIONS] worker
85.1.33.1.3. Description¶
pepsi-stage-edit-settings is a stage program run by pepsi-dispatch(1)
as a persistent worker reading message ids on standard input. It loads that pepsi.workqueue row
(refusing to act unless its status is running), reads its
[stage-<stage>] section, and decides whether the message is a
settings-control message an account owner sent to change their own per-address
settings (the pepsi.settings overrides described in pepsi-settings(1)).
A message is a control message only when all of these hold:
it is locally originated (
state.local_originistrue) and has a non-empty envelope sender (a null-sender message is never a control message);its
Subject:is exactly SUBJECT (defaultPepsi); andone of its envelope recipients is
<CONTROL_LOCAL_PART>@<domain>(default local partpepsi) for adomainthe instance accepts ([pepsi-ingress] ACCEPTED_DOMAINS).
Any other message is advanced unchanged to NEXT_STAGE, so the stage is safe to place anywhere on the local-submission path.
Deciding whether a message is a control message — locally originated, the right
Subject, addressed to the control local part at an accepted domain — needs only
the message metadata, never the body. This stage therefore takes part in stage
fusion (see pepsi-dispatch(1)) with a data-dependent gate: it defaults to
FUSION = yes and a predecessor fuses it in-process for every non-control
message (which it just advances), but declines fusion for a genuine control
message, which is then committed and dispatched as its own worker so the full body
is loaded for parsing. Set FUSION = no to opt out.
The address whose settings are edited is the envelope sender — the owner edits their own settings, and the reply is mailed back to them. (The operator’s submission authentication must therefore bind the envelope sender; otherwise an authenticated user could edit another address’s settings.)
85.1.33.1.4. The body¶
The message body is read as a lenient INI snippet: [stage-<name>] section
headers and OPTION = value assignments. Blank lines, comments
(#/%/;), a leading greeting, and a trailing signature (a -- line)
are ignored, so an ordinary e-mail containing the configuration works. Each
assignment adds or overrides the stored override for that stage’s option; an
assignment whose value is empty (OPTION =) removes the override, reverting
the option to its INI default.
Only sections whose stage name appears in EDITABLE_STAGES may be changed; a body addressing any other section is rejected.
For safety, an assignment that sets a stage’s PROGRAM is only accepted when its
value is a pepsi-stage-* command (with no path component), so an account owner
cannot redirect a stage at an arbitrary executable; and an assignment that sets
SIGNING_DOMAIN is refused outright, however permissive EDITABLE_STAGES is.
That option overrides the domain a message is DKIM-signed as, so allowing it would
let any account owner have their mail signed as any domain this host holds a key
for. Setting UNRESTRICTED_UNSAFE_STAGES = YES in the stage’s configuration
lifts both restrictions (unsafe; the default is NO).
For the same reason pepsi-setup --wizard never puts a signing stage in
EDITABLE_STAGES: it lists only the inbound gates and the vacation responder,
and where none of those is enabled it does not configure this stage at all.
The resulting combined settings — the INI defaults, plus the address’s existing
overrides, plus the new assignments — are validated by running each affected
stage’s own configuration parser, plus the checks that apply only to an account
owner’s own choice: the WHITELIST_NAME of a stage that writes a whitelist on
the owner’s behalf – pepsi-stage-auto-whitelist(1), which records the
recipients of their outgoing mail, and pepsi-stage-secretary(1), which
records the senders who answer a challenge – must name a whitelist in the
sender’s own <login>/... namespace, or the one the operator’s configuration
already names for them; a shared whitelist or another user’s is refused. The
login is resolved with the locality options (LOCAL_DOMAINS,
RECIPIENT_DELIMITER, TARGETS) of the operator’s stage section, not with
any override of the owner’s own. The rule applies only when the message
changes the whitelist the sender’s mail feeds, so a value already stored in
the row – which the operator may have set with pepsi-settings(1) – does
not stop the owner from editing other options. This stage is the only way an
account owner can write pepsi.settings, so it is where the rule is enforced;
the operator’s own layers (the INI file, pepsi.config_override at any
scope, and rows written with pepsi-settings(1)) may name any whitelist.
The settings are judged on top of the operator’s configuration for the sender,
domain: and address: overrides of pepsi.config_override included,
and the reply quotes that same effective configuration.
If anything is invalid, no change is made
and a human-readable reply explains every problem. If everything is valid, the
merged overrides are stored and a reply quotes the full effective option set
(defaults plus overrides) of every editable stage, in INI syntax.
A stage listed in EDITABLE_STAGES has its whole effective configuration quoted
back to any account owner who sends a control message, not just the options
they changed. Option values whose names look credential-bearing (PASSWORD,
PASSPHRASE, SECRET, TOKEN, CREDENTIAL, CLIENT_ID,
PEPPER) are replaced with ***, the same masking GET /api/v1/config
applies — but the rest of the section, including host names and file paths, is
disclosed verbatim. Do not list a stage whose section carries anything you
would not show a local user.
Either reply carries Subject: SUBJECT, the null envelope sender <>
(RFC 3834, so it is never itself gated or bounced), and is injected at
RESPONSE_STAGE (where it is DKIM-signed and relayed like any outbound mail).
The control message itself is then removed.
The framing prose of both replies is localised: it is rendered from the
operator-customizable edit-settings.<lang>.body Mustache template under
[pepsi] TEMPLATE_DIR, choosing <lang> from the sender’s detected
language(s) (the state.language Accept-Language string set by
pepsi-stage-detect-language) and always falling back to English
(edit-settings.en.body). The template’s {{#success}} / {{^success}}
branches frame, respectively, the effective-settings echo and the problem list.
This stage loads the message body but does not modify it.
85.1.33.1.5. Configuration¶
Options live in the stage’s own [stage-<name>] section
(PROGRAM = pepsi-stage-edit-settings): the required EDITABLE_STAGES
allowlist (which must name at least one existing stage), the required
RESPONSE_STAGE the reply is injected at, and a NEXT_STAGE for ordinary
mail — also required, since pepsi-setup(1) refuses a section without one:
every non-control message is advanced there. The optional
SUBJECT/CONTROL_LOCAL_PART/RESPONSE_FROM control parameters and the
UNRESTRICTED_UNSAFE_STAGES safety switch complete the section. They are
documented in pepsi.conf(5).
The stage reads its own section from the base configuration, never through the
per-address override layer, so no account owner’s pepsi.settings row can
change what this stage considers a control message or what it allows.
85.1.33.1.6. State¶
Inputs: state.local_origin (gates whether the message is a control
message), state.language (picks the language of the reply), and the envelope
sender/recipients and Subject: header.
Outputs: the stage rewrites the pepsi.settings row of the envelope sender
and injects a reply; it does not modify the state of the original message
(which it removes). The state layout is described in pepsi.state(7).
Transitions:
an ordinary message (not locally originated, or not matching SUBJECT / CONTROL_LOCAL_PART) → advance to NEXT_STAGE unchanged;
a control message → inject the reply (the INI echo on success, or the error on a rejected/invalid edit) at RESPONSE_STAGE and finish (the original row is deleted).
The stage never pauses, fails or reroutes.
The confirmation is rendered before the new settings are stored, so a missing reply template is retried with nothing changed. Once they are stored, a reply that cannot be queued is logged and the request deleted: the change is made, and must not be made again.
85.1.33.1.7. Commands¶
- worker
Run as a persistent pepsi-dispatch(1) worker, reading message ids on standard input.
85.1.33.1.8. 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.33.1.9. Exit Status¶
- 0
The message was processed (passed through, or applied/rejected with a reply).
- 1
An error occurred (message not found or not
running). The reason is written to the log.A fault of the host (the database, a template or helper that cannot be used) is not reported as a failure: the message is paused and retried, as Stage errors in pepsi-dispatch(1) describes. A section that does not parse makes the worker refuse to start (status 78) instead of failing each message in turn.
85.1.33.1.10. Examples¶
A pipeline section that lets owners edit their whitelist and language policy, replying via the outbound path:
[stage-edit-settings]
PROGRAM = pepsi-stage-edit-settings
EDITABLE_STAGES = check-whitelist, block-language
RESPONSE_STAGE = dkim-sign
NEXT_STAGE = srs
A control e-mail body (Subject: Pepsi, To: pepsi@example.org) that sets a
whitelist and clears a language blacklist:
[stage-check-whitelist]
WHITELIST_NAME = my-contacts
[stage-block-language]
BLACKLIST =
85.1.33.1.11. See Also¶
pepsi-settings(1), pepsi-config(1), pepsi-dispatch(1), pepsi-stage-check-whitelist(1), pepsi-stage-block-language(1), pepsi.conf(5), pepsi.state(7), pepsi-setup(1)
85.1.33.1.12. Bugs¶
Report bugs to the Pepsi issue tracker.