44. pepsi-stage-anti-spam¶
Gate a message behind a GNU Taler payment (pay-to-send).
44.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 – by the pepsi-resume merchant webhook, which
pepsi-setup provisions on the Taler backend to call POST /resume on
pepsi-httpd, or by the deadline lapsing – it forwards, rejects, or
waits. Reference: pepsi-stage-anti-spam(1).
44.2. Decision¶
Driven by the message’s state JSON:
spam: true→ drop (delete the row).spam: falseorpaid: true→ forward toNEXT_STAGE(an explicit classification overrides the payment flow).paid: falsepresent → 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 noBOUNCE_STAGEis wired;unpaid but still before the deadline → with
DELAY_DSN_AFTERset, this is the delay checkpoint: e-mail the sender a one-shot delayed DSN (RFC 3461, only if they asked forNOTIFY=DELAY) without releasing the message, then re-pause untilPAYMENT_DEADLINE; otherwise re-pause until the deadline with a warning (should not happen). The row stayspaused, neverpending, which would busy-loop against the early wake-up.
no
paidfield → create a v1 order priced byORDER_CHOICES(order_idis the message token), pause untilPAYMENT_DEADLINE(or until theDELAY_DSN_AFTERcheckpoint when that falls earlier), recordingpaid: falseandpay_deadlineinstate, 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) when one is configured, and forwarded to NEXT_STAGE
unchanged when it is not — never silently dropped.
44.3. Payment-request reply¶
After that first pause, the stage originates an auto-reply to the sender (when
BLOCK_RESPONSE_STAGE is set) — unless RFC 3834 says the message must not be
answered automatically (Auto-Submitted other than no, Precedence:
bulk|list|junk, X-Auto-Response-Suppress, any List-* field, a service
sender such as mailer-daemon or a VERP list bounce address, a
multipart/report), the same shared rules the vacation responder applies. The
message is held for payment either way; only the prompt is withheld, so the gate
does not turn list traffic and other responders into backscatter. The same holds
within BLOCK_RESPONSE_SUPPRESS (default 1 h) of a previous request to the
same sender for the same protected mailbox: a flood bearing one forged sender
draws one request per window, not one per message. The window is claimed and
recorded in one statement (payment_request_should_send over
pepsi.payment_request_reply, the vacation_should_reply pattern), so
concurrent workers cannot both send. 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, one chunk per detected language that has a template,
falling back to PAYMENT_MESSAGE_DEFAULT_LANGUAGE (en by default). 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. The
reply is rendered before the pause, so a missing default-language template fails
the stage rather than pausing a message whose sender could never be told; queueing
the rendered reply afterwards is best-effort — a failure is logged but never
undoes 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.
44.4. Configuration¶
[stage-<name>]: PROGRAM = pepsi-stage-anti-spam, NEXT_STAGE
(required — a paid message, one classified as not spam and a verified bounce all
go there), BOUNCE_STAGE, ORDER_CHOICES (the verbatim Taler v1 order
choices JSON array, required), PAYMENT_DEADLINE (h/m/s units;
default 2 days), DELAY_DSN_AFTER (h/m/s; optional, send a delayed
DSN mid-window — see above), SUMMARY (default E-mail delivery) 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), BLOCK_RESPONSE_SUBJECT,
BLOCK_RESPONSE_SUPPRESS (h/m/s; default 1 h, 0 s answers
every message) and
PAYMENT_MESSAGE_DEFAULT_LANGUAGE (default en, and its
payment-request.<lang>.body template must exist — pepsi-setup enforces
that). BOUNCE_TARGET_STAGE takes an unverifiable bounce; when it is unset such
a bounce is forwarded to NEXT_STAGE unchanged. Requires the shared
[pepsi-payments] merchant backend and, for the resume webhook,
[pepsi-httpd] RESUME_AUTHORIZATION_TOKEN. See
pepsi-stage-anti-spam(1).
44.5. State¶
Inputs:
state.spam/state.paid(booleans),state.pay_deadline(epoch seconds),state.dsn(recipientnotify/orcpt).Outputs: on the first encounter,
{paid: false, pay_deadline}merged intostate, the row paused, and a payment-request reply injected atBLOCK_RESPONSE_STAGE; on rejection, astate.bounceobject (permanent) for the bounce stage.
44.6. See also¶
pepsi-stage-bounce, pepsi-stage-discard, pepsi-httpd, pepsi-setup, SMTP Protocol Extensions, pepsi-stage-anti-spam(1).