38. pepsi-stage-anti-spam

Gate a message behind a GNU Taler payment (pay-to-send).

38.1. Role

pepsi-stage-anti-spam requires a GNU Taler payment for an otherwise-unclassified message before it may continue down the pipeline. On first sight it creates a merchant order and pauses the message; once the message is woken again (the pepsi-resume webhook on payment, or the deadline lapsing) it forwards, rejects, or waits. Reference: pepsi-stage-anti-spam(1).

38.2. Decision

Driven by the message’s state JSON:

  • spam: truedrop (delete the row).

  • spam: false or paid: trueforward to NEXT_STAGE (an explicit classification overrides the payment flow).

  • paid: false present → ask the merchant whether the order is paid:

    • paid → forward to NEXT_STAGE;

    • unpaid past the stored deadline → reject: reroute to BOUNCE_STAGE (a pepsi-stage-bounce rejection DSN, or a pepsi-stage-discard silent drop), or delete when no BOUNCE_STAGE is wired;

    • unpaid but still before the deadline → with DELAY_DSN_AFTER set, this is the delay checkpoint: e-mail the sender a one-shot delayed DSN (RFC 3461, only if they asked for NOTIFY=DELAY) without releasing the message, then re-pause until PAYMENT_DEADLINE; otherwise re-queue as pending with a warning (should not happen).

  • no paid field → create a v1 order priced by ORDER_CHOICES (order_id is the message token), pause until PAYMENT_DEADLINE (recording paid: false and pay_deadline in state), and e-mail the sender a payment request (see below).

The deadline is kept in state.pay_deadline because the dispatcher clears the row’s timeout when it requeues a paused message.

A message with the null envelope sender (a bounce or auto-reply) is never gated: the stage instead tries to verify it as a reply to mail this deployment genuinely sent, using the Pepsi-Origin: proof-of-origin header (see SMTP Protocol Extensions). A bounce whose embedded header verifies is forwarded to NEXT_STAGE; an unverifiable bounce — a forged or expired header, or any bounce when proof-of-origin is unconfigured — is routed to BOUNCE_TARGET_STAGE (e.g. a quarantine), never relayed back to a sender and never silently dropped.

38.3. Payment-request reply

After that first pause, the stage originates an auto-reply to the sender (when BLOCK_RESPONSE_STAGE is set). It explains that the recipient requires unknown senders to pay first, and carries the taler://pay/<backend>/<token> URI as a link and as a QR code (an inline image/png referenced by cid:, so it renders in the widest range of clients while the message stays self-contained), the acceptable payment options (the ORDER_CHOICES, omitting token-family choices), and a pointer to https://wallet.taler.net/ for senders without a wallet. When the original From: carries a display name, the sender is addressed by it. The body is rendered from the payment-request.<lang>.body Mustache template under [pepsi] TEMPLATE_DIR (only en ships; it is the fallback). The reply uses the null sender <> and enters at BLOCK_RESPONSE_STAGE (normally a signing/relay chain), so it is signed and delivered like any other mail. Sending is best-effort — a failure is logged but never blocks the pause.

The reply also carries two machine-readable headers so a sending Pepsi can settle the demand automatically (see pepsi-stage-auto-pay): a Taler: header with the taler://pay/… URI, and the original message’s Pepsi-Origin: header copied verbatim (when present), letting the originating site verify the demand is for its own mail and meter its per-message spend.

38.4. Configuration

[stage-<name>]: PROGRAM = pepsi-stage-anti-spam, NEXT_STAGE, BOUNCE_STAGE, ORDER_CHOICES (the verbatim Taler v1 order choices JSON array, required), PAYMENT_DEADLINE (h/m/s units; default 48h), DELAY_DSN_AFTER (h/m/s; optional, send a delayed DSN mid-window — see above), SUMMARY and FULFILLMENT_MESSAGE. For the payment-request reply: BLOCK_RESPONSE_STAGE (where to inject it; unset disables the reply), BLOCK_RESPONSE_FROM (its From:; default the original recipient) and BLOCK_RESPONSE_SUBJECT. Requires the shared [pepsi-payments] merchant backend and, for the resume webhook, [pepsi-httpd] RESUME_AUTHORIZATION_TOKEN. See pepsi-stage-anti-spam(1).

38.5. State

  • Inputs: state.spam / state.paid (booleans), state.pay_deadline (epoch seconds), state.dsn (recipient notify/orcpt).

  • Outputs: on the first encounter, {paid: false, pay_deadline} merged into state, the row paused, and a payment-request reply injected at BLOCK_RESPONSE_STAGE; on rejection, a state.bounce object (permanent) for the bounce stage.

38.6. See also

pepsi-stage-bounce, pepsi-stage-discard, pepsi-httpd, pepsi-setup, SMTP Protocol Extensions, pepsi-stage-anti-spam(1).