5. 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, 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 pepsi-setup(1), which this chapter cross-references rather than repeats.

5.1. How a run proceeds

  1. 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.

  2. 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.

  3. Render. The answers are turned into a configuration file — this is where the pipeline is assembled.

  4. 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.

  5. 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.

  6. 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 The administration console), described next.

5.2. 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.

The Server identity step of the browser setup console

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:

The Content filtering step, with the language-policy fields revealed

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.

5.3. 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.

5.3.1. 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.

                 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:

(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.

5.3.2. 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.

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 The shared tail and the bounces), and internet is also the relay destination when no smarthost was configured.

5.3.3. 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.

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.

5.3.4. 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:

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.

5.4. 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.

5.4.1. Step: Server identity

Asked in every direction.

HOSTNAMEMail 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.

DOMAINSDomain(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.<primary>), 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.

POSTMASTERPostmaster address

Named by the bounce stages and the direct-to-MX relay.

PUBLIC_IPSPublic 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_CONFIGPostgreSQL connection string

libpq form; the default postgres:///pepsi uses peer authentication over the local socket, which is what the packaged service accounts expect.

5.4.2. 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.

EXCHANGEIs 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 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 Microsoft Exchange as a gateway.

EXCHANGE_HOSTExchange 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_PORTExchange SMTP port

Default 25.

EXCHANGE_ADDRESS_FAMILYIP 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.

5.4.3. Step: Local delivery

Inbound path only, and the Maildir/LMTP questions are skipped when Exchange was answered yes.

MaildirDeliver 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_FORWARDHonour 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.

LMTPDeliver 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.

OrderBoth 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_ENABLEDEnable 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_ENABLEDCarry 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 The shared tail and the 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 pepsi-stage-milter.

ALIAS_STYLEQualify 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.

VACATIONAnswer 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.

SmarthostAlso 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.

5.4.4. Step: Submission authentication

Asked only on the outbound path, and only when a Dovecot is installed on this host.

DOVECOT_SASLAuthenticate 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).

5.4.5. Step: Trust loopback

Outbound path only, and it is a narrower question than it looks.

TRUST_LOOPBACKTrust 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.

5.4.6. Step: Signing forwarded mail

Inbound path only.

SIGN_RELAYEDDKIM-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.

5.4.7. Step: Transport security

Always asked; the first question only on the inbound path.

MTA_STSServe an MTA-STS policy over HTTPS

Default yes. Sets [pepsi] MTA_STS_MODE = enforce and emits pepsi-httpd with one mta-sts.<domain> certificate section per accepted domain, provisioned by pepsi-setup like any other. You must point each mta-sts.<domain> name’s DNS at this host for the policy to be reachable.

TLS_REPORTSSend 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_TELEMETRYShare 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.

5.4.8. Step: Content filtering

Inbound path only.

LANGUAGE_BLOCKINGReject mail by detected human language

Default no. Adds detect-language and block-language.

I18N_BOUNCESLocalize 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_LANGUAGESLanguages 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_POLICYLanguage 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_THRESHOLDScore threshold (mail must score strictly above this)

Default -0.1.

LANG_ENFORCEMENTEnforce 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.

5.4.9. Step: Anti-spam paywall

Inbound path only.

ANTI_SPAMEnable 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_URLTaler 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_TOKENMerchant 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_AMOUNTPrice 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_DEADLINEHow long to hold a message awaiting payment

Default 48 h. Hours, minutes and seconds only — the duration parser does not accept calendar units.

LEARN_WHITELISTLearn 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.

5.4.10. 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_SETTINGSLet account owners edit their own settings by e-mail

Adds edit-settings. A message to pepsi@<domain> 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 pepsi-stage-edit-settings.

WALLET_SELF_SERVICELet account owners operate their GNU Taler wallet by e-mail

Adds wallet. A message to pepsi-wallet@<domain> with a command in the Subject operates the sender’s own wallet: balance, top-up, peer-to-peer payments. See pepsi-stage-auto-pay.

5.4.11. 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.

CRYPTOEncrypt 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 Key management.

AUTOCRYPT_LEARNLearn 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.

5.4.12. 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 pepsi-setup(1).

5.5. 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.

5.6. 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 pepsi-setup(1).