EVERJUST Website Newsletter — Agent Skill
Wire up and operate the newsletter subscribe surface on a live everjust.app tenant's
public website: drop or edit a "Subscribe to our newsletter" block, bind it to a
mailing.list, understand the /website_mass_mailing/subscribe jsonrpc route it fires,
read who signed up, and manage opt-out — all through the everjust_agent_mcp server (the
WEBSITE TOOLS for the block's QWeb + the generic search/get/create/update/call/
describe_model for the records; see [[everjust-agent-mcp]] for connecting and opening a
session against the right tenant DB).
You connect the MCP at https://<tenant>.everjust.app/mcp with an Odoo API-key bearer and
act as that user — so record reads/writes run with your ACL, and dropping/editing the
block needs an admin or website-designer role (website.group_website_designer). A plain
key can subscribe from the front-end but can't place or restyle the box.
The crucial everjust fact: this is STOCK Odoo 19 website_mass_mailing — there is no
custom everjust newsletter addon. It layers a handful of snippets (s_newsletter_*) and
two public jsonrpc routes (/website_mass_mailing/subscribe,
/website_mass_mailing/is_subscriber) on top of the same mailing.list /
mailing.contact / mailing.subscription models a campaign uses. So this skill is the
front-end intake half of the mailing stack; the send half (mailing.mailing, blasts,
traces) is [[everjust-mass-mailing]]. What you must know that is everjust/version-specific:
- A subscribe block is a SNIPPET (QWeb), not a record. The Newsletter block lives in a
page's
ir.ui.viewarch as a<section class="s_newsletter_block s_newsletter_list …" data-list-id="N">. You add/edit it withwebsite_edit_page(which copy-on-writes the edit into a site-specificir.ui.view), not with acreateon some model. The binding to a list is thedata-list-idattribute — an integer that must be a realmailing.list.id. - The everjust marketing pages are Tailwind-utility QWeb, NOT the html_builder snippet
canvas.
website_connectdomain/website_tcswpages are hand-authored Tailwind QWeb wrapped in<t t-call="website.layout">, with header/footer as separate views. There is a set of on-brands_cd_*snippets (everjust_website_snippets:s_cd_hero,s_cd_heading,s_cd_features,s_cd_cta) that ship the site's compiled Tailwind idiom (cd-root,mx-auto w-full max-w-6xl px-6,eyebrow,font-display,text-stone-*,rounded-md bg-stone-900 …). There is NOs_cd_newsletter/ subscribe snippet — so a branded subscribe box = thes_cd_*Tailwind wrapper idiom + the stockjs_subscribewiring pasted inside. See [[everjust-website-snippets]] for thes_cd_*vocabulary. - Submitting the block hits
POST /website_mass_mailing/subscribe(ajsonrpc,website=True,auth='public'route onMassMailController). Its handler runs sudo and: parses the email, finds-or-creates amailing.contactby that email, then creates amailing.subscription(contact↔list join) — or, if one already exists and is opted-out, flipsopt_out=False. That is the entire effect of a signup: one subscription row. It sends no email, adds nomail.mail, starts no campaign. (It first verifies a reCAPTCHA/Turnstile token server-side if the tenant configured one.) mailing.list.is_publicis "Show In Preferences" — NOT "is this subscribable from the site".is_public=Trueonly makes the list appear on the recipient's own/mailing/mysubscription-preferences page so they can self-manage it. The subscribe block writes to ANY list id regardless ofis_public(the route sudo's). So making a list "public" is about the preferences UX; pointing a block at a list is a separatedata-list-idedit. Don't conflate them.
You reach both surfaces through the everjust_agent_mcp tools — see [[everjust-agent-mcp]] for
connecting, the WEBSITE TOOLS' signatures, and the confirm/ACL gates. For the debrand
(/-vs-/odoo, the everjust.public_website gate) and the COW-into-a-site-view mechanic, see
[[everjust-website]] and [[everjust-platform]].
When to use this skill
- Add a subscribe surface to a page — drop a Newsletter block/box/centered/grid/form or a
popup onto a page, or paste one into a Tailwind
s_cd_*marketing page, and bind it to a list. - Point an existing block at the right list — change/set its
data-list-id. - Make a list self-manageable — set
mailing.list.is_public=Trueso subscribers see it on/mailing/myand can opt in/out of it there. - Read who subscribed from the website — list the
mailing.contact/mailing.subscriptionrows a list accumulated, spot new signups, check opt-out state. - Manage opt-out — opt a contact out of a list (per-list) or globally, with a reason.
- Diagnose a broken subscribe box — wrong/zero
data-list-id, missingjs_subscribeclass, silent reCAPTCHA failure, list doesn't exist, contact created but not linked.
Do NOT use this skill for, and switch to the sibling if the task is really:
- Sending a newsletter / campaign to the collected contacts. Composing a
mailing.mailing, sending a test, launching a blast, readingmailing.traceopens/clicks/bounces — that is [[everjust-mass-mailing]] (the send half). A subscribe block only collects addresses; it never sends. (And bulk sending is human-gated on everjust — see that skill's gate.) - A one-off transactional send as a mailbox address — that's
everjust.mail.account.compose_send([[everjust-mail-ops]]). - Editing the marketing pages' general layout / hero / footer (not the subscribe box) —
that's the general QWeb page-edit path in [[everjust-website]] (and the
s_cd_*snippet library in [[everjust-website-snippets]]). This skill only owns the newsletter block within those pages. - Building a generic contact/lead form (name+message →
crm.lead) — that's a website Form snippet (s_website_form), a different mechanism ([[everjust-website-forms]]). The Newsletter block is email→list only. (Thes_newsletter_blockfull-form variant is ans_website_formposting tomailing.contact— but the common case is the inlinejs_subscribeinput, below.)
Architecture (the two surfaces + the model map)
Surface 1 — the SNIPPET (QWeb in a page view)
website_mass_mailing ships these s_* snippet templates (all carry the js_subscribe
wiring on the input wrapper and a data-list-id on the outer section, default "0" =
unbound). None is everjust-custom — they're stock, and there is NO s_cd_* newsletter variant,
so for a branded page you re-use the stock wiring inside a Tailwind wrapper (recipe 2):
Snippet template (ir.ui.view key) |
What it is |
|---|---|
website_mass_mailing.s_newsletter_subscribe_form |
The bare inline email + Subscribe button (js_subscribe wrapper). Reused by all the others. |
website_mass_mailing.s_newsletter_block |
A full-width "Subscribe to our newsletter" section (heading + copy + the form). data-list-id on this <section>. |
website_mass_mailing.s_newsletter_box |
Boxed/carded variant. |
website_mass_mailing.s_newsletter_centered |
Centered card variant. |
website_mass_mailing.s_newsletter_grid |
Grid-with-images variant. |
website_mass_mailing.s_newsletter_subscribe_popup |
A popup (s_popup) newsletter — data-list-id on the inner form. |
website_mass_mailing.s_newsletter_benefits_popup |
Popup with a benefits list. |
The binding is the data-list-id integer. At runtime the front-end Subscribe interaction
(selector = '.js_subscribe') resolves it as el.closest('section[data-list-id]')?.dataset.listId || el.dataset.listId — i.e. it prefers the list-id on the enclosing <section>, else the one
on the .js_subscribe element itself. (This closest-section-first rule is why the block variants
put data-list-id on the section and the popup puts it on the inner form — mirror whichever you
edit.) On load it calls /website_mass_mailing/is_subscriber to pre-fill/greet a known
subscriber; on the Subscribe click it POSTs /website_mass_mailing/subscribe.
In the html_builder editor these are configured by OWL Plugin options (the "Newsletter" list
<BuilderSelect> that writes data-list-id, a layout option, a "Display Thanks Message" toggle,
the input placeholder) — Odoo 19's html_builder replaced web_editor, and those OWL options are
not scriptable over MCP. Over the MCP you edit the arch directly (set data-list-id, keep
the js_subscribe* classes), which is exactly what the editor persists.
Surface 2 — the ROUTES (public jsonrpc, sudo)
Both live on MassMailController (website_mass_mailing/controllers/main.py),
type='jsonrpc', website=True, auth='public' (verified against the live core source):
/website_mass_mailing/is_subscriber(list_id, subscription_type='email')— read-only; returns{is_subscriber, value}. It countsmailing.subscriptionrows on(list_id, contact_id.email == value, opt_out == False)— so a person who opted out is reported not a subscriber (won't get the "already subscribed" greeting).valueis the logged-in user's email or the session-remembered one. Used to pre-fill the box./website_mass_mailing/subscribe(list_id, value, subscription_type='email')— the intake. First_verify_request_recaptcha_token('website_mass_mailing_subscribe'); on failure it returns{'toast_type':'danger', ...}and creates nothing (an unset/misconfigured captcha is the #1 silent failure). Thensubscribe_to_newsletter, sudo:tools.parse_contact_from_email(value)→(name, email).- Search an existing
mailing.subscriptionon(list_id, contact_id.email == email). - If none: find a
mailing.contactby email, or create one ({name, email}), then create themailing.subscription{contact_id, list_id}(defaultopt_out=False). - If one exists and
opt_out: setopt_out = False(re-subscribe). - Remember the email in
request.session['mass_mailing_email']. Returns a success toast — it does not send a confirmation email (single opt-in). Nomail.mail, no campaign.
So a website signup = one mailing.subscription row (+ maybe a new mailing.contact), and
that's it. A future mailing.mailing to that list is a separate, human-gated action in
[[everjust-mass-mailing]].
The record models (real fields — shared with the campaign side)
Everything is per-tenant DB and per-company_id. Stock Odoo mass_mailing models (introspect
any with describe_model):
| Model | Role in the subscribe flow | Key fields |
|---|---|---|
mailing.list |
The audience a block writes into. data-list-id points here. |
name* (required), is_public ("Show In Preferences", default False — governs /mailing/my, not subscribability), contact_ids (m2m → mailing.contact), subscription_ids (→ mailing.subscription), and computed hygiene contact_count, contact_count_email, contact_count_opt_out, contact_count_blacklisted. |
mailing.contact |
A recipient row — the subscribe route finds-or-creates one by email. | name, email, email_normalized, list_ids (m2m), subscription_ids, opt_out (global opt-out across all lists), is_blacklisted (computed from mail.blacklist), message_bounce. |
mailing.subscription |
The list↔contact join with per-list opt-out — this is the row a signup creates. (The old name mailing.contact.subscription does NOT exist.) |
contact_id* , list_id* , opt_out (per-list), opt_out_reason_id (→ mailing.subscription.optout), opt_out_datetime (auto-stamped when opt_out flips true), is_blacklisted/message_bounce (related from contact). Unique (contact_id, list_id). |
mailing.subscription.optout |
The opt-out reason vocabulary shown on /mailing/my. |
name. (Default set: "I never subscribed to this list", "I changed my mind", "…too many emails…", "…not relevant…", "Other".) |
Per-list opt-out (mailing.subscription.opt_out) removes them from THAT list; global opt-out
(mailing.contact.opt_out) suppresses them everywhere. Both are honored at send time by
[[everjust-mass-mailing]]; mail.blacklist (the shared suppression list, see [[everjust-mail-ops]])
trumps both.
Recipes
Route each through the everjust_agent_mcp MCP (open the session per [[everjust-agent-mcp]]; pick
the DB, e.g. connectdomain). Snippet edits use the WEBSITE TOOLS
(website_pages/website_edit_page/website_publish/website_menu/website_redirect); record
work uses the generic search/get/create/update/call. Odoo domains are triple-lists; m2m
writes use command tuples ([[6,0,ids]] replace, [[4,id]] add). On connectdomain at last
check: website+mass_mailing+website_mass_mailing+everjust_website_snippets all
installed; the s_newsletter_subscribe_form snippet view is present; everjust_website_snippets
ships only s_cd_hero/heading/features/cta (no newsletter variant).
0. First: pick the target LIST, and confirm it's the intake path (not the send path)
Adding a subscribe box is meaningless without a real list id to bind. Resolve it first, and sanity-check you're editing intake, not launching a campaign.
// Which lists exist, and which are self-manageable on /mailing/my?
search("mailing.list", [], ["id","name","is_public","contact_count"])
// -> e.g. [{id:1,name:"Newsletter",is_public:true,contact_count:1}, {id:2,name:"Launch — All opt-in",...}, ...]
Choose (or create) the list the block should feed — usually a dedicated public "Newsletter" list, not a private prospect/GTM list you built for outreach. If none fits, create one:
create("mailing.list", { "name": "Website — Newsletter", "is_public": true }) // is_public => shows on /mailing/my
If the real task is "email these subscribers", stop — that's [[everjust-mass-mailing]] (and it's human-gated). This skill only collects.
1. Add a Newsletter block to an editable page (WEBSITE TOOLS, COW)
For a normal html_builder page, read the page's arch, insert the s_newsletter_block section
with data-list-id set to your list, and write the whole arch back — website_edit_page
copy-on-writes the edit into a site-specific ir.ui.view so a module upgrade never clobbers
it (see [[everjust-website]] on COW).
website_pages() // find the page + its view (id, url, view_id, website_specific)
website_edit_page(url="/features") // returns the current QWeb arch to edit
// Insert this section where you want it (keep the classes EXACTLY; set data-list-id=1):
<section class="s_newsletter_block s_newsletter_list o_cc o_cc2 pt64 pb64" data-list-id="1" data-name="Newsletter">
<div class="container">
<div class="row">
<div class="col-lg-6">
<h2 class="h4-fs">Subscribe to our newsletter</h2>
<p class="text-muted">DNS auto-config + automatic SSL updates, in your inbox.</p>
</div>
<div class="col-lg-5 offset-lg-1">
<div class="s_newsletter_subscribe_form s_newsletter_list js_subscribe" data-list-id="1" data-name="Newsletter Form">
<div class="js_subscribed_wrap d-none">
<p class="h4-fs text-center text-success"><i class="fa fa-check-circle-o" role="img"/> Thanks for registering!</p>
</div>
<div class="js_subscribe_wrap">
<div class="input-group">
<input type="email" name="email" class="s_newsletter_subscribe_form_input js_subscribe_value form-control" placeholder="Email Address"/>
<a role="button" href="#" class="js_subscribe_btn o_submit btn btn-primary">Subscribe</a>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
// then persist the FULL arch back through website_edit_page (it overwrites the whole view arch → COW).
The Subscribe interaction resolves the list from closest('section[data-list-id]') first
— so the value on the outer <section> is what actually binds; keep the inner form's
data-list-id in sync. The js_subscribe / js_subscribe_value / js_subscribe_btn /
js_subscribed_wrap classes are load-bearing — the JS keys off them; don't rename them.
2. Add an on-brand subscribe box to a Tailwind s_cd_* MARKETING page
The website_connectdomain / website_tcsw pages are verbatim Tailwind-utility QWeb
(wrapped in <t t-call="website.layout">), and the branded snippet library is s_cd_*. There
is no s_cd_newsletter, so build the box as an s_cd_cta-style Tailwind section and paste
the stock js_subscribe wiring inside. The only non-negotiables are the four js_subscribe*
classes and a valid data-list-id; everything visual re-uses the page's Tailwind vocabulary
(cd-root, mx-auto w-full max-w-6xl px-6, eyebrow, font-display, text-stone-*,
rounded-md bg-stone-900 … — see [[everjust-website-snippets]]).
website_pages() // locate the marketing page's ir.ui.view
website_edit_page(url="/") // read the Tailwind arch
<!-- On-brand wrapper (s_cd_* idiom) + stock js_subscribe wiring: -->
<section class="s_cd_cta cd-root border-b border-stone-200 bg-stone-50">
<div class="mx-auto w-full max-w-6xl px-6 py-16 text-center">
<p class="eyebrow">Stay in the loop</p>
<h2 class="font-display mt-3 text-4xl text-stone-900 sm:text-5xl">Subscribe to our newsletter</h2>
<p class="mx-auto mt-4 max-w-xl text-lg leading-8 text-stone-600">DNS auto-config + automatic SSL updates, in your inbox.</p>
<div class="s_newsletter_list js_subscribe mx-auto mt-9 flex max-w-md gap-3" data-list-id="1">
<div class="js_subscribed_wrap d-none w-full text-green-700">Thanks for subscribing!</div>
<div class="js_subscribe_wrap flex w-full gap-3">
<input type="email" name="email" class="js_subscribe_value flex-1 rounded-md border border-stone-300 px-4 py-3" placeholder="you@company.com"/>
<a role="button" href="#" class="js_subscribe_btn o_submit rounded-md bg-stone-900 px-6 py-3 font-medium text-stone-50 transition-colors hover:bg-stone-700">Subscribe</a>
</div>
</div>
</div>
</section>
// write the whole arch back (COW into the connectdomain site view).
Here there is no enclosing section[data-list-id] around the .js_subscribe div, so the
interaction falls back to el.dataset.listId — put data-list-id on the .js_subscribe
div. Keep the four js_subscribe* classes verbatim; a Tailwind class the compiled
connectdomain.css doesn't define won't be styled.
3. Re-point an existing block at a different list
If a page already has a subscribe box aimed at the wrong (or a 0) list, just change the
data-list-id. Find the pages first:
// Which views carry a subscribe block?
search("ir.ui.view", [["arch_db","like","js_subscribe"]], ["id","key","name","website_id"])
website_edit_page(url="<that page url>") // read the arch
// change data-list-id="0" (or the old id) -> data-list-id="1" on BOTH the section and the
// inner form (closest-section-first rule), keep everything else, write the FULL arch back (COW).
data-list-id="0" is the "unbound" sentinel a freshly-dropped snippet ships with — a box left
at 0 posts to list 0 (nonexistent) and silently no-ops. Always set a real id.
4. Make a list self-manageable on /mailing/my (is_public)
is_public is orthogonal to the block — it controls whether subscribers see the list on their
own preferences page (/mailing/my) and can opt in/out of it there. Turn it on for lists you
want recipients to manage themselves:
update("mailing.list", [1], { "is_public": true }) // "Show In Preferences"
This does not change what any subscribe block does, and it does not publish a page.
Leave your private outreach lists (is_public=False) hidden so recipients can't see/join them.
5. Read who subscribed from the website (and their opt-out state)
A signup is a mailing.subscription row; join to mailing.contact for the address/name.
// Everyone on the list, with per-list opt-out:
search("mailing.subscription",
[["list_id","=",1]],
["contact_id","opt_out","opt_out_datetime","opt_out_reason_id","create_date"],
order="create_date desc")
// Resolve the contact emails:
search("mailing.contact", [["list_ids","in",[1]]],
["name","email","email_normalized","opt_out","is_blacklisted","message_bounce"])
// New signups in the last week (subscription rows created recently):
search("mailing.subscription",
[["list_id","=",1],["create_date",">=","2026-06-28 00:00:00"]],
["contact_id","create_date"])
// Hygiene at a glance (feeds the campaign-side send decision):
get("mailing.list", [1],
["name","contact_count","contact_count_email","contact_count_opt_out","contact_count_blacklisted"])
Web signups are single opt-in (no confirmation email), so treat the list as self-declared —
before any blast, hand it to [[everjust-mass-mailing]] and respect that skill's account-wide
reputation gate. is_blacklisted/message_bounce flag addresses that will be dropped at send.
6. Opt a contact out (per-list or global), with a reason
Mirror what /mailing/my does — set opt-out on the subscription (this list only) or on the
contact (all lists). Don't delete the row (you'd lose the audit + they could re-subscribe
silently). A create-free update on mailing.subscription/mailing.contact is a read-write
generic call; it does not need confirm:true (only delete, non-read call, and structural
models do).
// Per-LIST opt-out (leaves them on other lists). opt_out_datetime auto-stamps:
find("mailing.subscription.optout", "I changed my mind") // -> reason id
update("mailing.subscription",
search("mailing.subscription", [["list_id","=",1],["contact_id","=",<cid>]], ["id"]),
{ "opt_out": true, "opt_out_reason_id": <reason_id> })
// GLOBAL opt-out (every list):
update("mailing.contact", [<cid>], { "opt_out": true })
To hard-suppress an address across the whole tenant (bounces/complaints, legal removal), that
is mail.blacklist via everjust.mail.suppression._ingest — see [[everjust-mail-ops]] /
[[everjust-mass-mailing]] on the atomic mirror; a raw mail.blacklist create skips it. A
subscribe box re-subscribes an opted-out subscription (flips opt_out=False) but does NOT
override the global mailing.contact.opt_out or a blacklist entry.
Pitfalls (everjust-specific)
Subscribing collects; it does NOT send.
/website_mass_mailing/subscribeonly creates amailing.subscription(and maybe amailing.contact). It fires no email, nomail.mail, no campaign, and there is no double opt-in confirmation on this build. To email the collected people you build and (human-gated) launch amailing.mailing— that's [[everjust-mass-mailing]]. Don't promise a "welcome email" from adding a box.A subscribe block is a SNIPPET (QWeb arch), not a record — edit it with
website_edit_page. There is no "newsletter subscription" record youcreateto place a box. You edit the page'sir.ui.viewarch (website_edit_page, which COWs into a site-specific view and overwrites the whole arch — always send the complete template, not a fragment). The binding is thedata-list-idattribute; the wiring is thejs_subscribe/js_subscribe_value/js_subscribe_btn/js_subscribed_wrapclasses — keep them verbatim or the front-end JS won't attach.data-list-idmust be a REAL list id, and resolution is closest-section-first. A freshly-dropped snippet shipsdata-list-id="0"(unbound → silent no-op). TheSubscribeinteraction readsclosest('section[data-list-id]')before the element's own attribute — so for the block variants set it on the<section>(keep the inner form in sync), and for a barejs_subscribeblock (recipe 2) set it on the.js_subscribeelement itself. Wrong/zero id = box does nothing with no error.is_public≠ "subscribable from the site".mailing.list.is_public("Show In Preferences") only controls whether the list appears on a recipient's/mailing/mypage. The subscribe route runs sudo and writes to whateverdata-list-idyou give it, public or not. Making a list public and pointing a block at a list are two independent actions; don't assume one implies the other.reCAPTCHA/Turnstile mis-config is the #1 silent breakage.
/subscribecalls_verify_request_recaptcha_token('website_mass_mailing_subscribe')first; on a partial/invalid captcha config the route returns adangertoast and creates no subscription — the box "does nothing" for the visitor. When a subscribe box fails, check the captcha config before blaming the list binding.It's the SAME models as a campaign — you're sharing an audience.
mailing.list/mailing.contact/mailing.subscriptionhere are literally the records [[everjust-mass-mailing]] sends to. Pointing a public subscribe box at a private outreach list silently mixes cold-outreach contacts with self-serve signups (and vice-versa). Use a dedicated public "Newsletter" list for the website box; keep prospect lists off the site.mailing.subscriptionis the real join model (not the nonexistentmailing.contact.subscription), and opt-out lives at two levels: per-list (mailing.subscription.opt_out) vs global (mailing.contact.opt_out). Toggle the right one.opt_out_datetimeauto-stamps;is_subscriberreports an opted-out contact as NOT a subscriber (no greeting). Don't delete a subscription to "unsubscribe" — opt it out (keeps the audit and blocks a silent re-add).Editing the block needs website-designer rights, and it's COW.
website_edit_page/ publishing requirewebsite.group_website_designer; the MCP acts as YOUR user, so a plain key can subscribe from the front-end but can't drop or restyle the box (AccessError). That's an ACL limit, not aconfirmgate — get a designer/admin key ([[everjust-agent-mcp]]). Note Odoo 19: user group membership isgroup_ids, notgroups_id, if you inspectres.users. Granting admin and writing secret config are hard-blocked regardless of key.No branded newsletter snippet exists — you compose one.
everjust_website_snippetsshipss_cd_hero/heading/features/ctaonly; there is nos_cd_newsletter. For an on-brand box, wrap the stockjs_subscribewiring in thes_cd_*Tailwind idiom (recipe 2). Don't invent a Tailwind class the compiledconnectdomain.cssdoesn't define, and don't add OWL/React — the subscribe behavior is the addon's front-end interaction on the.js_subscribehook.Everything is per-tenant DB, per-
company_id, and per-website_id. List ids, contacts, and page views are tenant-scoped; on a multi-site tenant a page (and its subscribe block) is bound to onewebsite_id. Confirm the tenant DB,env.company, and the site before editing (per-tenant isolation — [[everjust-platform]]).
See also
- [[everjust-mass-mailing]] — the send half of the exact same models. This skill
collects addresses into a
mailing.list; that skill composes and (human-gated) sendsmailing.mailingblasts and readsmailing.traceresults. Cross the boundary deliberately: never "send to subscribers" from here. - [[everjust-website]] — the general page-edit surface these blocks live in:
website_pages/website_edit_page/website_publish/website_menu/website_redirect, the copy-on-write mechanic, per-page SEO/visibility, and the Tailwind-QWeb-vs-html_builder distinction. - [[everjust-website-snippets]] — the on-brand
s_cd_*snippet vocabulary (s_cd_hero/heading/features/cta,cd-root,eyebrow,font-display,text-stone-*) you re-use to make a branded subscribe box. - [[everjust-platform]] — tenancy,
/odoodebrand, theeverjust.public_websitegate, and the platform invariants (one-DB-per-tenant, per-tenant secrets, hard-blocked admin-grant). - [[everjust-agent-mcp]] — connecting to
https://<tenant>.everjust.app/mcp, the genericsearch/get/create/update/delete/call/describe_model/list_modelstoolset, the WEBSITE TOOLS' signatures, and theconfirm/ACL gates every recipe rides on. - [[everjust-mail-ops]] — the separate everjust webmail product (
everjust.mail.*) and the sharedmail.blacklistsuppression (viaeverjust.mail.suppression._ingest). A newsletter signup does not touch the webmail stack; hard suppression does go through that mirror.