54. pepsi-stage-secretary

Hold mail from unknown senders until they confirm by replying.

54.1. Role

pepsi-stage-secretary is confirm-to-send, after qmail’s qsecretary. Mail from a sender no whitelist knows is held, and the sender is asked — in their language — to reply once. The reply whitelists them and releases everything held for them; later mail from them passes pepsi-stage-check-whitelist and is never held again. Without a reply by HOLD_TIME the held mail takes the timeout path (BOUNCE_STAGE if one is wired, else it is deleted). Reference: pepsi-stage-secretary(1).

It is weaker than pay-to-send (pepsi-stage-anti-spam): a spammer with a working mailbox can reply, and the declaration the sender agrees to (PENALTY) is a legal deterrent rather than an economic one. It is still worth having, because most bulk mail comes from addresses that cannot receive, and a real correspondent pays one reply, once. Supported Features compares the two.

54.2. What is challenged

A null-sender message, locally submitted mail and a whitelisted sender (state.spam = false) pass untouched; state.spam = true takes the timeout path. Everything else is challenged — unless it must not be, in which case it goes to UNCHALLENGEABLE_STAGE (unset: the timeout path):

  • RFC 3834 says do not answer it (lists, Precedence: bulk, Auto-Submitted:, service senders, delivery reports);

  • its From: is not exactly the envelope sender — the challenge goes to MAIL FROM and the whitelist matches From:, so they must be one address;

  • REQUIRE_AUTHENTICATED (default on) and neither SPF nor DMARC passed — the backscatter gate;

  • the sender has had MAX_CHALLENGES_PER_SENDER (default 5) challenges in the last 24 hours, across every whitelist.

One challenge covers every message from one sender to one whitelist: a second message joins the open challenge and inherits its deadline. A message whose recipients expand WHITELIST_NAME to different lists is split one row per list.

54.3. The challenge and the reply

The challenge is a null-sender text/plain message with Auto-Submitted: auto-replied, injected at RESPONSE_STAGE. Its From: and Reply-To: are the reply address <CONTROL_LOCAL_PART>-<cookie>@<domain> (default secretary-…), whose cookie is a random 128-bit identifier. It never quotes the held message — a challenge to a forged sender would otherwise deliver the spam for the spammer — beyond an 8-character hint of its subject (SUBJECT_HINT_LENGTH).

The text comes from MESSAGE_<LANG> in the stage’s own section (a user’s own, through the settings layer) or the secretary-challenge.<lang>.body template, per language, with a built-in English fallback; the fields are SENDER_NAME, RECIPIENT, SUBJECT_HINT, ORIGINAL_DATE, DEADLINE, REPLY_ADDRESS and PENALTY. Language choice and the two-space rule are pepsi-stage-vacation’s.

A reply to the address confirms when it comes from the challenged address (its envelope sender or From:). A null-sender reply means the challenge bounced, and releases the held mail to the timeout path at once; a reply marked as automatic (an out-of-office) is ignored. A reply after the deadline has no effect: the challenge is gone. The reply itself is always consumed.

54.4. Placement

After pepsi-stage-detect-language and pepsi-stage-check-whitelist, before pepsi-stage-aliases, the list router and local delivery. Run pepsi-stage-auto-whitelist into the same WHITELIST_NAME on the outbound path so the people your users write to are never challenged. pepsi-setup’s wizard wires all of it when asked “Ask unknown senders to confirm by reply”.

54.5. Configuration

[stage-<name>]: PROGRAM = pepsi-stage-secretary, NEXT_STAGE and RESPONSE_STAGE (mandatory), WHITELIST_NAME (mandatory, one name or a {login}/{localpart} template; the operator may name any whitelist, a shared one included, while a value an account owner sets by mail must stay in their <login>/... namespace or be the operator’s – enforced by pepsi-stage-edit-settings when it stores the value), BOUNCE_STAGE, UNCHALLENGEABLE_STAGE, CONTROL_LOCAL_PART, HOLD_TIME, REQUIRE_AUTHENTICATED, MAX_CHALLENGES_PER_SENDER, DKIM_REQUIRED, CONFIRM_NOTICE, PENALTY, SUBJECT_HINT_LENGTH, TEMPLATE, SUBJECT, DEFAULT_LANGUAGE, the MESSAGE_<LANG>/SUBJECT_<LANG>/CONFIRM_MESSAGE_<LANG> overrides and the locality options. pepsi-setup rejects unresolvable targets and a DEFAULT_LANGUAGE with no text, and warns when no check-whitelist consults the whitelist, no auto-whitelist writes it, or CONTROL_LOCAL_PART is a login or an alias. See pepsi-stage-secretary(1) and Configuration.

54.6. State

  • Inputs: state.spam, state.local_origin, state.auth (SPF, DMARC, DKIM, ARC), state.language.

  • Outputs: state.secretary on a held row (challenge, deadline, whitelist; then confirmed or abandoned), state.secretary.unchallengeable on mail routed to UNCHALLENGEABLE_STAGE, and state.spam = false on released mail. The timeout path removes state.secretary. state.dsn is preserved.

  • Transitions: advance to NEXT_STAGE; pause while held; reroute to UNCHALLENGEABLE_STAGE or BOUNCE_STAGE; delete a reply, or timed-out mail when there is no BOUNCE_STAGE.

54.7. Database

pepsi.secretary_challenge (one open challenge per whitelist and sender, deleted when it is answered, bounces or expires) and pepsi.secretary_sent (when each challenge was sent, without its cookie, for the daily cap). A confirmation writes an ordinary pepsi.whitelist row, escaped and anchored exactly as pepsi-stage-auto-whitelist writes one.

54.8. See also

pepsi-stage-check-whitelist, pepsi-stage-auto-whitelist, pepsi-stage-anti-spam, pepsi-whitelist, pepsi-settings, Supported Features, pepsi-stage-secretary(1).