85.1.49. pepsi-tlsrpt

SMTP TLS Reporting (RFC 8460) daily reports

Manual section:

1

85.1.49.1.1. Name

pepsi-tlsrpt - daily SMTP TLS Reporting (RFC 8460) aggregate reports.

85.1.49.1.2. Synopsis

pepsi-tlsrpt [GLOBAL-OPTIONS] report [–date YYYY-MM-DD] [–dry-run]

pepsi-tlsrpt [GLOBAL-OPTIONS] prune [–older-than DAYS]

85.1.49.1.3. Description

pepsi-tlsrpt is the sender side of SMTP TLS Reporting (RFC 8460). As Pepsi delivers outbound mail, the relay stages (pepsi-stage-relay-to-internet(1) and pepsi-stage-relay-to-smarthost(1)) record the outcome of each TLS session — a success, or a classified failure (starttls-not-supported, certificate-host-mismatch, certificate-expired, certificate-not-trusted, validation-failure, sts-policy-invalid) — as an aggregate counter in the pepsi.tls_session table, keyed by UTC day, policy domain, applied policy type (tlsa/sts/no-policy-found), MX host and result. A session is a success when TLS was negotiated, whatever the SMTP exchange over it then returned (an SMTP rejection after the handshake is not a TLS failure). A message delivered in cleartext is not a TLS session: with no policy (a MODE = plain smarthost, or an MX that offers no STARTTLS) it is not recorded, and where MTA-STS or DANE required TLS it is recorded as starttls-not-supported. A failure that does not classify as a TLS or policy problem (a plain connection error) is not recorded at all. The policy domain is the recipient domain for pepsi-stage-relay-to-internet(1); the smarthost stage records under the smarthost’s own host name, since that — not the recipient’s domain — is the peer whose TLS was observed. This recording is enabled by [pepsi-tlsrpt] SEND_REPORTS (off by default) and is skipped for a report message itself (the state.tlsrpt loop guard), so a report that hits a TLS error never spawns a report about a report.

Run once a day from cron, pepsi-tlsrpt report compiles a day’s counters into one RFC 8460 aggregate report per policy domain, looks up that domain’s advertised reporting address (the rua field of its _smtp._tls TXT record) and ships the gzip-compressed JSON report there: by e-mail for a mailto: target (the report is injected into the pipeline at REPORT_STAGE so it is DKIM-signed and relayed like any other message; with REPORT_STAGE unset the target is logged and skipped), or by HTTPS POST (with Content-Type: application/tlsrpt+gzip) for an https: target. A report e-mail carries REPORT_FROM as both envelope sender and From: — RFC 8460 §5.3 requires a report to be DKIM/DMARC-alignable, so it is deliberately not null-sender, and the state.tlsrpt flag rather than the null sender is what stops the recursion. Once a domain’s report is shipped, that day’s counters for it are deleted, so re-running for the same day is safe.

Both target forms are validated before use, because both come out of a DNS record the reported-on domain publishes. A mailto: target must be a single plain local@domain addr-spec, so it cannot inject headers or extra recipients into the report message. An https: target is posted to through the same hardened client key discovery uses: the host name is resolved and every candidate address vetted, so a rua pointing at a loopback, private, link-local or CGNAT address is refused rather than posted to from inside the mail server’s network, and a POST follows no redirect, so a public URL cannot bounce the report elsewhere. A target that fails either check is logged and skipped; the day’s counters are kept, so nothing is lost.

It connects to the shared database through the [pepsi-postgres] section and is configured by [pepsi-tlsrpt] (see pepsi.conf(5)). It is not a stage. Being a cron job it is normally started as root, in which case it continues as the pepsi service account; see Running as root.

The advertising half — publishing our own _smtp._tls TXT record so other senders report their TLS results toward our domains to us — is handled by pepsi-setup(1), which prints the record from [pepsi-tlsrpt] RUA.

85.1.49.1.4. Commands

report [–date YYYY-MM-DD] [–dry-run]

Compile and ship the reports for one UTC day, defaulting to yesterday.

–date YYYY-MM-DD

Report on this UTC day instead of yesterday.

–dry-run

Print each report’s JSON to standard output instead of sending it, and retain the counters (nothing is deleted). Useful for inspection.

prune [–older-than DAYS]

Delete pepsi.tls_session counters older than the retention window. With no flag the window is [pepsi-tlsrpt] RETAIN_DAYS (default 7). Reported days are already cleared by report; this only sweeps days that were never reported (e.g. a domain that advertises no rua). The shipped pepsi-tlsrpt-prune.timer runs it daily, independently of report — counters accumulate whether or not reports are ever sent, so retention must not depend on the report job being set up.

–older-than DAYS

Override the retention window for this run.

85.1.49.1.5. Configuration

The [pepsi-tlsrpt] section (see pepsi.conf(5)) is read as a whole: it is considered configured only when it either advertises a RUA or sets SEND_REPORTS, and report refuses to run otherwise (prune, which only deletes rows, does not need it).

RUA

The reporting URI(s) we advertise in our own _smtp._tls TXT record, verbatim (e.g. mailto:tls@example.org, or a comma-separated mailto:/https: list). Only pepsi-setup(1) reads it, to print the record; it plays no part in sending. Unset means we advertise nothing.

SEND_REPORTS

Whether the relay stages record outbound TLS sessions in pepsi.tls_session at all. Default no.

REPORT_FROM

Envelope sender and From: of outgoing report e-mails; its domain is the report’s submitter (and part of every report-id). Required by report.

REPORT_STAGE

The pipeline stage report e-mails are injected at, so they are signed and relayed like any other message. Without it a mailto: target cannot be used. pepsi-setup(1) checks that it names an existing stage.

ORGANIZATION

organization-name in emitted reports. Defaults to REPORT_FROM’s domain.

CONTACT

contact-info (an e-mail address) in emitted reports. Omitted from the report when unset.

RETAIN_DAYS

How many days of pepsi.tls_session counters prune keeps (default 7), unless --older-than overrides it for one run.

85.1.49.1.6. Running as root

Pepsi gives every component its own PostgreSQL role, authenticated over the local socket by the operating-system account it runs as, and root is deliberately not one of them — which matters here because the daily job normally runs from root’s crontab. Rather than fail to connect, the tool detects that it was started as root and becomes the unprivileged pepsi service account — which owns pepsi.tls_session and may inject the report messages — before it connects. A crontab entry therefore needs no sudo -u pepsi.

The configuration file (and any @inline-secret@ fragment it references) is read before the switch, so a root-only configuration is still loaded normally.

If the pepsi account does not exist — an uninstalled source tree, a test rig — the identity is left untouched, a warning is logged, and the connection is attempted as the invoking user, so a setup in which root can reach the database keeps working.

85.1.49.1.7. Global Options

These global options precede the subcommand (a trailing flag is rejected).

-c FILE, –config FILE

Read the configuration from FILE instead of searching the default locations.

-L LOGLEVEL, –log LOGLEVEL

Set the logging verbosity. LOGLEVEL is one of error, warn, info, debug or trace (default: info).

-v, –verbose

Show log messages from all sources, including third-party libraries.

-h, –help

Print a usage summary and exit.

-V, –version

Print the version and exit.

85.1.49.1.8. Exit Status

0

Successful completion. Per-domain delivery problems (a lookup failure, an unreachable HTTPS endpoint) are logged and the run continues; the affected day’s counters are retained for the next run.

1

An error occurred: a malformed configuration, a [pepsi-tlsrpt] section that neither advertises a RUA nor sets SEND_REPORTS (there is nothing to report), a missing required option (REPORT_FROM), an unparseable --date, or a failed database connection or query.

85.1.49.1.9. Examples

Send yesterday’s reports (typical cron invocation):

pepsi-tlsrpt -c /etc/pepsi/pepsi.conf report

Inspect what would be sent for a specific day without sending:

pepsi-tlsrpt -c /etc/pepsi/pepsi.conf report --date 2026-06-14 --dry-run

Prune counters older than 30 days:

pepsi-tlsrpt -c /etc/pepsi/pepsi.conf prune --older-than 30

A daily crontab entry (after midnight UTC, reporting the day just ended; the prune is already scheduled by pepsi-tlsrpt-prune.timer):

17 1 * * *  pepsi  pepsi-tlsrpt -c /etc/pepsi/pepsi.conf report

85.1.49.1.10. See Also

pepsi-setup(1), pepsi.conf(5), pepsi-stage-relay-to-internet(1), pepsi-stage-relay-to-smarthost(1), pepsi-config(1)

85.1.49.1.11. Bugs

Report bugs to the Pepsi issue tracker.