Skip to the content
Documentation
Help Start
Reading in:
  • German: 9,059 of 9,059 pages
About the help in other languages
All help pages

E-mail preference commands

The reference of the five console commands of the e-mail preferences and of the cronjob part mailpreferences: every action and option, the exit codes and real output. For the tasks they serve, read the administrator's…

Download as PDF

Address: https://doc.exponential.earth/exphelp/viewer/specifications/6.0/mail-preferences-cli.md

On this page

E-mail preference commands

The reference of the five console commands of the e-mail preferences and of the cronjob part mailpreferences: every action and option, the exit codes and real output. For the tasks they serve, read the administrator's guide.

All output below comes from a test installation on 2026-10-04, with addresses on the reserved .invalid domain; the host is replaced by www.example.com and personal links are shortened to m1.... Each command also runs as php bin/php/<script>.php (named in the table); add --allow-root-user as root and --no-colors for plain text.

CommandAlias ofScript
exp:mail:statusexp:mailstatusbin/php/mailstatus.php
exp:mail:preferencesexp:mailpreferencesbin/php/mailpreferences.php
exp:mail:suppressionexp:mailsuppressionbin/php/mailsuppression.php
exp:mail:consentexp:mailconsentbin/php/mailconsent.php
exp:mail:gateexp:mailgatebin/php/mailgate.php

Exit codes: every command ends with a last line PASS (exit code 0) or FAIL: <reason> (exit code 1). exp:mail:status fails only for a problem of level error (a missing table).

Addresses are hidden (r***@docs.invalid) unless --addresses is given. Files written by --output get mode 0600.

No command sends mail, with one exception: exp:mail:preferences set of a double opt-in category sends the confirmation mail through the installation's transport unless --no-mail or --source=import is given.

exp:mail:status

The tables, the gate, the footer settings, the categories with their counts, the suppression list, the consent log, the gate's decisions of the last 24 hours and 7 days, and the problems. Changes nothing.

OptionMeaning
--jsonone JSON object (the array of expMailPreferencesService::status())
$ ./console exp:mail:status
Tables
  expmail_category         ok
  expmail_preference       ok
  expmail_consent_log      ok
  expmail_suppression      ok
  expmail_pending          ok
Gate
  gate                     enabled
  site secret              present
  links point to           https://www.example.com
  organisation name        (empty)
  postal address           (empty)
Categories                   on    off  pending
  content                      0      2        0  optional
  collaboration                0      0        0  optional
  newsletter                   1      0        0  optional, double opt-in
  marketing                    0      0        0  optional, double opt-in
  system                       0      0        0  optional
  security                     -      -        -  essential
  orders                       -      -        -  essential
  legal                        -      -        -  essential
  admin                        -      -        -  essential
People
  with preferences         2
  all optional mail off    1
  pending confirmations    0   (0 expired)
  suppressed addresses     0
  consent log rows         22   (0 anonymised)
Gate, last 24 hours
  sent 1, blocked 3, essential 0, uncategorised 128, errors 0
  blocked because: off 3
  uncategorised from kernel/classes/notification/ezmailnotificationtransport.php: 127
  uncategorised from tests/tests/lib/ezutils/eZMailTest.php: 1
Gate, last 7 days
  sent 1, blocked 3, essential 0, uncategorised 128, errors 0
  blocked because: off 3
  uncategorised from kernel/classes/notification/ezmailnotificationtransport.php: 127
  uncategorised from tests/tests/lib/ezutils/eZMailTest.php: 1
Problems
  [warning] The organisation name or postal address of the mail footer is empty (mailpreferences.ini [FooterSettings]); the law requires both in every optional mail.
  [notice] 128 mails without a category in the last 7 days.
PASS

--json starts like this:

{
    "tables": {
        "expmail_category": true,
        "expmail_preference": true,
        "expmail_consent_log": true,
        "expmail_suppression": true,
        "expmail_pending": true
    },
    "gate": "enabled",
    "secret": true,
    "base_url": "https://www.example.com",
    "footer": {
        "organisation_name": "",
        "organisation_address": ""
    },
    "categories": {
        "content": {
            "name": "Content notifications",
            "essential": false,
            "source": "ini",
            "double_opt_in": false,
            "on": 1,
            "off": 0,
            "pending": 0
        },

and goes on with recipients, master_off, pending, pending_expired, suppression (total, by_reason), consent_log, consent_log_anonymised, gate_24h, gate_7d (sent, blocked, essential, uncategorised, error, by_category, uncategorised_senders, blocked_reasons, last), log_file and problems (a list of [level, code, detail]; codes table_missing, footer_missing, secret_missing, gate_disabled, uncategorised, gate_errors).

exp:mail:preferences

Shows and changes the preferences of one person: an account (--user=<login or id>) or an address (--email=<address>; an address of an account is that account). Every change is written to the consent log with source admin (or import) and the text of --wording (default "Changed on the console").

ActionDoes
categoriesthe categories: identifier, kind, default, frequencies, double opt-in, name
showthe main switch, each category with state, frequency and whether mail goes
seta category on or off (--category, --state=on|off)
frequencythe frequency of a category (--category, --frequency=immediate|daily|weekly)
masterthe main switch (--state=on|off)
exporteverything stored about the person (--format=json|csv, --output=<file>)
eraseremove the preferences, anonymise the consent log; needs --yes
linkthe person's preference link (works without login)
OptionMeaning
--user, --emailthe person
--category, --state, --frequencyfor set, master, frequency
--format, --outputfor export
--wordingthe text recorded in the consent log
--sourceadmin (default) or import (switches a double opt-in category on at once; only for consent confirmed elsewhere)
--no-mailset: store a pending double opt-in without sending its confirmation mail
--yeserase: really erase
--jsonshow, categories as JSON
--addressesshow the address
$ ./console exp:mail:preferences categories
content              optional  default off  immediate,daily,weekly   -  Content notifications
collaboration        optional  default off  immediate,daily,weekly   -  Collaboration and approvals
newsletter           optional  default off                           double opt-in  Newsletters
marketing            optional  default off                           double opt-in  Offers and news
system               optional  default off                           -  Application and system notices
security             essential default off                           -  Account security
orders               essential default off                           -  Orders and receipts
legal                essential default off                           -  Legal and service notices
admin                essential default off                           -  Administration alerts
PASS

(The column "default off" means nothing for an essential category: it is always sent.)

$ ./console exp:mail:preferences set --email[email protected] --category=content --state=on --wording="Docs example"
r***@docs.invalid: content is on
PASS
$ ./console exp:mail:preferences frequency --email[email protected] --category=content --frequency=weekly
r***@docs.invalid: content is weekly
PASS
$ ./console exp:mail:preferences set --email[email protected] --category=newsletter --state=on --no-mail
r***@docs.invalid: newsletter is pending_confirmation
PASS
$ ./console exp:mail:preferences set --email[email protected] --category=marketing --state=on --source=import --wording="Imported with the opt-in of 2026-10-01"
r***@docs.invalid: marketing is on
PASS
$ ./console exp:mail:preferences show --email[email protected]
Person            r***@docs.invalid
All optional mail on
  content              on       weekly     mail goes
  collaboration        off      immediate  no mail   (default)
  newsletter           pending             no mail
  marketing            on                  mail goes
  system               off                 no mail   (default)
  security             always              mail goes
  orders               always              mail goes
  legal                always              mail goes
  admin                always              mail goes
PASS
$ ./console exp:mail:preferences link --email[email protected]
https://www.example.com/mailpreferences/manage/m1...
PASS
$ ./console exp:mail:preferences master --email[email protected] --state=off
r***@docs.invalid: all optional mail off
PASS
$ ./console exp:mail:preferences export --email[email protected] --format=json --output=var/tmp/reader.json
written to var/tmp/reader.json (6412 bytes, mode 0600)
PASS

The CSV export (--format=csv without --output prints it):

section,key,value,detail
recipient,key,a:d5b6a13e...,
recipient,user_id,0,
recipient,email,[email protected],
recipient,master,on,
recipient,suppressed,no,
category,content,on,weekly
category,collaboration,off,immediate
category,newsletter,pending,
category,marketing,on,
...
pending,newsletter,category,2026-10-05T06:39:05Z
consent_log,id,created,recipient_key,user_id,email,category,action,old_value,new_value,source,wording,ip,actor_user_id,siteaccess,anonymised
consent_log,369,2026-10-05T06:39:03Z,a:d5b6a13e...,0,[email protected],content,on,off,on,admin,"Docs example",,0,site,0
...
PASS
$ ./console exp:mail:preferences erase --email[email protected]
FAIL: erase removes the preferences and anonymises the consent log: add --yes
$ ./console exp:mail:preferences erase --email[email protected] --yes
r***@docs.invalid: 4 preferences and 1 pending confirmations removed, 9 consent log rows anonymised
PASS
$ ./console exp:mail:preferences set --email[email protected] --category=security --state=off
FAIL: The mail category 'security' is essential and cannot be switched
$ ./console exp:mail:preferences show
FAIL: name the person with --user=<login or id> or --email=<address> (an existing user, a valid address)

exp:mail:suppression

The suppression list. The table holds a salted hash of each address, never the address; an entry is found by typing the address.

ActionDoes
listthe entries: start of the hash, reason, date, note (--reason, --limit default 50, --offset, --json with whole hashes)
checkwhether --email is suppressed, and why
addsuppress --email with --reason (bounce, complaint, unsubscribe_all, legal, admin, bridge) and an optional --note (an address in it is removed)
liftlift the entry of --email or --hash
$ ./console exp:mail:suppression add --email[email protected] --reason=legal --note="Docs example"
suppressed (hash 0b2dc01385cf4440…)
PASS
$ ./console exp:mail:suppression check --email[email protected]
suppressed: legal (hash 0b2dc01385cf4440…)
PASS
$ ./console exp:mail:suppression list --reason=legal --limit=5
1 entries (legal)
  0b2dc01385cf4440…  legal            2026-10-04 23:39  Docs example
PASS
$ ./console exp:mail:suppression lift --email[email protected]
lifted
PASS
$ ./console exp:mail:suppression check --email[email protected]
not suppressed
PASS

exp:mail:consent

The consent log.

ActionDoes
listthe newest rows: time, person, category, action, old -> new, source (--limit default 50, --offset, --addresses)
exportthe rows as CSV (--output=<file>, or the screen); the CSV holds addresses and IP addresses
cleanupthe retention of the cronjob part mailpreferences; --dry-run counts only

Filters for list and export: --user, --email, --category, --action, --source, --from, --to (YYYY-MM-DD).

$ ./console exp:mail:consent list --email[email protected] --limit=10
6 rows
  2026-10-04 23:39:11  r***@docs.invalid      -              master_on   off -> on  admin
  2026-10-04 23:39:09  r***@docs.invalid      -              master_off  on -> off  admin
  2026-10-04 23:39:05  r***@docs.invalid      marketing      on          off -> on  import
  2026-10-04 23:39:05  r***@docs.invalid      newsletter     pending     off -> pending  admin
  2026-10-04 23:39:04  r***@docs.invalid      content        frequency   immediate -> weekly  admin
  2026-10-04 23:39:03  r***@docs.invalid      content        on          off -> on  admin
PASS
$ ./console exp:mail:consent export --email[email protected] --output=var/tmp/consent.csv
written to var/tmp/consent.csv (6 rows, mode 0600)
PASS
$ ./console exp:mail:consent cleanup --dry-run
Would remove: 0 consent log rows, 0 expired confirmations
PASS

The CSV columns are those of expConsentLog::CSV_COLUMNS; a cell that starts with =, +, - or @ (and is not a number) is written with a leading '.

exp:mail:gate

What the gate would decide for a mail of --category to the addresses of --to (allow, off, pending, master_off, suppressed, unknown_category). With --write the test mail goes through the gate with the transport forced to file in this process, into --mail-file-dir (default var/tmp/mailgate-test); it is never sent.

OptionMeaning
--categorythe category of the test mail; empty: a mail without category
--toaddresses, comma separated
--writeput the test mail through the gate into files
--htmlan HTML test mail
--mail-file-dirwhere --write writes
--addressesshow the addresses
$ ./console exp:mail:gate --category=content --to[email protected],[email protected]
Category content: allow
  r***@docs.invalid                allow
  o***@docs.invalid                off
PASS
$ ./console exp:mail:gate --category=content --to[email protected],[email protected] --write --mail-file-dir=var/tmp/gate-mail
Category content: allow
  r***@docs.invalid                allow
  o***@docs.invalid                off

Written: 1 file(s) in var/tmp/gate-mail, decision partly_blocked, result true
  var/tmp/gate-mail/1791182348-1270746661.mail
PASS
$ ./console exp:mail:gate --to[email protected]
Category (none): no_category
  r***@docs.invalid                allow
PASS
$ ./console exp:mail:gate --category=content --to[email protected]
Category content: master_off
  r***@docs.invalid                master_off
PASS
$ ./console exp:mail:gate --category=nothing_here --to[email protected]
Category nothing_here: unknown_category
  a***@docs.invalid                allow
PASS

The written mail:

Subject: Mail gate test
From: [email protected]
Bcc: [email protected]
X-Exp-Mail-Category: content
List-Unsubscribe: <https://www.example.com/mailpreferences/unsubscribe/m1...>
List-Unsubscribe-Post: List-Unsubscribe=One-Click
This is a test of the mail gate.

-- 
You get this e-mail because you turned on "Content notifications" on Example Site.
Unsubscribe with one click: https://www.example.com/mailpreferences/unsubscribe/m1...
Choose which e-mail you get: https://www.example.com/mailpreferences/manage/m1...

(With [FooterSettings] filled in, the organisation and its postal address follow after an empty line.)

The cronjob part mailpreferences

php runcronjobs.php mailpreferences          # alone
php runcronjobs.php infrequent               # with the other infrequent parts

It prints one line, E-mail preferences retention: <n> consent log rows, <n> expired confirmations, <n> suppression entries removed, or Skipped: the e-mail preference tables are not installed (database update).

Something wrong on this page? Report an error

Documentation license

This documentation is free documentation, licensed under the GNU Free Documentation License 1.3 or later.

Exponential Copyright © 1998-2026 Exponential Foundation and 7x