Client API Reference
This page lists every HTTP endpoint exposed by the Email Templates feature and shows how to call it. For the higher-level concepts (templates, fragments, contexts, MJML + Fluid, translations, versioning) see the feature overview.
All endpoints require the standard authentication headers (Authorization: Bearer …) and Content-Type: application/json. Authorization uses the
EmailTemplates access item; the access type (View / Add / Edit / Delete) is shown per endpoint.
There are two route groups:
/api/email/templates— full templates/api/email/fragments— reusable MJML fragments referenced from templates
Each version of a template is its own row with its own numeric id. CRUD, send and "create new version" are addressed by id; the version list
and delete endpoints are addressed by templateKey. Preview is template-independent — it takes the MJML in the request body, not an id.
Templates
GET /api/email/templates
Returns every template row in the system (every version of every template).
- Access:
EmailTemplates.View - Response:
200 OK— array ofEmailTemplateViewDto
GET /api/email/templates/{id}
Returns a single template row by numeric id.
- Access:
EmailTemplates.View - Response:
200 OK—EmailTemplateViewDto - Errors:
404if no row with that id exists.
POST /api/email/templates
Creates a brand-new template. The new row is inserted at version = 1 with whatever status was supplied (defaults to Draft). The MJML source is
validated and compiled on save; any referenced fragments must already exist.
-
Access:
EmailTemplates.Add -
Body:
{"templateKey": "order-scheduled-for-delivery","templateContextKey": "OrderScheduledForDelivery","name": "Order scheduled for delivery","description": "Sent when a customer order is dispatched.","mjmlSource": "<mjml>…</mjml>","statusId": 81587}Field Required Notes templateKeyyes Lowercase alphanumeric + hyphens, 2–100 chars. Must not collide with any existing template key. templateContextKeyno The data context the template binds to (e.g. Event,OrderScheduledForDelivery). Must match an existing context known to the server. Omit it when the MJML has no model placeholders to resolve.nameyes Display name. descriptionno Up to 1000 chars. mjmlSourceyes MJML markup, may reference fragments. statusIdyes DictionaryItem id from the Publish status group of the Email templates configuration dictionary ( 81587= Draft,81588= Published). -
Response:
201 Created—EmailTemplateViewDto.Versionstarts at1. -
Errors:
400for invalid key, duplicate key, missing fragment, MJML compile error, or Fluid template error.
PUT /api/email/templates/{id}
Updates the template row identified by id in place. templateKey and version are fixed for the life of a row — change them by creating a new
version (see below). Everything else is mutable. The MJML source is re-validated on save.
- Access:
EmailTemplates.Edit - Body:
EmailTemplateEditDto - Response:
200 OK—EmailTemplateViewDto. - Errors:
404if the id does not exist;400for the same validation errors as create.
POST /api/email/templates/{id}/versions
Creates a new Draft version by copying the row identified by id. The new row inherits the source's templateKey, templateContextKey,
name, description, and mjmlSource. Its version is set to max(version) + 1 across all rows sharing that templateKey,
and its status is forced to Draft.
At most one Draft can exist per template key — if one already exists, this call is rejected.
- Access:
EmailTemplates.Add - Body: none
- Response:
201 Created—EmailTemplateViewDtofor the newly inserted draft row. - Errors:
404if the source id does not exist;400if a Draft already exists for that template key.
GET /api/email/templates/{templateKey}/versions
Lists every version (any status) for the given template key, ordered by version descending.
- Access:
EmailTemplates.View - Response:
200 OK— array ofEmailTemplateVersionDto
DELETE /api/email/templates/{templateKey}
Soft-deletes either a single version of a template or every version of it. Exactly one selector must be supplied: a version query parameter, or deleteAllVersions=true.
-
Access:
EmailTemplates.Delete -
Query parameters:
Name Type Notes versionint? The single version to delete. deleteAllVersionsbool When true, every version of the template key is soft-deleted. -
Response:
204 No Content -
Errors:
400if neither or both selectors are supplied.404if no rows match.
POST /api/email/templates/preview
Renders the specific template row identified by id to final HTML without sending an email. The id can refer to any version (Draft or Published),
so the editor can preview the version it is currently working on.
-
Access:
EmailTemplates.View -
Body:
{"mjmlSource": "<mjml>…</mjml>","templateContextKey": "OrderScheduledForDelivery","data": {/* model fields matching templateContextKey */},"locale": "en-US"}Field Required Notes mjmlSourceyes The MJML being authored. May reference fragments, which must already exist. templateContextKeyno The data context the MJML binds to (e.g. Event). Must match an existing context; drives the data model used for Fluid. Omit it when the MJML has no model placeholders — it is then rendered with no model.datano JSON object matching templateContextKey. If omitted, the server fills in default sample data so you can still see a render. Ignored when notemplateContextKeyis supplied.localeyes Locale code used for the ttranslation filter (e.g.en-US,el-GR). -
Response:
200 OK{"html": "<!doctype html>…"} -
Errors:
400ifmjmlSourceis empty,templateContextKeyis supplied but unknown, a referenced fragment is missing, the MJML fails to compile, ordatafails Fluid rendering.
POST /api/email/templates/{id}/send
Testing endpoint: renders the specific template row identified by id — regardless of status, including Draft — and queues the resulting HTML
for delivery. The endpoint returns as soon as the message is queued; actual SMTP delivery happens asynchronously via SparkPost.
Use this to dispatch a draft to a real inbox while still iterating on it; production sends are issued from backend code that
resolves templateKey → latest Published version.
-
Access:
EmailTemplates.Edit -
Body:
{"data": {/* model fields matching the template's context */},"locale": "en-US","to": ["alice@example.com", "bob@example.com"],"subject": "Your order is on its way"}Field Required Notes datano Same shape as preview. Omit for default sample data (mostly useful in dev). localeno Locale for translations. Falls back to the template's default if omitted. toyes At least one recipient. Duplicates are removed automatically. subjectyes Email subject line. The sender identity is fixed server-side (
info@alerts.people-t.com/ "People Omni") and cannot be overridden by the caller — there are nofromEmail/fromName/replyTofields. -
Response:
202 Accepted— empty body. The email has been queued, not yet delivered. -
Errors:
404if the row id does not exist;400for an emptyto, emptysubject, or model/context mismatch.
:::note Async delivery Because the send is asynchronous, the endpoint does not return a SparkPost transmission id. If you need delivery confirmation, listen on the existing email-webhook / table-storage transmission log. :::
Fragments
Fragments are reusable MJML snippets referenced from templates. They support parameters (passed in via Fluid) and come in two types: Component
(rendered MJML) and Style (stylesheet-only injection).
GET /api/email/fragments
Returns every fragment.
- Access:
EmailTemplates.View - Response:
200 OK— array ofEmailTemplateFragmentViewDto
GET /api/email/fragments/{id}
Returns a single fragment by id.
- Access:
EmailTemplates.View - Response:
200 OK—EmailTemplateFragmentViewDto - Errors:
404if the id does not exist.
POST /api/email/fragments
Creates a new fragment.
-
Access:
EmailTemplates.Add -
Body:
{"fragmentKey": "cta-button","name": "CTA Button","description": "Branded call-to-action button.","fragmentType": 0,"mjmlSource": "<mj-button background-color=\"#0066cc\" href=\"{{ href }}\">{{ text }}</mj-button>","parameters": [{ "name": "text", "default": "Click here" },{ "name": "href" }]}Field Required Notes fragmentKeyyes Stable identifier used from templates. nameyes Display name. descriptionno Optional notes for editors. fragmentTypeno 0= Component,1= Style. Defaults toComponent.mjmlSourceyes MJML markup. Can reference {{ parameter }}placeholders.parametersno Declared parameters with optional defaults. A parameter without a default must be supplied by the caller at template-time. -
Response:
201 Created—EmailTemplateFragmentViewDto -
Errors:
400for invalid input.
PUT /api/email/fragments/{id}
Updates a fragment. Templates that reference it will pick up the change on next render (with cache invalidated when any referenced fragment changes).
- Access:
EmailTemplates.Edit - Body:
EmailTemplateFragmentEditDto(same fields as add, plusid). - Response:
200 OK—EmailTemplateFragmentViewDto - Errors:
404if the id does not exist.
OData
For list/filter/sort scenarios there is an OData endpoint on templates:
GET /api/odata/email-templates— supports$filter,$select,$orderby,$top,$skip,$expand.GET /api/odata/email-templates({id})— single entity.
The shape is the same as EmailTemplateViewDto. Every version of every template is a separate row in this feed.
Schemas
EmailTemplateViewDto
| Field | Type | Notes |
|---|---|---|
id | int | Auto-generated row id. Unique per version. |
templateKey | string | Stable identifier. Shared across every version of the same template. |
templateContextKey | string | The data context this template binds to. |
name | string | Display name. |
description | string? | Optional. |
mjmlSource | string | MJML markup. |
status | reference | Publish status dictionary item, e.g. { "id": 81588, "name": "Published" }. |
version | int | Version number for this row. (templateKey, version) is unique. |
createdAt / modifiedAt | datetime? | Audit timestamps. |
creator / modifier | user? | Audit user info. |
EmailTemplateEditDto
The mutable surface of an existing row. templateKey and version are fixed for the row and are not part of this DTO.
| Field | Type | Notes |
|---|---|---|
templateContextKey | string? | Optional. Must match an existing context when supplied; omit it when the MJML has no model placeholders. |
name | string | Required. Display name. |
description | string? | Up to 1000 chars. |
mjmlSource | string | Required. Re-validated and re-compiled on every save. |
statusId | int | DictionaryItem id from the Publish status group. Set to 81588 (Published) to publish a Draft row. |
EmailTemplateVersionDto
Returned by the "list versions" endpoint.
| Field | Type | Notes |
|---|---|---|
id | int | Row id of this version. |
version | int | Version number. |
status | reference | Publish status dictionary item, e.g. { "id": 81587, "name": "Draft" }. |
EmailTemplateFragmentViewDto
| Field | Type | Notes |
|---|---|---|
id | int | Auto-generated. |
fragmentKey | string | Stable identifier. |
name | string | Display name. |
description | string? | Optional. |
fragmentType | int | 0 = Component, 1 = Style. |
mjmlSource | string | MJML markup. |
parameters | array? | See FragmentParameterDefinitionDto. |
createdAt / modifiedAt | datetime? | Audit timestamps. |
creator / modifier | user? | Audit user info. |
EmailTemplateFragmentEditDto
All fields of EmailTemplateFragmentAddDto, plus id (must match route).
FragmentParameterDefinitionDto
| Field | Type | Notes |
|---|---|---|
name | string | Parameter name as used in {{ name }}. |
default | string? | Optional default value. If omitted, callers must supply this parameter. |
Typical client flows
Edit a published template through a new draft
GET /api/email/templates/{templateKey}/versionsto find the currently-published row.POST /api/email/templates/{id}/versionswith that row's id — server returns a fresh Draft row at the next version number.PUT /api/email/templates/{draftId}to iterate on the draft (name, description, MJML, context, statusId).POST /api/email/templates/previewwith the draft'smjmlSource+templateContextKeyto verify the result in the editor.POST /api/email/templates/{draftId}/sendto dispatch the draft to a real inbox for end-to-end QA.PUT /api/email/templates/{draftId}once more withstatusId: 81588(Published) to publish.
Render a preview while editing a template
POST /api/email/templates/previewwith the in-progressmjmlSource, thetemplateContextKey, optional sampledata, and alocale— display the returnedhtmlin an iframe. No template needs to exist yet.
Delete a template
- One specific version:
DELETE /api/email/templates/{templateKey}?version=2. - Every version:
DELETE /api/email/templates/{templateKey}?deleteAllVersions=true.
Create a template that reuses fragments
POST /api/email/fragmentsfor each shared snippet (button, header, row…).POST /api/email/templatesreferencing those fragment keys from the template's MJML.POST /api/email/templates/previewwith that MJML +templateContextKeyto verify.