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_sessioncounters 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 norua). The shippedpepsi-tlsrpt-prune.timerruns 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).
RUAThe reporting URI(s) we advertise in our own
_smtp._tlsTXT record, verbatim (e.g.mailto:tls@example.org, or a comma-separatedmailto:/https:list). Only pepsi-setup(1) reads it, to print the record; it plays no part in sending. Unset means we advertise nothing.SEND_REPORTSWhether the relay stages record outbound TLS sessions in
pepsi.tls_sessionat all. Defaultno.REPORT_FROMEnvelope sender and
From:of outgoing report e-mails; its domain is the report’s submitter (and part of everyreport-id). Required by report.REPORT_STAGEThe 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.ORGANIZATIONorganization-namein emitted reports. Defaults toREPORT_FROM’s domain.CONTACTcontact-info(an e-mail address) in emitted reports. Omitted from the report when unset.RETAIN_DAYSHow many days of
pepsi.tls_sessioncounters prune keeps (default7), unless--older-thanoverrides 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,debugortrace(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 aRUAnor setsSEND_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.