2. Getting started on a cheap VPS

This chapter is a single, end-to-end walkthrough: starting from a freshly rented virtual private server and a domain name you have just registered, it takes you all the way to a first e-mail received and delivered into a local mailbox — and a proper bounce sent back for anything that cannot be delivered. Every other chapter explains one piece in depth; this one strings the pieces into one concrete story you can follow top to bottom.

2.1. What we will build

A small mail host that accepts mail for example.org on the standard SMTP port and delivers each message into the recipient’s local Maildir on the same server. Anything it cannot deliver locally — an unknown recipient, a mailbox that is full or unwritable — is turned into a real bounce (a delivery-status notification) and sent back to the original sender, rather than silently disappearing into the queue. The inbound pipeline is short:

inbound SMTP  ->  [stage-init]  ->  [stage-local]
(pepsi-ingress)   pepsi-stage-arc   pepsi-stage-relay-to-maildir
                  (record the       (deliver to the user's
                   inbound verdict)  ~/Maildir/new/)

and whatever it cannot deliver is signed and mailed back to the sender over a short outbound path:

[stage-bounce]      ->  [stage-sign]       ->  [stage-internet]
pepsi-stage-bounce      pepsi-stage-dkim-sign   pepsi-stage-relay-to-internet
(build the DSN)         (sign the bounce)       (send it to the sender's MX)

Replace example.org with your own domain and alice with a real account name throughout. Because the host now also sends that bounce — DKIM-signed, from an SPF-authorised address — this setup publishes the sender-authentication records that let receivers trust its outgoing mail: DKIM, SPF and DMARC, which a receive-only host would not need. (MTA-STS and TLS reporting, which harden inbound TLS and need the HTTPS server running, are left off here and added as optional hardening — see Next steps.) A final section reuses exactly the same outbound path to add an authenticated submission port so your own users can send through the host too.

Note

Scope. This is the simplest realistic deployment — receiving mail, delivering it locally, and bouncing what it cannot. It is not the forwarding showcase Pepsi is built for (ARC + SRS + DKIM re-signing on the way back out); see Introduction and pepsi-stage-relay-to-internet for that, and Next steps for how to grow this host into one.

Pepsi delivers into a Maildir but ships no IMAP/POP server, so once mail has landed you read it on the box itself (mutt, mail) until you add a mailbox server such as Dovecot. See Next steps.

2.2. Prerequisites

  • A VPS with a public, static IPv4 address (and ideally an IPv6 address) and root access. We use 203.0.113.7 / 2001:db8::25 as placeholders.

  • A registered domain whose DNS you can edit (example.org).

  • TCP port 25 must reach the VPS both ways. Inbound port 25 carries the mail you receive; outbound port 25 lets the host send bounces back to senders. Many budget providers block outbound 25 by default — if inbound mail never arrives, or bounces never leave the queue, that is the usual cause; ask the provider to open it. (If outbound 25 cannot be unblocked, send the bounces through a smarthost instead — see pepsi-stage-relay-to-smarthost.)

  • Reverse DNS (PTR). Set the VPS’s PTR record to mail.example.org — this is done in the VPS provider’s control panel, not in your domain’s zone. It must name the same host you announce in EHLO: many receivers compare the two and refuse the session when they differ (HELO host does not match rDNS), which is easy to trip on a machine that answers to several names. pepsi-setup run warns about every sending address whose PTR is missing, unconfirmed or names a different host, and pepsi-setup --wizard proposes the PTR name as the hostname for exactly this reason.

  • The build prerequisites from Installation: a Rust toolchain, PostgreSQL, and (for automatic TLS) certbot. Install them with your platform’s package manager.

2.3. Step 1 — Delegate DNS and place the base records

Pepsi contains no DNS server; you publish records wherever your domain’s zone is authoritative. You have two choices, and “delegation” means different things for each:

  • Host the zone at a DNS provider (your registrar, or a service such as a managed DNS host). Here the registrar already delegates example.org to that provider’s name servers; you only add records in their panel. This is the simplest path.

  • Run your own authoritative name server (BIND, NSD, Knot, …) on the VPS or elsewhere. Then at the registrar you set example.org’s NS records to your name servers and, if a name server is itself inside example.org, add the matching glue A/AAAA records at the registrar so the delegation can be resolved:

    ; at the registrar (delegation + glue)
    example.org.        NS    ns1.example.org.
    ns1.example.org.    A     203.0.113.7
    

Either way, once the zone is under your control, add the two records the mail host needs immediately:

; the mail host's address
mail.example.org.   A     203.0.113.7
mail.example.org.   AAAA  2001:db8::25

; mail for the domain goes to that host
example.org.        MX    10 mail.example.org.

Note

The remaining records — SPF, DKIM and DMARC — are added in Step 6 — Publish the remaining records and verify after pepsi-setup has generated the DKIM key and printed the exact text to publish. (MTA-STS and TLS-reporting records are only added if you enable those optional features — see Next steps.) Add the A/AAAA record now, though: the automatic TLS certificate in Step 5 requires mail.example.org to already resolve to this VPS.

2.4. Step 2 — Install Pepsi

Build and install from a source checkout (there is no binary package yet). Fetch the vendored submodule first, then install binaries, SQL and the sample config in one step:

git submodule update --init --recursive
make install PREFIX=/usr SYSCONFDIR=/etc

This installs the pepsi-* binaries, the SQL migrations and a sample /etc/pepsi/pepsi.conf. See Installation for what each step does and for the database-schema lifecycle; run make install as root so the local-delivery helper can be installed with its privileges (next step).

2.5. Step 3 — Prepare the system

Local delivery writes into other users’ home directories, so Pepsi separates privileges with a dedicated service user and a tightly scoped setuid helper. Create the accounts (names are conventional; use your platform’s user/group tools):

  • a pepsi service user the long-running daemons run as, and

  • a pepsi-maildir group that gates the delivery helper.

The delivery path has two privilege bits, which make install sets automatically when run as root and the pepsi-maildir group already exists; otherwise set them by hand:

See those two chapters for the rationale.

Create the database. Pepsi connects over the local socket as the connecting system user (peer authentication), so create a pepsi role and a database it owns to match CONFIG = postgres:///pepsi below:

createuser pepsi
createdb -O pepsi pepsi

Finally, create at least one local mailbox account to receive mail:

useradd --create-home alice

2.6. Step 4 — Write the configuration

All components read one file, /etc/pepsi/pepsi.conf. The sample installed in Step 2 is a full-featured forwarder; for this walkthrough you want the minimal local-delivery-plus-bounce configuration instead. There are two equivalent ways to produce it — let pepsi-setup generate it from a few answers, or write it by hand — and they build the same pipeline. (The format and every option are described in Configuration and pepsi.conf(5).)

pepsi-setup --wizard runs a short interview, writes a complete and already-validated /etc/pepsi/pepsi.conf, and then offers to run the provisioning of Step 5 for you. Start it:

pepsi-setup --wizard

Answer the prompts as follows. Press Enter to accept a [default]; the prompts not listed here can all keep their defaults (they are no for this minimal host):

  • Mail direction this host servesinbound

  • Mail server hostname (MX / EHLO name)mail.example.org. The default offered here is the name this host’s public address reverse-resolves to; if that is not mail.example.org, fix the PTR record before going further rather than overriding it here.

  • Domain(s) we accept mail forexample.org

  • Postmaster address — accept the default (postmaster@example.org)

  • Public sending IP address(es) for SPF203.0.113.7 2001:db8::25

  • PostgreSQL connection string — accept the default (postgres:///pepsi)

  • Deliver mail for local system accounts into their Maildiryes

  • Also relay non-local recipients to an upstream smarthostno

  • Serve an MTA-STS policy over HTTPSno (optional hardening, added later; see Next steps)

  • Send and advertise SMTP TLS Reports (TLSRPT)no

The wizard validates the result, writes the file, and asks whether to run the full setup now: answer yes to fold Step 5 into this step, or no to run pepsi-setup run yourself afterwards. Your answers are recorded in a [pepsi-wizard] section, so re-running the wizard pre-fills them.

The file it writes is the same pipeline as the hand-written one in the other tab, with a few extra production-sensible defaults (DANE = warn, message lifetimes). It also delivers to any regular login account; add TARGETS = alice to [stage-local] to restrict delivery to named accounts.

The same interview without a terminal. pepsi-setup questions prints it as JSON — every question’s identifier, shape, default and help text — and pepsi-setup --wizard --answers answers.json answers it from a file of those identifiers. It is the interactive interview with the prompts supplied, not a second implementation, so the branches and the per-field validation are the ones above. That is also the model the browser setup renders; see the “Setting up in a browser” section of Installation.

Replace /etc/pepsi/pepsi.conf with the following:

[pepsi]
# Where pepsi-setup writes the generated DKIM/ARC keys.
KEY_DIR = /var/pepsi/keys
DKIM_SELECTOR = pepsi
# Identity that signs the ARC seal recording the inbound verdict.
ARC_DOMAIN = example.org
# This minimal host does not serve an MTA-STS policy (that needs the
# HTTPS server), so do not advertise one. See "Next steps" to add it.
MTA_STS_MODE = none

[pepsi-postgres]
CONFIG = postgres:///pepsi

[pepsi-ingress]
HOSTNAME = mail.example.org
ACCEPTED_DOMAINS = example.org

# The MX listener on port 25. MODE = starttls with no TLS_CERT/TLS_KEY
# lets pepsi-setup fill in the certbot certificate paths (Step 5).
[pepsi-ingress-listener-mx]
SERVE = tcp
BIND_TO = 0.0.0.0
PORT = 25
MODE = starttls

# New messages enter here. ARC-seal the SPF/DKIM/DMARC verdict Pepsi
# computed at the boundary, then hand the message to local delivery.
[stage-init]
PROGRAM = pepsi-stage-arc
NEXT_STAGE = local

# Deliver to a local user's Maildir. By default any regular login account
# (the /etc/login.defs UID range) may receive mail, so 'alice' is already
# covered; set TARGETS to restrict delivery to an allow-list (e.g.
# TARGETS = alice). Anything not deliverable here is sent to the bounce
# path: an unknown recipient via NEXT_STAGE, a mailbox that fails to
# accept it via BOUNCE_STAGE.
[stage-local]
PROGRAM = pepsi-stage-relay-to-maildir
SERVER_NAME = mail.example.org
NEXT_STAGE = bounce
BOUNCE_STAGE = bounce

# --- Outbound bounce path: turn an undeliverable message into a DSN and
# mail it back to the original sender. Three short stages: build, sign,
# send.

# Rewrite the message in place into an (unsigned) RFC 3464 bounce
# addressed to the original sender, then hand it to the signer.
[stage-bounce]
PROGRAM = pepsi-stage-bounce
SERVER_NAME = mail.example.org
NEXT_STAGE = sign

# DKIM-sign the bounce so it authenticates at the sender's server.
[stage-sign]
PROGRAM = pepsi-stage-dkim-sign
NEXT_STAGE = internet

# Deliver the signed bounce directly to the sender's mail exchanger.
[stage-internet]
PROGRAM = pepsi-stage-relay-to-internet
SERVER_NAME = mail.example.org

# Read by pepsi-setup to build the SPF record authorising this host to
# send (the bounces above, and any outbound mail you add later).
[pepsi-stage-relay-to-smarthost]
PUBLIC_IP = 203.0.113.7 2001:db8::25

A message for a recipient that is not a permitted local mailbox is turned into a bounce and mailed back to the sender, rather than silently failing: an unknown @example.org recipient takes [stage-local]’s NEXT_STAGE to the bounce path, and a known mailbox that cannot be written (full disk, permissions) is retried and then bounced via BOUNCE_STAGE. Both point at the same [stage-bounce].

Note that NEXT_STAGE here goes to the bounce stage, not to [stage-internet]. This host serves only local mailboxes, so every recipient it cannot deliver is an unknown user of our own domain, and relaying those onward would look up example.org’s MX, find this very host, and post the mail straight back to us — a loop. Turning this host into a real forwarder — where an alias may legitimately expand to an address elsewhere — is a separate step (Next steps); there NEXT_STAGE does reach a relay, and UNKNOWN_MAILBOX_STAGE keeps unknown local users on the bounce path (see pepsi-stage-relay-to-maildir).

2.7. Step 5 — Provision the schema, keys and certificate

If you used the wizard in Step 4 and answered yes to “run setup now”, this is already done — re-run the command below at any time to capture the DNS records again (it is idempotent). Otherwise, run the bootstrap tool once, capturing the DNS records it prints:

pepsi-setup -c /etc/pepsi/pepsi.conf run > pepsi-dns.zone

This validates the configuration, installs the pepsi schema, generates the per-domain DKIM/ARC keys under KEY_DIR, obtains the TLS certificate for mail.example.org via certbot (this needs the A record from Step 1 to be live and inbound port 80 reachable), and prints the remaining DNS records to pepsi-dns.zone. It is idempotent and safe to re-run.

If you would rather manage the certificate yourself, pass --no-certbot and set TLS_CERT/TLS_KEY in the listener section. The full provisioning workflow is in pepsi-setup and Installation.

2.8. Step 6 — Publish the remaining records and verify

Open pepsi-dns.zone and publish the records it lists in your zone:

  • the DKIM public key (pepsi._domainkey.example.org TXT),

  • the SPF policy listing this host’s sending addresses, and

  • a DMARC policy (_dmarc.example.org TXT).

(Only these are printed for the minimal host; an MTA-STS or TLS-reporting record appears here too once you enable those features — see Next steps.)

The suggested DMARC policy is v=DMARC1; p=none, which asks receivers to report on mail that fails authentication without acting on it. That is the right place to start and the wrong place to stay: add rua=mailto:<address> so the aggregate reports reach you, and once they show your own mail passing, tighten p= to quarantine and then reject. Publish it by hand, carefully — a DMARC record that does not parse is discarded in silence, and a domain whose policy is one missing ; away from valid looks protected and is not. That is precisely what the check below is for.

Two more blocks appear once this host does end-to-end cryptography, and both are about letting correspondents find your users’ keys (Key management):

  • an openpgpkey.example.org A/AAAA record pointing at this host, needed by the Web Key Directory’s advanced method — the one every current client tries first. pepsi-setup also asks certbot to cover that name, so publish the record before the next certificate issuance. The direct method on the apex works without it, so a missing record is a degradation rather than a breakage;

  • an OPENPGPKEY (RFC 7929) or SMIMEA (RFC 8162) record per published identity. Publish these only if your zone is DNSSEC-signed: an unsigned key record is a key from whoever can answer for the zone, which is no improvement on having no key at all. The Web Key Directory needs no DNSSEC, because HTTPS authenticates the domain itself.

Once DNS has propagated, check what is live against what Pepsi expects:

pepsi-setup -c /etc/pepsi/pepsi.conf check

This is informational (it always exits 0); it flags a missing or mismatched DKIM or SPF record (and MTA-STS, once enabled) so you can fix the zone before relying on it. It also validates the DMARC record strictly and reports one that receivers would ignore as [INVALID], naming the offending tag — the one failure in this list that produces no bounce, no log line and no complaint from anyone.

2.9. Step 7 — Start the services

Two long-lived processes run continuously, both as the pepsi user:

  • pepsi-ingress serve — the inbound SMTP server, and

  • pepsi-dispatch serve — the stage coordinator (exactly one per host).

Run each under your platform’s service/process manager so they start at boot and restart on failure. The stage programs themselves are spawned by the dispatcher; you never start them by hand. Binding port 25 needs privilege — grant the ingress binary the capability to bind low ports, or use socket activation — see Installation and pepsi-ingress.

2.10. Step 8 — Send the first e-mail

From an account on another provider, send a message to alice@example.org (or use a tool such as swaks --to alice@example.org --server mail.example.org). Watch it move through the pipeline:

pepsi-status -c /etc/pepsi/pepsi.conf

A delivered message is removed from the queue, so a healthy run shows an empty backlog and an incremented delivery counter. Confirm the mail actually landed:

ls /home/alice/Maildir/new/
mutt -f /home/alice/Maildir          # or: mail

If the file is there, you have received your first e-mail.

2.11. Step 9 — Open the console (optional)

Everything above is visible in a browser as well. Add an administrative listener bound to loopback:

[pepsi-httpd-listener-admin]
SERVE = tcp
BIND_TO = 127.0.0.1
PORT = 8443
MODE = plain
ADMIN = yes

and an account to sign in with — over the local socket a member of the pepsi-admin group needs none, but a browser cannot open a UNIX socket, so create one and reach the listener through an SSH tunnel:

ssh -L 8443:127.0.0.1:8443 root@mail.example.org

Then open http://127.0.0.1:8443/ui. The dashboard shows the same queue pepsi-status printed, the configuration pages show where each value came from, and the audit log records every change made from any surface. See The administration console for the page reference and The administrative API for the API it is a client of. The console performs no service control: restarting a component is still systemctl’s job.

2.12. Sending mail too: add a submission port

So far the host only receives. To let your own users send mail through it from a mail client (Thunderbird, Evolution, KMail, or K-9 Mail on a phone), add an authenticated submission listener and an outbound delivery path. A client authenticates with a username and password; pepsi-ingress then records the message with state.local_origin = true, which both allows it to relay to any domain (not only example.org) and lets the pipeline tell it apart from inbound mail.

Open inbound TCP ports 465 and/or 587 on the VPS firewall, the same way you opened port 25.

2.12.1. Route inbound and outbound apart

You already built an outbound delivery path for the bounces — [stage-sign][stage-internet], with the SPF record covering this host. Authenticated submissions reuse exactly that path; you only need to route them onto it.

Outbound mail must take a different path from inbound mail, and getting this wrong causes a mail loop: if you simply pointed [stage-init] at [stage-sign], inbound mail would be signed and relayed back out instead of delivered locally. Instead, branch on state.local_origin at the entry point with a pepsi-stage-if stage — locally-originated (authenticated) mail goes to the outbound path, everything else to the inbound path. Crucially, the split must come before pepsi-stage-arc: ARC preserves an upstream sender’s authentication across your forwarding hop, so it is meaningless for — and must never touch — mail your own users originate (that mail is authenticated by [stage-sign] instead; ARC-sealing it would only publish the submission’s own SPF fail). So make the router the [stage-init] entry stage and move ARC onto the inbound branch:

# Entry: split by direction FIRST. Authenticated submissions (local_origin =
# true) are outbound and reuse the signing path; everything else is inbound and
# goes through ARC. This only moves the stage; it never changes the message.
[stage-init]
PROGRAM = pepsi-stage-if
STATE_PATH = local_origin
VALUE = true
TRUE_STAGE = sign       # outbound: our users' mail (reuses the bounce path)
FALSE_STAGE = arc       # inbound: ARC-seal, then deliver locally

# ARC now sits on the inbound branch (received mail only), feeding local
# delivery just as [stage-init] used to.
[stage-arc]
PROGRAM = pepsi-stage-arc
NEXT_STAGE = local

[stage-sign], [stage-internet] and the PUBLIC_IP line are already in your configuration from the bounce path, so submissions share them unchanged. [stage-arc] is the former [stage-init] body (unchanged but renamed), and [stage-local] is unchanged too — its NEXT_STAGE/BOUNCE_STAGE still bounce mail to unknown local users rather than relaying it anywhere. (The ARC stage also skips any state.local_origin message on its own, so a misplacement cannot seal a submission — but keeping it off the outbound branch avoids the wasted hop.)

Note

The outbound path needs outbound port 25 (see Prerequisites), which budget providers often block. If yours is blocked, deliver through an upstream smarthost instead: replace [stage-internet] with a pepsi-stage-relay-to-smarthost stage and define the smarthost — see pepsi-stage-relay-to-smarthost. To return non-delivery reports when an MX refuses your users’ outgoing mail, give [stage-internet] a BOUNCE_STAGE = bounce as well.

2.12.2. Provide a password backend (Dovecot)

A submission listener needs a way to check passwords. Pepsi keeps no password database of its own; it verifies credentials through a Dovecot SASL socket. Install Dovecot and configure it to authenticate your accounts (the system users you created, or virtual users). Pepsi’s inbound server runs as the unprivileged pepsi-ingress user, which cannot open Dovecot’s shared, root-only auth-client socket, so give it a dedicated listener it owns — in /etc/dovecot/conf.d/10-pepsi.conf:

service auth {
  unix_listener auth-client-pepsi {
    mode = 0600
    user = pepsi-ingress
  }
}

You do not have to write this by hand: pepsi-setup run detects an unreachable socket and offers to install exactly this drop-in (validating it with doveconf and reloading Dovecot), or prints it if it cannot.

Dovecot is also what you would add to let clients read their Maildirs over IMAP (Next steps), so it earns its keep twice. Its full configuration is beyond this guide; see the Dovecot documentation.

2.12.3. Add the submission listeners

Add an implicit-TLS listener on 465 (recommended for clients) and/or a STARTTLS listener on 587. SUBMISSION = yes makes authentication mandatory and applies the RFC 6409 fixups (a missing Date:/Message-ID: is added); SASL_TYPE/SASL_PATH wire in Dovecot. Authentication is only ever offered over TLS. As with the MX listener, leaving TLS_CERT/TLS_KEY unset lets pepsi-setup install the mail.example.org certificate automatically:

# Port 465: implicit TLS.
[pepsi-ingress-listener-submissions]
SERVE = tcp
BIND_TO = 0.0.0.0
PORT = 465
MODE = tls
SUBMISSION = yes
SASL_TYPE = dovecot
SASL_PATH = /run/dovecot/auth-client-pepsi

# Port 587: cleartext upgraded with STARTTLS.
[pepsi-ingress-listener-submission]
SERVE = tcp
BIND_TO = 0.0.0.0
PORT = 587
MODE = starttls
SUBMISSION = yes
SASL_TYPE = dovecot
SASL_PATH = /run/dovecot/auth-client-pepsi

Re-run the bootstrap so the new listeners’ certificates and the updated SPF record are picked up, then restart the services:

pepsi-setup -c /etc/pepsi/pepsi.conf run > pepsi-dns.zone

Publish the updated records (the SPF record now lists this host) and re-verify with pepsi-setup -c /etc/pepsi/pepsi.conf check.

2.12.4. Connect a mail client

In the client’s account settings, configure the outgoing (SMTP) server:

  • Server / host: mail.example.org

  • Port / security: 465 with implicit TLS (SSL/TLS), or 587 with STARTTLS

  • Authentication: normal password

  • Username / password: the account Dovecot expects (e.g. alice or alice@example.org) and its password

If you also set up Dovecot for IMAP, point the incoming server at the same host (IMAP, port 993, TLS). Send a test message to an address at another provider, confirm it left the queue:

pepsi-status -c /etc/pepsi/pepsi.conf

and check that it arrived. Outgoing deliverability depends on the same DNS hygiene as receiving — correct PTR/reverse DNS, the SPF record listing this host, and DKIM signing (all configured above); a brand-new IP is sometimes greylisted on first contact, so a short delay on the very first message is normal.

2.13. Troubleshooting

Nothing arrives

Check the MX record resolves to mail.example.org and that record’s A/AAAA points at the VPS; confirm inbound port 25 is open (test from another host); confirm the PTR/reverse-DNS is set. Watch the ingress log.

A message is stuck or failed

Inspect it with pepsi-status and pepsi-queue. A recipient that is not in TARGETS or has no account is now bounced back to the sender (watch [stage-bounce] and [stage-internet]), not left in the queue; a timeout/paused row often means a filesystem problem (e.g. a full disk) in the recipient’s home, retried before it bounces. A bounce that itself sticks usually means outbound port 25 is blocked (see below).

The certificate step failed

certbot needs mail.example.org to already resolve to this VPS and inbound port 80 reachable. Fix DNS/port 80 and re-run pepsi-setup run, or use --no-certbot and supply TLS_CERT/TLS_KEY yourself.

“Permission denied” on delivery

The privilege bits from Step 3 are wrong: the helper must be 4750 root:pepsi-maildir and the relay-to-maildir stage 2755 setgid pepsi-maildir. Re-run make install as root with the group present, or set them by hand.

A client cannot authenticate or send

Authentication is offered only over TLS — use port 465 (implicit TLS) or 587 with STARTTLS and “normal password”. A 530 reply means the session was unauthenticated; check that the Dovecot auth socket (SASL_PATH) is readable by the pepsi user and that Dovecot accepts the credentials.

Outgoing mail is stuck

Direct internet delivery needs outbound port 25; if the provider blocks it, switch [stage-internet] to a smarthost (see the note above). Inspect stuck rows with pepsi-status and pepsi-queue.

2.14. Next steps

  • Read mail remotely. Add an IMAP server (e.g. Dovecot) pointed at the same ~/Maildir so mail clients can fetch it; Pepsi handles only delivery into the Maildir. If you already added Dovecot for submission authentication above, enabling IMAP is a small additional step.

  • Turn this into a forwarder. Insert pepsi-stage-srs on the inbound path so mail re-sent to a third party passes SPF, re-using the outbound relay ([stage-sign][stage-internet]) already in this configuration — Pepsi’s core purpose (Introduction).

  • Customise the bounces. The bounce path is already wired in; you can localise or brand the DSN text with a BOUNCE_MESSAGE template — see pepsi-stage-bounce.

  • Harden inbound TLS (MTA-STS + TLS reporting). Tell other senders to use validated TLS toward your MX by serving an MTA-STS policy, and collect TLS Reports (RFC 8460). Both need the HTTPS server (pepsi-httpd) running and a certificate for each mta-sts.<domain>; the easiest way to add them is to re-run pepsi-setup --wizard and answer yes to the two transport-security questions (it pre-fills your earlier answers).

  • Filter and protect. Layer in language blocking, sender whitelisting or the pay-to-send paywall — see Supported Features and the per-stage chapters.

  • Front an existing Exchange or Microsoft 365 deployment. Pepsi can sit in front of and behind Exchange as a transparent crypto gateway, so users get S/MIME and OpenPGP without touching their clients. That is a different topology from the one above — mail is routed to Exchange rather than delivered locally, which makes loop prevention mandatory rather than advisable — and it has its own chapter: Microsoft Exchange as a gateway.