85.1.47. pepsi-list¶
manage Pepsi’s mailing lists
- Manual section:
1
85.1.47.1.1. Name¶
pepsi-list - create and manage mailing lists, their members and their owners.
85.1.47.1.2. Synopsis¶
pepsi-list [GLOBAL-OPTIONS] domain add|list|remove [ARGS]
pepsi-list [GLOBAL-OPTIONS] list create|show|set|set-ext|remove|find|styles [ARGS]
pepsi-list [GLOBAL-OPTIONS] members list|add|remove [ARGS]
pepsi-list [GLOBAL-OPTIONS] owner add|remove|reset-password [ARGS]
pepsi-list [GLOBAL-OPTIONS] import mailman3|mailman21 [ARGS]
pepsi-list [GLOBAL-OPTIONS] invite send|status [ARGS]
pepsi-list [GLOBAL-OPTIONS] digest send|bump|periodic|status [ARGS]
pepsi-list [GLOBAL-OPTIONS] ban add|list|remove [ARGS]
pepsi-list [GLOBAL-OPTIONS] template list|set|clear [ARGS]
pepsi-list [GLOBAL-OPTIONS] unsub-token LIST EMAIL [–serial N] [–uri]
pepsi-list [GLOBAL-OPTIONS] tasks –once
pepsi-list [GLOBAL-OPTIONS] check [–strict]
85.1.47.1.3. Description¶
pepsi-list is the operator’s command-line tool for Pepsi’s mailing-list
subsystem. It is not a stage: it reads and writes the pepsi schema’s
list tables directly, in the shape of pepsi-whitelist(1) and
pepsi-settings(1).
Pepsi’s mailing-list subsystem is a reimplementation of GNU Mailman 3. Its
data model, its rule/chain and handler/pipeline architecture, its REST API, its
e-mail command vocabulary and its notice-template names are the GNU Mailman
project’s design, copyright the Free Software Foundation, and Pepsi exists to be
compatible with them. Upstream’s documentation at <https://docs.mailman3.org>
describes concepts that apply directly here and is worth reading alongside this
page. Some files in Pepsi are taken from GNU Mailman, Postorius or HyperKitty
and remain under the GNU General Public License; vendor/PEPSI-VENDORING.md
lists them.
Per-list configuration lives in the database, not in pepsi.conf(5).
Lists are created at runtime, potentially in their thousands, by people who are
not the operator; the configuration file holds only what the operator owns,
in the [pepsi-list] section.
85.1.47.1.4. Global options¶
The global flags come before the subcommand, as everywhere else in Pepsi:
-c,--configFILEConfiguration file to read.
-L,--logLEVELLog level.
-v,--verboseShow log messages from all sources, including third-party libraries.
-V,--versionPrint the version and exit.
85.1.47.1.5. Naming a list¶
Every verb that takes a list accepts either spelling:
- name@domain
The posting address, which is what people type.
- name.domain
Upstream’s
list_id, with a dot. This is the identifier the REST API uses, and it is not interchangeable with the posting address — both appear in the API and writing the wrong one is a compatibility bug.
85.1.47.1.6. Commands¶
- domain add DOMAIN [–base-url URL] [–description TEXT]
Register a mail domain that lists may be created in. This is not the same as pepsi-ingress(1)’s
ACCEPTED_DOMAINS: a domain can accept mail without hosting lists. If a list domain is not one ingress accepts, mail to the list is refused atRCPTand nothing in this subsystem’s logs says so.- domain list
Show the list domains and how many lists each holds.
- domain remove DOMAIN
Remove a list domain. Refused while it still holds lists: deleting it would cascade through every list’s roster, held messages and archive.
- list create ADDRESS [–style STYLE] [–set NAME=VALUE]…
Create a mailing list.
--styleapplies a named set of attribute defaults before any--set; see Styles below. An unknown style name is an error, not a silent no-op.- list show ADDRESS [–explain]
Print every one of the 99 REST attributes with its current value.
--explainadds each attribute’s one-line explanation. (The flag is--explainrather than--helpbecause the latter belongs to the argument parser.)- list set ADDRESS NAME VALUE
Set one attribute. The value goes through the same validator the REST API and the owner console use, so the three cannot disagree about what a valid value is. Multi-valued attributes take one entry per line; a comma is not a separator, because a regular expression may contain one.
- list set-ext ADDRESS KEY VALUE
Set one Pepsi-only per-list setting. These are deliberately invisible to the Mailman REST API: upstream’s whole-resource
PUTrequires every writable attribute to be present, so a client built against Mailman 3.3.10 would omit anything Pepsi invented and be rejected. An empty value restores the site default. The keys aresearch_trigram,archive_show_addressesandarchive_retention_days.- list remove ADDRESS –yes
Delete a list, its roster, its held messages and its archive.
--yesis required.- list find [SUBSTRING]
List the mailing lists, optionally those matching a substring.
- list styles
Show the styles and their aliases.
- members list ADDRESS [–role ROLE]
Show a list’s roster. ROLE is
member,owner,moderatorornonmember.- members add ADDRESS EMAIL [–display-name NAME] [–role ROLE]
Subscribe an address. This bypasses the subscription policy entirely, which is what a command-line tool is for and also what makes it dangerous: the whole point of the confirmation workflow is that an address proves it wants to be there. Use it to migrate a roster or to fix a mistake, not to add people who did not ask.
- members remove ADDRESS EMAIL [–role ROLE]
Unsubscribe an address from one role.
- owner add ADDRESS EMAIL
Make an address an owner. Unlike an ordinary subscriber, an owner gets a user account, because they will sign in to the owner console.
- owner remove ADDRESS EMAIL
Remove an owner.
- owner reset-password ADDRESS EMAIL [–password PASSWORD]
Set an owner’s account password and print it. It prints rather than mails on purpose: this path has to work when mail is broken, which is exactly when an operator needs it.
The ordinary way to change a password is the web reset flow at
/lists/reset, which mails a link and never a password — a mailed password is a password in a mailbox for ever. This subcommand is the escape hatch for the case that flow cannot cover: the account’s address does not receive mail, or nothing on the host is delivering any. It sets the samelist_user.password_hashthe web flow does, with the same Argon2 parameters, and, like a password change made in the browser, it ends every open session of that account.- ban add PATTERN [–list ADDRESS]
Ban an address, or a regular expression beginning with
^. Without--listthe ban is site-wide.- ban list [–list ADDRESS]
Show the bans.
- ban remove PATTERN [–list ADDRESS]
Lift a ban.
- tasks –once
Run the periodic sweeps once and exit: the bounce warn-and-remove passes, the eviction of processed bounce events (
[pepsi-list] BOUNCE_EVENT_RETENTION), themax_days_to_holdexpiry of held messages, the sweeps of expired confirmation tokens and purge tombstones, and the archive retention sweep. This is what the pepsi-list-tasks.timer unit runs daily.--onceis required: without it the command refuses, because the schedule belongs to the timer and not to a loop in this program.Everything in the subsystem that happens on a schedule is here, and each part is invisible when the timer does not run: a member whose bounce score crossed the threshold is disabled by the bounce stage, and the warnings and the eventual unsubscription are this sweep’s, so a deployment without it disables members and then neither warns nor removes them.
Note
tasks --once also discards held messages older than their list’s
max_days_to_hold. That attribute is inert in GNU Mailman 3 — a grep of
its whole non-test tree finds the column, the style default and the REST
validator, and no job that reads it — and active here. It is a strict
superset: the default 0 means “never expire”, which is what happens
upstream, so nothing changes for a list that leaves it alone. Each discard is
written to the audit log, and the moderators are told once per sweep with a
count rather than once per message.
Note
The archive retention sweep deletes each list’s archived messages older
than its retention, exactly as pepsi-archive expire would (threads and
counters are repaired afterwards). The retention is resolved per list: a
list’s own archive_retention_days (set with list set-ext) wins,
0 included; a list without one uses [pepsi-list] ARCHIVE_RETENTION,
whose own default is 0; and 0 keeps everything. A stored value that
is not a day count is skipped with a warning rather than guessed at.
- check [–strict]
Validate every list’s configuration and the installation’s. Each finding is an
error, awarningor anote, with a remedy where there is an obvious one. Exits non-zero on an error, or on a warning too with--strict.- import mailman3 –rest URL [–user NAME] [–password-file FILE] [–dry-run] [–report FILE]
Import a running GNU Mailman 3 site over its own REST API: domains, lists attribute for attribute, users, addresses (keeping their
verified_on), members, bans, header matches and template URIs.REST is the default path rather than the fallback, because it works against a running site, needs no knowledge of upstream’s schema, and is insulated from upstream’s own migrations. It is also what makes the import checkable: both ends answer
/3.1/lists/<id>/config, so a per-attribute comparison is possible.Prefer
--password-fileto--password: an argument is visible to every other process on the host.- import mailman21 –listdir DIR [–list NAME] [–domain HOST] [–dry-run] [–report FILE]
Import a Mailman 2.1 site from its
lists/directory, reading eachconfig.pck. The attribute mapping is GNU Mailman’s own — the eighteen renames, the integer-to-enum tables, the two boolean collapses, the DMARC precedence rule and theuser_optionsbitfield are transcribed from upstream’sutilities/importer.py.--domainsupplies the mail host for a pickle that names none.
Note
pepsi-list starts as root and immediately drops to the ``pepsi``
service user, so a --report path must be one that user can write.
--report /root/import.txt fails with a permission error and produces no
report; /tmp or a directory owned by pepsi works. Without
--report the full report goes to standard output, so it is never lost.
- import mailman21 –json FILE [–list NAME]
The escape hatch. This reader constructs nothing — no module is looked up and no callable is called, because a
config.pckcomes from somebody else’s server. The price is strictness, so when it refuses a file, runcontrib/mm21-export.pyon the old server with that server’s own Python and feed the JSON here. Both routes go through the same mapping.- invite send [–list ADDRESS] [–rate N] [–limit N] [–dry-run]
Invite everybody with no password to choose one.
importdeliberately sends nothing, so this is the verb that decides when the largest mailing the server will ever send actually happens.--ratedefaults to[pepsi-list] INVITE_RATE(300/hour).--dry-runprints the per-domain histogram, which is a deliverability tool rather than a progress bar: one provider holding half a migrated site is what decides the rate. The run resumes where it stopped — an address with a live invitation is skipped — and invitations last four weeks, because one competes with a holiday rather than with an evening.- invite status [–list ADDRESS]
How many accounts have a password, how many have an outstanding invitation, and how many are still to invite. Nobody is ever unsubscribed for not answering.
- digest send ADDRESS
Send one list’s accumulated digest now, whatever
digest_size_thresholdsays. An operator asking for an issue has already decided.- digest bump ADDRESS
Advance the volume and reset the number to 1 without sending, which is upstream’s
--bump. What an owner does at the start of a year, or after an issue that went out wrong.- digest periodic
Send every list whose digest is due: over
digest_size_threshold, or accumulated at all whendigest_send_periodicis on. This is the timer’s verb. Upstream has no periodic runner for digests at all —maybe_send_digest_nowfires synchronously on a post once the threshold is crossed, andmailman digests --sendis meant to be cron’d by the operator. Pepsi keeps both halves and puts the second on a systemd timer, the same arrangementtasks --oncehas.- digest status [ADDRESS]
What each list has accumulated, and whether it is due.
- template list
Show the 28 notice-template names, and any URI overrides set for them at the site, domain or list level.
- template set NAME URI [–list ADDRESS | –domain DOMAIN] [–user USER] [–password PASSWORD]
Point one template at a URI:
mailman:(the built-in text) or anhttp(s):URL, optionally with HTTP Basic credentials. Without--listor--domainthe override applies to the whole site. Anhttp(s):URI is fetched by the server, under the limits in[pepsi-list] TEMPLATE_FETCH_TIMEOUT,TEMPLATE_FETCH_MAX_BYTESandTEMPLATE_FETCH_ALLOW_INTERNAL. Afile:URI is refused, here as on every other write path (the REST API and the importers), because it would make the server read its own disk; afile:row already in the table is never read, and the notice is rendered as if it were absent.- template clear NAME [–list ADDRESS | –domain DOMAIN]
Remove an override, so the built-in text is used again.
- unsub-token LIST EMAIL [–serial N] [–uri]
Print the RFC 8058 one-click unsubscribe token a delivery to EMAIL would carry, computed by the same code that mints it;
--uriprints the wholehttps:URI instead.--serialsigns a given subscription serial rather than the member’s current one. Needs[pepsi-list] UNSUBSCRIBE_SECRET. It discloses nothing the member does not already have in their mailbox.
85.1.47.1.7. Reserved verbs¶
Three verbs are listed by --help and answer with where the capability
actually is, rather than with “unknown subcommand”:
- held, requests
Moderating held messages and subscription requests is not implemented on the command line. It is in the owner console at
/lists/<list-id>/admin(pepsi-httpd(1)), over the REST API, and by mail to list-request.- inject
Not implemented, and not needed: post to the list’s own address with pepsi-sendmail(1) or any SMTP client. A message injected by a verb here and a message posted normally would go through the same stage anyway.
These answer before any database connection is attempted, so the answer is the same on a host that has no list schema.
85.1.47.1.8. Styles¶
A style is a named set of attribute defaults applied when a list is created.
GET /3.0/lists/styles reports three, which are upstream’s, with upstream’s
names and descriptions:
legacy-defaultOrdinary discussion mailing list style. The default. Also accepted as
discussionordefault.legacy-announceAnnounce only mailing list style — members may not post. Also accepted as
announce-onlyorannounce.private-defaultDiscussion mailing list style with private archives; not advertised, and subscriptions need both a confirmation and a moderator. Also accepted as
private.
One further style is Pepsi’s own and is accepted here but not listed over REST, for the same reason the Pepsi-only per-list settings are not:
moderatedA discussion list on which every member’s post is held for a moderator.
85.1.47.1.9. Exit status¶
0 on success, non-zero on failure. check exits non-zero when it found
an error (or, with --strict, a warning). import exits non-zero when
anything was not carried over, even though everything else was written.
85.1.47.1.10. Files¶
/etc/pepsi/pepsi.confConfiguration; see pepsi.conf(5), section
[pepsi-list].
85.1.47.1.11. See also¶
pepsi.conf(5), pepsi-setup(1), pepsi-settings(1), pepsi-whitelist(1).
GNU Mailman 3’s documentation at <https://docs.mailman3.org>.