Email send core (recipient dedupe → template resolution → render → SES). Injected for sendTest — admin test sends go through the same pipeline as the lambda's event-driven sends.
Publishes notification.emailTemplate.*; null for the import script.
Discard a draft: clear draft_version_id. The version row remains — it is
immutable history. live_version_id is untouched, so a variant with a live
version keeps sending.
Authoring list/read, cached under the admin key-prefix partition. Projects
invalidReason from the validator (collect mode), so a broken variant
shows its problem in the admin list without a write or render. Write/
render validation stays throwing (update re-validates).
Optionalids?: readonly string[]Optionalkeys?: readonly (Optionallive?: booleanOnly variants with a live version.
Optionalpage?: numberOptionalpageSize?: numberOptionalstatusVariants?: readonly string[]A variant's full authoring history, newest first — what the editor's Versions tab lists and diffs (EMAIL_TEMPLATES.md §4.6: "field-level diff between any two versions to make rollback informed"). versions stays the two-pointer read for the working copy; this is the append-only trail behind it.
Uncached, like versions: the tab renders straight after a save, and version rows are immutable so there is nothing to invalidate anyway.
Send a test email via EmailSendCore, exercising the full send pipeline (recipient dedupe → active template resolution → render → SES) in the same way the lambda does for event-driven sends. One implementation, two entrypoints.
The outcome names the exact copy exercised (version + content hash) on a completed send, and the reason nothing arrived on a skip. Throws on SES failure.
Save + lifecycle (PATCH semantics, §4.5). Content fields validate and
insert a version row; a status change moves the pointers, running the
two-actor guard on ACTIVE. Both happen in one transaction; every write
then busts the shared cache. Any refusal rolls the whole transaction back.
The acting admin, adusr_…; saved content records it as its author.
The full live-vs-draft content pair for a variant, plus the pointers' copy.
The authoring list flattens to the working copy (EmailTemplate) so the
versions tab diffs live against draft here instead.
Authoring and lifecycle for stored email templates (docs/EMAIL_TEMPLATES.md §4.5).
updatespans both tables in one transaction — content writes an immutable version row (the trigger snapsdraft_version_id), a status change moves the pointers on the identity row.The two-actor activation control lives here:
status: "ACTIVE"returnsEMAIL_TEMPLATE_SAME_ACTOR_ACTIVATIONwhen the publishing actor also saved the copy being published.status: "DISABLED"is deliberately single-actor — it is the kill switch, reviewed after the fact (§4.6).Receives the shared EmailTemplateCache as an injected dependency and reads its authoring projection through the
adminpartition. Every write busts both surfaces via EmailTemplateCache.invalidate — the shared template-key tag vocabulary evicts this surface's stale pages AND the send surface's pages through the one namespace.