.. 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 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. ========== The Wizard ========== ``pepsi-setup --wizard`` runs a short interview and writes a complete, already-validated ``pepsi.conf``. It is the intended way to produce a first configuration: the next chapter, :doc:`configuration`, explains the file the wizard writes, and this one explains how the answers become that file. The point of the interview is not to save typing. It is that **the stage pipeline is assembled from the answers** — which stages exist, and how their ``NEXT_STAGE`` and ``BOUNCE_STAGE`` options wire together, follows mechanically from the direction the host serves and the features that were switched on. There is no menu of pipelines to pick from and no template being filled in; the graph is computed. So the useful way to understand the wizard is to look at the three pipelines it can build, and then at which question inserts or removes each stage in them. .. note:: This chapter describes *what the answers do*. The operational detail of the command — every flag, the MTA migration, the readline bindings, the secret-prompt behaviour, the answer-file format — is in :manpage:`pepsi-setup(1)`, which this chapter cross-references rather than repeats. How a run proceeds ================== #. **Import.** On a host with no ``pepsi.conf`` yet, the wizard detects an existing Postfix or sendmail and offers to read its settings in as the interview's *defaults*. Nothing is adopted silently — every imported value is still shown at its own prompt. On a re-run, the ``[pepsi-wizard]`` section of the existing file supplies the defaults instead. #. **Interview.** The questions below, grouped into numbered steps. Each answer is validated as it is entered, so a malformed domain or port is caught at its own prompt. #. **Render.** The answers are turned into a configuration file — this is where the pipeline is assembled. #. **Validate.** The rendered text is loaded and validated through the very same path ``pepsi-setup run`` uses. If it fails, the wizard prints the fault and walks back through the questions with the previous answers pre-filled, so correcting one option is a press-Enter walk past the rest. #. **Write.** The file is written, and the secrets in it (the SRS HMAC key, the smarthost and LMTP passwords, the merchant and ``/resume`` tokens) are moved out into mode-restricted ``secrets.d/*.secret`` fragments referenced by ``@inline-secret@`` directives. What was written is then re-loaded and re-validated, because moving the secrets rewrote it. #. **Offer to run setup.** Installing the schema, generating DKIM keys, provisioning TLS certificates and printing the DNS records to publish are ``pepsi-setup run``'s job, not the interview's. The wizard offers to chain straight into it. The same interview is also asked by the browser setup console (see :doc:`web-ui`), described next. .. _wizard-browser: The same interview in a browser =============================== There is no second interview. Every question, its shape, its default, its help text and the conditions under which it is asked live in one place as *data* (``pepsi_setup_model::questions``), and both front-ends render that model — so a question cannot be added to one and not the other, and a test asserts the two identifier sets are equal. What differs is only the presentation: the terminal asks one question at a time, the console shows a step per page. .. figure:: images/setup-identity.png :alt: The Server identity step of the browser setup console :width: 100% The first step. Each field carries the same help the terminal prints, and — where the answer maps to exactly one option — the ``[section] OPTION`` it will end up in, so the same knob can be found in the file afterwards. The strip along the top is the whole interview, with the steps that have been answered marked. The branching is the model's too, so the console hides what the terminal would not ask. Below, switching language blocking on has revealed the policy, threshold and enforcement fields — and has removed the *localize bounce messages* question, because blocking already implies detection and the terminal only asks that one when blocking is off: .. figure:: images/setup-content.png :alt: The Content filtering step, with the language-policy fields revealed :width: 100% Content filtering, with blocking switched on. Two things about the console are worth knowing before using it, and both are deliberate: * **Answers are drafts.** Each step is staged in the database, not written to ``/etc``; nothing takes effect until the interview is applied. A session that times out half way through has not half-configured a mail server. * **The console asks; it does not act.** It runs unprivileged and cannot write ``pepsi.conf``, run certbot or install the schema. A save records a request that the separately-started, root-running ``pepsi-setup apply`` validates and performs. On a host where that privileged half is not installed — it is its own Debian package — the setup pages render **read-only**, showing every value with the controls disabled and the reason beside them, rather than accepting answers into a queue nothing drains. The screenshots above are generated from the shipping templates and stylesheet by ``contrib/screenshot-setup.sh``, so they can be brought back into agreement with the code rather than drifting from it. .. _wizard-direction: The first question decides the shape ==================================== Before any step banner, the wizard asks:: Mail direction this host serves [both]: inbound | outbound | both Everything else follows from it. ``inbound`` means this host receives mail for your domains from the Internet; ``outbound`` means it relays what your own users and local programs submit; ``both`` — the default — means it does each, and the two flows are separated at the first stage. The answer decides which SMTP listeners are written, which of the later steps are asked at all, and which of the three pipelines below is built. .. _wizard-pipeline-both: Pipeline: ``both`` ------------------ The entry stage is a branch on ``state.local_origin`` — the flag ``pepsi-ingress`` sets for an authenticated submission — so a message this host *received* and a message our own user *sent* never share a stage. That separation is not cosmetic: ARC exists to carry an upstream administrative domain's authentication verdicts across our forwarding hop, and sealing our own users' submissions would publish their SPF/DKIM/DMARC results to the world. .. code-block:: text pepsi-ingress Internet ──▶ :25 :465 / :587 ◀── your users │ │ (and the local sendmail └────────┬────────┘ socket, always served) ▼ init pepsi-stage-if on │ state.local_origin ┌──────────────┴────────┐ false │ │ true (our own submissions) ▼ ▼ the received branch wallet WALLET_SELF_SERVICE (second panel below) │ ▼ edit-settings EDIT_SETTINGS │ ▼ auto-whitelist LEARN_WHITELIST or ANTI_SPAM │ ▼ encrypt CRYPTO │ ▼ milter-* imported non_smtpd_milters │ ▼ dkim-sign (always) │ ▼ internet (always) And the received branch, which is where nearly every feature lives: .. code-block:: text (the false branch of the split above) │ ▼ init pepsi-stage-arc │ ▼ decrypt CRYPTO │ ▼ detect-language LANGUAGE_BLOCKING or I18N_BOUNCES │ ▼ block-language LANGUAGE_BLOCKING │ ▼ milter-* MILTER_* (one per filter found) │ ▼ check-whitelist ANTI_SPAM │ ▼ anti-spam ANTI_SPAM │ ▼ autocrypt-learn AUTOCRYPT_LEARN │ ▼ aliases ALIASES_ENABLED │ ▼ vacation VACATION │ ▼ route ──▶ exchange EXCHANGE (our own domains) │ ▼ dot-forward DOT_FORWARD │ ▼ local LOCAL_METHODS (maildir) │ ▼ local-lmtp LOCAL_METHODS (lmtp) │ ▼ srs (always) │ ▼ dkim-sign-relay SIGN_RELAYED │ ▼ smarthost SMARTHOST_HOST (else straight to internet) │ ▼ internet (always) Reading the figures: a stage with ``(always)`` is unconditional for this direction, and every other stage is present exactly when the answer beside it says so. The stages are strung together in the order shown, each one's ``NEXT_STAGE`` pointing at the next one that survived — so switching a feature off does not leave a gap, it shortens the chain. Two structural facts are worth naming, because they are the reason for the order rather than consequences of it: * ``decrypt`` is **first** on the received branch and ``encrypt`` is **last** before signing on the submitted one. Everything downstream of ``decrypt`` therefore sees cleartext (language detection, the whitelist, the paywall), and DKIM signs the bytes that actually go on the wire. * ``srs`` and the relay destination after it are **unconditional** on the received branch. Recipients local delivery did not consume are not errors — an alias expanding off-site, a ``~/.forward`` at another provider, or (with no local delivery configured at all) the entire pure-relay path. They leave as a *forward*, which is why the envelope sender is SRS-rewritten first. .. _wizard-pipeline-inbound: Pipeline: ``inbound`` --------------------- With no submission path there is nothing to branch on, so the ``if`` stage disappears and ARC becomes the entry stage itself. The received branch is otherwise identical to the one above, minus the questions that were never asked: no ``encrypt``, no ``auto-whitelist``, no ``wallet`` or ``edit-settings``. .. code-block:: text Internet ──▶ pepsi-ingress :25 │ ▼ init pepsi-stage-arc (the entry stage) │ ▼ decrypt CRYPTO │ ▼ detect-language LANGUAGE_BLOCKING or I18N_BOUNCES │ ▼ block-language LANGUAGE_BLOCKING │ ▼ milter-* MILTER_* (one per filter found) │ ▼ check-whitelist ANTI_SPAM │ ▼ anti-spam ANTI_SPAM │ ▼ autocrypt-learn AUTOCRYPT_LEARN │ ▼ aliases ALIASES_ENABLED │ ▼ vacation VACATION │ ▼ route ──▶ exchange EXCHANGE (our own domains) │ ▼ dot-forward DOT_FORWARD │ ▼ local LOCAL_METHODS (maildir) │ ▼ local-lmtp LOCAL_METHODS (lmtp) │ ▼ srs (always) │ ▼ dkim-sign-relay SIGN_RELAYED │ ▼ smarthost SMARTHOST_HOST (else straight to internet) │ ▼ internet (always) ``dkim-sign`` and ``internet`` are still written even though no submission reaches them: they are the tail every **bounce** goes down (see :ref:`wizard-bounces`), and ``internet`` is also the relay destination when no smarthost was configured. .. _wizard-pipeline-outbound: Pipeline: ``outbound`` ---------------------- Only the submission branch is built. The ``if`` stage is still emitted, because ``pepsi-ingress`` still sets ``state.local_origin`` and a message without it must go *somewhere* — but on a submission-only host such a message cannot normally occur, so that branch falls straight through to the signing tail rather than into an inbound chain that does not exist. .. code-block:: text your users ──▶ pepsi-ingress :465 / :587 (and the local sendmail socket, always served) │ ▼ init pepsi-stage-if on │ state.local_origin ┌────────┴────────┐ false │ │ true │ ▼ │ wallet WALLET_SELF_SERVICE │ │ │ ▼ │ edit-settings EDIT_SETTINGS │ │ │ ▼ │ encrypt CRYPTO │ │ │ ▼ │ milter-* imported non_smtpd_milters │ │ └────────┬────────┘ ▼ dkim-sign (always) │ ▼ internet (always) Note what is *not* here. ``auto-whitelist`` is absent even though it is an outbound stage: it is offered only as the write side of a correspondent whitelist whose read side (``check-whitelist``) lives on the inbound path, and the question that would enable it is asked inside the inbound-only anti-spam step. An outbound-only host also has no ``srs``: SRS rewrites the sender of mail we *forward*, and this host forwards nothing. .. _wizard-bounces: The shared tail and the bounces ------------------------------- ``dkim-sign ──▶ internet`` is written in every direction, because it is what every generated bounce goes down. The bounce stages themselves are emitted per feature that can produce one: .. code-block:: text bounce (always) ─┐ bounce-language LANGUAGE_BLOCKING ├──▶ dkim-sign ──▶ internet bounce-payment ANTI_SPAM ─┘ discard-rejected any MILTER_* (drops it; never bounces) Which stage routes to which is fixed by the same rendering pass: ``block-language`` bounces to ``bounce-language`` and ``anti-spam`` to ``bounce-payment``; every delivery and relay stage, plus ``dot-forward``, bounces to the plain ``bounce``. The Maildir stage additionally points ``UNKNOWN_MAILBOX_STAGE`` there — a recipient on one of *our* domains with no mailbox must be refused, not relayed, or the lookup would find our own MX and post the message back to ourselves — and both local methods point ``QUOTA_LIMIT_STAGE`` there as well, so a site that sets a quota later does not also have to discover that edge. ``discard-rejected`` is the exception, and deliberately so. A milter verdict arrives **after** Pepsi has accepted the message, so "reject" can no longer mean a 5xx in the SMTP session; bouncing instead would send backscatter to whoever a forged sender named. Milter-rejected mail is therefore dropped, with the verdict still recorded in ``state.milter`` and in the log. Pointing a filter's ``REJECT_STAGE`` at ``bounce`` is a one-word edit if you want senders told. The questions ============= Below, every question the terminal interview can ask, in the order it asks them, with the ``[pepsi-wizard]`` key its answer is remembered under. A step is shown only when the direction (and, for one step, the presence of a local Dovecot) makes it applicable; the ``Step X/Y`` banner counts only steps that will really be asked. Step: Server identity --------------------- Asked in every direction. ``HOSTNAME`` — *Mail server hostname (MX / EHLO name)* The identity of the **host**, as distinct from the domains it serves: the ARC sealing domain, every relay and bounce ``SERVER_NAME``, and the subject of the fallback TLS certificate. The default comes from **reverse DNS**, not from ``/etc/hostname``, because receivers compare the ``EHLO`` name against the ``PTR`` of the connecting address and reject when they disagree. If the offered default contradicts the ``PTR``, the wizard names both and says why before asking. ``DOMAINS`` — *Domain(s) we accept mail for (space separated)* Becomes ``[pepsi-ingress] ACCEPTED_DOMAINS``, and through it the default ``LOCAL_DOMAINS`` of the local-delivery stages, the set of ``mta-sts.*`` policy hosts, and the domains routed to Exchange. The first one is the *primary* domain: the SRS domain (``srs.``), the TLS-report addresses, the control addresses of the self-service stages and the ``SIGNING_DOMAIN`` of the forward-signing stage are all derived from it. ``POSTMASTER`` — *Postmaster address* Named by the bounce stages and the direct-to-MX relay. ``PUBLIC_IPS`` — *Public sending IP address(es) for SPF* Auto-detected and offered for confirmation, from the hostname's ``A``/``AAAA`` records unioned with this host's globally-routable interface addresses (and, on a NAT host with no public IPv4, the WAN address the local UPnP gateway reports). Only public addresses are ever pre-filled: these end up verbatim in the SPF record, so a private one there makes your own outbound mail fail SPF everywhere. A previous run's answer is re-checked rather than trusted, since a host may have moved since it was written. ``DB_CONFIG`` — *PostgreSQL connection string* libpq form; the default ``postgres:///pepsi`` uses peer authentication over the local socket, which is what the packaged service accounts expect. Step: Existing mail system -------------------------- Asked on the **inbound** path only, and asked *before* local delivery because the answer decides whether there is a local-delivery question at all. ``EXCHANGE`` — *Is there an existing Exchange / Microsoft 365 mail system behind this host* Default **no**. Answering yes makes this host a transparent gateway in front of Exchange: it becomes the MX, Exchange keeps the mailboxes. It inserts the ``route`` stage and a dedicated ``exchange`` relay into the received branch (see :ref:`wizard-pipeline-both`), and it **suppresses the local-delivery questions entirely** — offering to write mailboxes for domains Exchange owns would produce a configuration in which a recipient is delivered twice or not at all. Recipients Exchange does not own (an alias expanding off-site, a forward) still leave through the relay tail. See :doc:`exchange`. ``EXCHANGE_HOST`` — *Exchange host (tenant endpoint, or the on-premises server)* The **tenant-specific** endpoint, not your domain's public MX. That MX is this host, and pointing Exchange back at it makes every message loop. ``EXCHANGE_PORT`` — *Exchange SMTP port* Default ``25``. ``EXCHANGE_ADDRESS_FAMILY`` — *IP address family to reach Exchange over* ``any`` (default), ``ipv4`` or ``ipv6``. Exchange Online refuses mail from a sending IPv6 address with no ``PTR`` record, so pinning this one next hop to IPv4 is a common and legitimate answer; other destinations are unaffected. Step: Local delivery -------------------- Inbound path only, and the Maildir/LMTP questions are skipped when Exchange was answered yes. *Maildir* — *Deliver mail for local system accounts into their Maildir* Adds ``local`` to the received branch. Requires the SGID stage and setuid helper ``make install`` provisions (group ``pepsi-maildir``). ``DOT_FORWARD`` — *Honour per-user ~/.forward files for Maildir accounts* Default no; offered only alongside Maildir, since it is the system accounts' own home directories that are read. Adds ``dot-forward`` immediately before local delivery, so a recipient with a ``~/.forward`` is diverted before the mailbox write. The generated section forwards to **addresses only**; ``ALLOW_PIPE`` and ``ALLOW_FILE`` are left off, as each would run a command or write a file as the target user. *LMTP* — *Deliver mail locally via LMTP to an MDA (Dovecot, which runs Sieve)* Adds ``local-lmtp``. Pepsi implements no Sieve itself; this hands the message to an MDA that does. When a local Dovecot is detected, the follow-up question is just its LMTP socket path (``LMTP_SOCKET``); otherwise a remote TCP target is collected — ``LMTP_HOST``, ``LMTP_PORT``, ``LMTP_TLS``, ``LMTP_AUTH`` and, where the mechanism needs them, ``LMTP_USERNAME`` and ``LMTP_PASSWORD``. Pepsi does not configure Dovecot: if the sockets it will rely on are absent, a suggested ``10-pepsi.conf`` is printed at the end of the run. *Order* — *Both local methods are enabled — which is tried first?* Asked only when both were enabled. The methods are chained in the order given, each delivering what it can and forwarding the rest to the next; the last one's leftovers go to the relay tail. Stored in ``LOCAL_METHODS``. ``ALIASES_ENABLED`` — *Enable aliases / mailing lists (expand recipients via a map file)* Default no. Adds ``aliases`` after the spam gates and just before routing and delivery, so the gates see the address the sender wrote to while the expanded set is routed per recipient. The map file is created next to ``pepsi.conf`` and re-read automatically when its modification time changes. ``MILTERS_ENABLED`` — *Carry the milters over into the pipeline* Only asked when an MTA import found some. Note the change in timing it warns about: Pepsi runs filters **after** accepting the message, so one that used to reject in the session now produces a bounce (which is why detected filters are wired to ``discard-rejected`` instead — see :ref:`wizard-bounces`). ``MILTER_GREYLIST``, ``MILTER_REGEX``, ``MILTER_CLAMAV``, ``MILTER_SPAMASSASSIN``, ``MILTER_RSPAMD``, ``MILTER_MIMEDEFANG``, ``MILTER_AMAVIS`` One yes/no question per filter daemon a scan of *this host* found installed and answering. Each accepted filter becomes one ``milter-*`` stage, placed by what it does — policy first, then virus, then spam, then the general-purpose frameworks — with imported filters of unknown purpose keeping the relative order their old MTA ran them in. All default to yes **except** ``MILTER_GREYLIST``: greylisting works by refusing delivery in the session and betting a spam engine will not retry, and post-queue there is nothing to refuse and Pepsi is itself what retries, so the bet is always won and only the delay remains. Filters whose *function* Pepsi already performs (``opendkim``, ``openarc``, ``opendmarc``, SPF policy daemons, SRS rewriters) are recognised and reported as superseded rather than offered. See :doc:`programs/pepsi-stage-milter`. ``ALIAS_STYLE`` — *Qualify imported alias names* Only when a migration supplied bare alias names such as ``postmaster`` or ``root``, which Pepsi's keys cannot use because they carry a domain: ``per-domain`` (one entry per served domain), ``wildcard`` (a single ``@``-wildcard) or ``primary``. ``VACATION`` — *Answer mail for recipients who are away (out-of-office replies)* Default no. Adds ``vacation`` after alias expansion and after the spam gates, so a notice is never sent on behalf of an alias or in reply to mail the gates would have refused. Switching it on **answers nobody**: leave dates are ordinary per-address configuration, set later with ``pepsi-settings``, at domain scope with ``pepsi-config`` for a shared holiday, or by the users themselves by e-mail. On a host with no local delivery the generated section also sets ``VACATION_TAG = none``, because tagging the subject rewrites a header the author's DKIM signature and our own ARC seal both cover — harmless when the mail stops here, visible to the next hop when it is relayed on. *Smarthost* — *Also relay recipients local delivery does not handle to a smarthost* **Optional** when local delivery or Exchange handles your domains, in which case it only serves what neither did; **required, and not asked**, when neither does, since a pure relay must have somewhere to send. Saying yes collects ``SMARTHOST_HOST``, ``SMARTHOST_PORT``, ``SMARTHOST_MODE`` (``starttls`` / ``tls`` / ``plain``), ``SMARTHOST_AUTH`` (``none`` / ``plain`` / ``login``) and, where authenticated, ``SMARTHOST_USERNAME`` and ``SMARTHOST_PASSWORD``. **The credentials are checked on the spot** against the real relay: a refusal offers the password again, while an unreachable relay says nothing about the password and does not push you into retyping a working one. Store-and-hope is how a mistyped relay password becomes a silently dead outbound path. Step: Submission authentication ------------------------------- Asked only on the **outbound** path, and only when a Dovecot is installed on this host. ``DOVECOT_SASL`` — *Authenticate submissions against the local Dovecot SASL socket* Default yes. Sets mandatory ``AUTH`` against Dovecot's auth-client socket on the 465 and 587 listeners, so mail accounts and IMAP accounts are the same accounts. Declining leaves the listeners with a commented pointer to the alternatives (Dovecot SASL configured by hand, or TLS client certificates). Step: Trust loopback -------------------- Outbound path only, and it is a narrower question than it looks. ``TRUST_LOOPBACK`` — *Trust loopback connections on port 25* Default **no**, and the default is the right answer for nearly everyone. The wizard does *not* ask whether local programs may submit mail — they always can: ``pepsi-ingress`` unconditionally serves the UNIX socket that ``/usr/sbin/sendmail`` uses, and the kernel tells it which account opened it, so a caller can only ever send **as itself**. This question is only about the weaker additional mechanism of trusting an *address* — a ``MYNETWORKS`` entry for ``127.0.0.0/8 ::1/128`` on the port-25 listener — which authenticates a network position rather than a user, so any local process, including a compromised web application, can then send as anyone. It is also what lets a message this host relays to itself re-enter as a fresh submission and loop. Say yes only for software that insists on speaking SMTP to 127.0.0.1 and cannot be pointed at the socket. Step: Signing forwarded mail ---------------------------- Inbound path only. ``SIGN_RELAYED`` — *DKIM-sign mail this host forwards, as this host's own domain* Default **yes**. Mail this host merely forwards — an alias expanding off-site, a ``~/.forward`` at another provider, the whole pure-relay path — leaves down the received branch, which the outbound ``dkim-sign`` never touches; without this it arrives at the next hop with our SRS envelope but ``dkim=none`` from us. Answering yes inserts a second signing stage, ``dkim-sign-relay``, between ``srs`` and the destination, with ``SIGNING_DOMAIN`` pinned to the primary served domain. The pin is deliberate: signing is fail-closed and the author's domain belongs to somebody else, so a signing domain derived from a foreign ``From:`` would fail every forwarded message for want of a key. Step: Transport security ------------------------ Always asked; the first question only on the inbound path. ``MTA_STS`` — *Serve an MTA-STS policy over HTTPS* Default yes. Sets ``[pepsi] MTA_STS_MODE = enforce`` and emits ``pepsi-httpd`` with one ``mta-sts.`` certificate section per accepted domain, provisioned by ``pepsi-setup`` like any other. You must point each ``mta-sts.`` name's DNS at this host for the policy to be reachable. ``TLS_REPORTS`` — *Send and advertise SMTP TLS Reports (TLSRPT, RFC 8460)* Default yes; emits ``[pepsi-tlsrpt]``. Applies to any host that sends, so it is asked in every direction. Add the daily ``pepsi-tlsrpt report`` cron entry the wizard reminds you about. Declining both of these — and not enabling the paywall — produces a minimal host with **no HTTP server at all**: the ``pepsi-httpd`` sections are written only when something needs them. ``SHARE_TELEMETRY`` — *Share anonymous feature-usage telemetry with the Pepsi project* Default **no**, and it is opt-in in the strict sense: an operator who presses Enter through the entire interview shares nothing. The question explains what would be sent *before* asking, because a decision is only informed that way. Yes makes ``pepsi-setup run`` mint a random 256-bit ``SYSTEM_ID`` and arms the ``pepsi-telemetry-client`` daemon, which periodically reports which features this deployment has exercised and how often, under that identifier — no addresses, message data, host names or IP addresses. No means no identifier is generated and nothing is sent, and re-running the wizard to answer no stops the daemon rather than waiting for it to notice. Step: Content filtering ----------------------- Inbound path only. ``LANGUAGE_BLOCKING`` — *Reject mail by detected human language* Default no. Adds ``detect-language`` and ``block-language``. ``I18N_BOUNCES`` — *Localize bounce messages to the sender's language* Asked only when blocking is off, since blocking already implies detection. Adds ``detect-language`` alone, which annotates ``state.language`` and refuses nothing. ``DETECT_LANGUAGES`` — *Languages the detector should recognise* Asked when either of the above enabled detection, after printing the full list of supported ISO 639-1 codes. ``*`` (the default) means every language the detector supports; naming fewer costs less worker memory. An unsupported code is rejected at the prompt. Only a *detected* language can then be allowed or blocked, which is why this comes before the policy. ``LANG_POLICY`` — *Language policy (+allow / -block, e.g. '+en +fr -*')* One combined answer, split into the stage's ``WHITELIST`` and ``BLACKLIST``. A single ``-*`` or ``+*`` wildcard covers every other detected language, so ``+en +fr -*`` allows only English and French; the literal ``none`` may be listed to decide the fate of mail no language could be detected for. ``LANG_THRESHOLD`` — *Score threshold (mail must score strictly above this)* Default ``-0.1``. ``LANG_ENFORCEMENT`` — *Enforce the language policy strictly or weakly* ``hard`` (default) refuses mail the policy fails, routing it to ``bounce-language``. ``soft`` marks exactly the same mail instead — a ``[!LANG]`` subject flag and a ``Pepsi-Detected-Languages`` header — which is how the lists and the threshold get tuned against real traffic without anyone's mail being thrown away in the meantime. The bounce stage is written under ``soft`` too, so switching over later is a one-word edit rather than a hunt for what else it needs. Step: Anti-spam paywall ----------------------- Inbound path only. ``ANTI_SPAM`` — *Enable the GNU Taler anti-spam paywall (pay-to-send)* Default no. Adds **two** stages to the received branch — ``check-whitelist`` and ``anti-spam`` — and, when there is also an outbound path, ``auto-whitelist`` to the submitted one. The three are one feature: the outbound stage records everyone you write to, the inbound one recognises their replies, and only what neither vouched for meets the payment gate. It also emits the ``bounce-payment`` stage and gives ``pepsi-httpd`` a ``/resume`` webhook token. ``MERCHANT_BACKEND_URL`` — *Taler merchant backend URL* Asked first and **verified on the spot**, because its ``/config`` reports the currencies the price may be quoted in — so the backend has to be known before the price can be validated. ``MERCHANT_ACCESS_TOKEN`` — *Merchant access token ('secret-token:…') or the instance password* Either is accepted: a ready-made access token is verified against the backend, an instance password is exchanged for a scoped access token. ``PAYMENT_AMOUNT`` — *Price per message (space-separated Taler amounts)* Each amount becomes one choice in the generated order, so ``EUR:1 CHF:1 KUDOS:10`` lets the payer pick a currency. Validated against the currencies the backend advertised. ``PAYMENT_DEADLINE`` — *How long to hold a message awaiting payment* Default ``48 h``. Hours, minutes and seconds only — the duration parser does not accept calendar units. ``LEARN_WHITELIST`` — *Learn a correspondent whitelist from your outgoing mail* Asked **instead** of the above when the paywall is declined, and only when there is also an outbound path. It adds ``auto-whitelist`` on its own: the correspondents are recorded, and nothing yet consumes the verdict. That is a reasonable thing to switch on early, since a whitelist is only useful once it has some history in it. Step: Account self-service -------------------------- Outbound path only. Both default to no, and both are stages that pass ordinary mail straight through — they act only on a message a local account sent to their own control address. ``EDIT_SETTINGS`` — *Let account owners edit their own settings by e-mail* Adds ``edit-settings``. A message to ``pepsi@`` from a local account edits that address's own ``pepsi.settings`` overrides, restricted to an allowlist of stages the wizard derives from the inbound gates you actually enabled. See :doc:`programs/pepsi-stage-edit-settings`. ``WALLET_SELF_SERVICE`` — *Let account owners operate their GNU Taler wallet by e-mail* Adds ``wallet``. A message to ``pepsi-wallet@`` with a command in the ``Subject`` operates the sender's own wallet: balance, top-up, peer-to-peer payments. See :doc:`programs/pepsi-stage-auto-pay`. Step: End-to-end cryptography ----------------------------- Always asked — encryption serves what your users send and decryption what arrives for them, and a deployment that does one without the other is rare enough not to be worth two questions. Which halves are actually emitted still follows the direction. ``CRYPTO`` — *Encrypt and decrypt mail end-to-end (OpenPGP / S/MIME)* Default no. On the inbound path it adds ``decrypt`` **first**, so everything downstream sees plaintext; on the outbound path it adds ``encrypt`` **last** before signing, so DKIM covers what actually goes on the wire. Nothing is encrypted until a recipient's key is known — keys come from discovery (WKD, DANE, VKS, LDAP), from ``pepsi-keys``, or from the mail correspondents send you. The key store, the discovery daemon and the key-encryption key are provisioned by the rest of setup either way; this answer only decides whether the stages are in the pipeline. See :doc:`key-management`. ``AUTOCRYPT_LEARN`` — *Learn correspondents' keys from the mail they send us* Default yes, asked only when ``CRYPTO`` is on and there is an inbound path — without a decrypt stage there is no plaintext to read gossip out of, and without an encrypt stage nothing would ever spend a learnt key. Adds ``autocrypt-learn`` **after** every stage that can form a spam opinion and before anything that answers or delivers, which is exactly what Autocrypt Level 1 requires: a message believed to be spam teaches nothing, and a key learnt after delivery is learnt too late to encrypt the reply with. Learnt keys sit on the lowest rungs of the trust ladder — such a key can never displace one from discovery, and a signature checked against one is never reported as verified. Step: Expert options -------------------- Shown only when ``--expert`` named some. These are real Pepsi options the interview does not normally ask about — ``MAX_MESSAGE_SIZE``, ``MYNETWORKS``, ``RECIPIENT_DELIMITER``, ``MAX_LIFETIME``, ``DMARC_ENFORCE``, ``HELO_NAME``, ``TLS_CERT``/``TLS_KEY``, ``DNS_TIMEOUT``, ``MAX_CONNECTIONS``, ``MAX_OPEN_SOCKETS``, ``DKIM_SELECTOR``, ``KEY_DIR``, ``MAILBOX_QUOTA``, ``MAILBOX_OVER_QUOTA``, ``CRYPTO_ALLOW_DOWNGRADE`` — each of which knows which section it belongs in. A value already carried in the defaults (from a previous run, or from an MTA import) is applied whether or not ``--expert`` asks about it; the flag only decides which ones get a prompt. Leaving an answer empty restores Pepsi's own default. See :manpage:`pepsi-setup(1)`. What the wizard does not ask ============================ Some things are absent from the interview on purpose, and knowing which saves looking for them: * **The local submission socket.** ``pepsi-ingress`` serves it unconditionally, deriving the path from ``[pepsi-sendmail] SOCKET``, so no listener section is written and there is nothing that can disagree with anything else. It used to be a question, and the only symptom of getting it wrong was that cron mail stopped. * **SRS.** The rewrite is wired automatically wherever mail can be forwarded, with a generated HMAC secret. Nothing about it is a choice. * **How the listeners bind.** A host running systemd (detected by ``/run/systemd/system``) gets socket-activated listeners (``SERVE = systemd``, one ``FD_INDEX`` per listener); anything else gets direct binds. With socket activation you must install matching ``.socket`` units whose ``ListenStream=`` order lines up with the ``FD_INDEX`` values — the wizard prints a reminder, and ``pepsi-ingress`` warns at start-up about any descriptor no section claimed. * **Keys, certificates, the schema and DNS.** All of that is ``pepsi-setup run``, which the wizard offers to chain into. TLS certificates are provisioned via certbot; a certificate that cannot be obtained defers rather than aborting the run, so the schema, keys and DNS output still happen. Re-running it ============= The interview records every non-secret answer in a ``[pepsi-wizard]`` section at the end of the file it writes, and a later ``--wizard`` run imports those as its defaults — so re-running to change one thing is a press-Enter walk down to that question. Secrets are handled separately and are never written there: a generated one (the SRS key, the ``/resume`` token) is read back and **kept**, because rotating the SRS key would invalidate every rewritten sender still in flight; a typed one (a smarthost or LMTP password, the merchant token) is offered back at its own prompt as ``***``, so pressing Enter keeps it without it ever being echoed or re-typed. Two non-interactive forms exist for the same interview. ``--answers FILE`` supplies answers by key from a JSON file, short-circuiting every prompt (a missing or malformed answer is a hard failure rather than a prompt nobody is there to see), and is how a configuration can be reproduced across hosts. ``--import`` names an MTA to migrate from explicitly, rather than detecting one. Both are documented in :manpage:`pepsi-setup(1)`.