.. This file is part of PEPSI. Copyright (C) 2026 Pepsi contributors PEPSI is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. ===================== pepsi-stage-anti-spam ===================== *Gate a message behind a GNU Taler payment (pay-to-send).* 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: :manpage:`pepsi-stage-anti-spam(1)`. Decision ======== Driven by the message's ``state`` JSON: * ``spam: true`` → **drop** (delete the row). * ``spam: false`` **or** ``paid: true`` → **forward** 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 :doc:`pepsi-stage-bounce` rejection DSN, or a :doc:`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 :doc:`../smtp-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. 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//`` 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..body`` :doc:`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 :doc:`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. Configuration ============= ``[stage-]``: ``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 :manpage:`pepsi-stage-anti-spam(1)`. 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. See also ======== :doc:`pepsi-stage-bounce`, :doc:`pepsi-stage-discard`, :doc:`pepsi-httpd`, :doc:`pepsi-setup`, :doc:`../smtp-extensions`, :manpage:`pepsi-stage-anti-spam(1)`.