CLI command
lifecycle consent
The email consent register: each recipient's consent to each topic of email Echo sends, every change kept, and the preference page every email links to.
Invocation#
echo-cli lifecycle consent show --address <email>
echo-cli lifecycle consent record --address <email> --topic campaigns|announcements|newsletter --state granted|withdrawn --source <where> [--detail <text>] --by <name>Inputs and output#
show writes address, topics and frequency: one topic per kind of email (campaigns: outreach campaign emails and their follow-ups; announcements: product release announcements the recipient subscribed to; newsletter: the newsletter of the best posts and announcements) with topic, describes and records, the address's changes of consent to it newest first: id, address, topic, state (granted or withdrawn), source, detail (or null), recorded_by and recorded_at. The first record is the consent as it stands; a topic with none was never recorded. frequency is the recipient's own limit chosen on their preference page — recipient, max_messages, window_hours, source and chosen_at — or null when they chose none. record writes the record it appended.
State effect#
Both verbs call Echo Web's authenticated /api/analytics/lifecycle/consents with ECHO_INTERNAL_BEARER at ECHO_WEB_BASE_URL; the Email consent section of the tracking screen and of Echo Desktop's Contact frequency screen call the same operation, and echo-mcp reads it as echo_email_consent. Records are appended to email_consents in lower case and never edited. Before a campaign email Echo asks whether the recipient withdrew campaigns, before an announcement email whether they withdrew announcements, and before a newsletter whether they withdrew newsletter; one that did is not emailed: the campaign recipient is skipped and the announcement or newsletter delivery records why, each with "<address> withdrew consent to <topic> emails at <instant> through <source>, as <id>; only a record granting it again lets these emails reach them". A topic never recorded is emailed as before the register. Every campaign, announcement and newsletter email ends with the recipient's preference link, <origin>/api/public/preferences/page?address=<address>&token=<HMAC-SHA256 of the JSON ["preferences", address] under ECHO_EMAIL_LINK_SECRET>, at the deployment's NEXT_PUBLIC_SITE_URL; it does not expire. The page shows each topic, whether the recipient gets it, and a Stop or Resume button that records the change as the recipient's own, with source preference page, and how often Echo may write to them: a limit of at most so many emails in any so many hours, saved or cleared by the recipient (contact_frequency_choices), in plain HTML that needs no script. That limit stands beside the operator's one cap (lifecycle caps): a send to the address that would reach either is deferred and recorded as the cap's deferrals are, naming "their own choice (preference page, <instant>)" or "the cap". /api/public/preferences answers the same as JSON for a product's own settings screen, with frequency, and takes { address, token, frequency: { max_messages, window_hours } } to set the limit or frequency: null to clear it. A deployment without ECHO_EMAIL_LINK_SECRET sends no campaign, announcement or newsletter email: each records "ECHO_EMAIL_LINK_SECRET is required". Like suppressions, the register is not a personal store privacy erase removes, because erasing a withdrawal would let mail the recipient refused reach them again.
Refusals#
- A wrong invocation is refused before anything is sent: "lifecycle consent takes show --address <email>; record --address <email> --topic <topic> --state <granted|withdrawn> --source <where> [--detail <text>] --by <name>; got <word>", "lifecycle consent <verb> does not take <flag>; it takes …", "<flag> needs a value" and "lifecycle consent <verb> needs <flag>".
- Echo Web answers a bad request with "address must be one email address, a local part and a domain around one @; got <value>", "topic must be one of campaigns, announcements, newsletter; got <value>", "state must be one of granted, withdrawn; got <value>", "source is required: where the consent was given or withdrawn, such as the preference page or a signed form", "detail is required: what the recipient said, when given" for a blank --detail or "recorded_by is required: …"; and a conflict with "<address>'s consent to <topic> emails is already <state> since <instant>, as <id>". The command prints Echo Web's sentence and exits 1.
- The preference page refuses a link without a token with "token is required: the one in the preference link of an email Echo sent to this address" and a link sent to another address with "this preference link was not sent to <address>; open the link from an email Echo sent to it", both as a bad request; a second click recording what the register already holds shows the conflict sentence on the page instead of failing.