Appearance
Email
Templates, the blocks they share, and the one email configuration the whole tenant sends through. Every template serves one templateType, and a mapping decides which template a type resolves to. A new tenant gets copies of the shipped defaults; origin marks those copies, and the boot reconciler replaces any that lag behind the shipped version, leaving edited ones alone.
IAM fires the system types — account_validate, user_invite, password_reset, password_changed — each carrying its own recipient. A graph's EMAIL_NOTIFY node arrives as FUNCTIONS_EMAIL_NOTIFICATION naming a template but no recipient; the notification subscriptions on the firing instance decide who gets the mail.
| GET | Read the email configuration/api/v1/email/config |
| PUT | Set the email configuration/api/v1/email/config |
| GET | List blocks/api/v1/email/blocks |
| GET | Read one block/api/v1/email/blocks/:blockId |
| POST | Create a block/api/v1/email/blocks |
| PATCH | Update a block/api/v1/email/blocks/:blockId |
| DELETE | Delete a block/api/v1/email/blocks/:blockId |
| GET | List templates/api/v1/email/templates |
| GET | Read one template/api/v1/email/templates/:templateId |
| GET | List a template's parameters/api/v1/email/templates/:templateId/parameters |
| POST | Create a template/api/v1/email/templates |
| PATCH | Update a template/api/v1/email/templates/:templateId |
| DELETE | Delete a template/api/v1/email/templates/:templateId |
| GET | List type mappings/api/v1/email/mappings |
| POST | Set a type mapping/api/v1/email/mappings/:templateType |
| DELETE | Remove a type mapping/api/v1/email/mappings/:templateType |
| GET | Preview a rendered template/api/v1/email/render/:templateId |
| GET | List delivery records/api/v1/email/deliveries |
| POST | Send an email/api/v1/email |
Read the email configuration
Transport and sender address of the tenant; pass and secretAccessKey come back as ***. Writing this response back unchanged would store the mask as the secret.
GET
/api/v1/email/configResponse
| Name | Description |
|---|---|
transportoptional | How mail leaves the backend — an SMTP server or AWS SES, told apart by type. Absent means nothing is sent at all. |
Alternative 1object | SMTP transport — host, port and credentials of the server the backend hands each message to. |
| type | Discriminator of the transport union; smtp selects this variant. |
hoststring | A hostname or host:port combination min length 1 |
portintegeroptional | Port of the SMTP server; 587 for STARTTLS, 465 together with secure.Default: 587> 0 |
securebooleanoptional | Connect over TLS immediately — true for port 465, false to upgrade the plain connection via STARTTLS. Default: false |
authobjectoptional | Credentials for the SMTP server; omit for a server that asks for none. |
userstring | Login name on the SMTP server. |
passstring | Password on the SMTP server. GET /email/config returns it as ***, so write a real value back, never the mask. |
tlsobjectoptional | TLS options for the connection; omit to keep the defaults. |
rejectUnauthorizedbooleanoptional | Whether a certificate that does not validate aborts the connection. False accepts a self-signed certificate — and every other one just as well. Default: true |
poolbooleanoptional | Keep connections open and reuse them; maxConnections and maxMessages take effect only while this is true.Default: false |
maxConnectionsintegeroptional | Upper bound of simultaneously open connections while pooling. Default: 5> 0 |
maxMessagesintegeroptional | How many messages one pooled connection carries before it is replaced. Default: 100> 0 |
Alternative 2object | AWS SES transport — the backend calls SESv2 itself instead of talking to a mail server. |
| type | Discriminator of the transport union; ses selects this variant. |
regionstring | AWS region the SES client is created for. min length 1 |
endpointstringoptional | Replaces the endpoint the SDK would derive from region; omit for the regional one.min length 1 |
credentialsobjectoptional | Static AWS credentials. Omit them together with resolveCredentialsFromInstanceProfile to let the SDK resolve credentials on its own. |
accessKeyIdstring | Access key id of the IAM identity that sends. min length 1 |
secretAccessKeystring | Secret access key of that identity. GET /email/config returns it as ***, so write a real value back, never the mask.min length 1 |
resolveCredentialsFromInstanceProfilebooleanoptional | Take credentials from the EC2 instance metadata; when set it wins over anything given in credentials.Default: false |
fromstringoptional | Default sender address (e.g. App Name <noreply@example.com>) |
Errors
| Status | Description | Body |
|---|---|---|
403 | You do not have permission to perform this action | |
409 | No email configuration exists for this tenant, or one without transport or from; details says which. Set it with PUT /email/config. | code: EMAIL_CONFIG_NOT_SET, details: string |
401 | No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key. | error: string |
500 | The request failed for a reason that is not the caller's to fix. Safe to retry. | error: string, message: string, statusCode: 50 |
Set the email configuration
Replaces the tenant's single email configuration — a field left out is gone, not kept. Without transport or from every send fails with EMAIL_CONFIG_NOT_SET.
PUT
/api/v1/email/configBody
| Name | Description |
|---|---|
transportoptional | How mail leaves the backend — an SMTP server or AWS SES, told apart by type. Absent means nothing is sent at all. |
Alternative 1object | SMTP transport — host, port and credentials of the server the backend hands each message to. |
| type | Discriminator of the transport union; smtp selects this variant. |
hoststring | A hostname or host:port combination min length 1 |
portintegeroptional | Port of the SMTP server; 587 for STARTTLS, 465 together with secure.Default: 587> 0 |
securebooleanoptional | Connect over TLS immediately — true for port 465, false to upgrade the plain connection via STARTTLS. Default: false |
authobjectoptional | Credentials for the SMTP server; omit for a server that asks for none. |
userstring | Login name on the SMTP server. |
passstring | Password on the SMTP server. GET /email/config returns it as ***, so write a real value back, never the mask. |
tlsobjectoptional | TLS options for the connection; omit to keep the defaults. |
rejectUnauthorizedbooleanoptional | Whether a certificate that does not validate aborts the connection. False accepts a self-signed certificate — and every other one just as well. Default: true |
poolbooleanoptional | Keep connections open and reuse them; maxConnections and maxMessages take effect only while this is true.Default: false |
maxConnectionsintegeroptional | Upper bound of simultaneously open connections while pooling. Default: 5> 0 |
maxMessagesintegeroptional | How many messages one pooled connection carries before it is replaced. Default: 100> 0 |
Alternative 2object | AWS SES transport — the backend calls SESv2 itself instead of talking to a mail server. |
| type | Discriminator of the transport union; ses selects this variant. |
regionstring | AWS region the SES client is created for. min length 1 |
endpointstringoptional | Replaces the endpoint the SDK would derive from region; omit for the regional one.min length 1 |
credentialsobjectoptional | Static AWS credentials. Omit them together with resolveCredentialsFromInstanceProfile to let the SDK resolve credentials on its own. |
accessKeyIdstring | Access key id of the IAM identity that sends. min length 1 |
secretAccessKeystring | Secret access key of that identity. GET /email/config returns it as ***, so write a real value back, never the mask.min length 1 |
resolveCredentialsFromInstanceProfilebooleanoptional | Take credentials from the EC2 instance metadata; when set it wins over anything given in credentials.Default: false |
fromstringoptional | Default sender address (e.g. App Name <noreply@example.com>) |
Response
| Name | Description |
|---|---|
resourceIdentifierstring | Unique Coldwave resource name (CRN) that identifies a resource |
Errors
| Status | Description | Body |
|---|---|---|
403 | You do not have permission to perform this action | |
400 | The request did not match the schema for this endpoint. The details field carries the specific failures. | error: Validation Error |
401 | No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key. | error: string |
500 | The request failed for a reason that is not the caller's to fix. Safe to retry. | error: string, message: string, statusCode: 50 |
List blocks
Every block of the tenant the caller may read; blocks the access rules hide are left out of the list rather than refusing the call.
GET
/api/v1/email/blocksResponse
| Name | Description |
|---|---|
idstring | Resource id: Base58 (9–10 characters) or a 16-character Crockford snowflake. Not an RFC 4122 UUID. Pattern: ^[0-9A-HJ-NP-Za-km-z]{9,16}$ |
namestring | Human-readable block name min length 1 |
contentstring | HTML content of the block. May contain {{param}} placeholders. |
parametersarrayoptional | Parameters expected by this block's content Default: [] |
namestring | Name of the parameter, written as {{name}} in the content.min length 1 |
requiredbooleanoptional | When true, a send without this parameter is refused; fallback is consulted for optional ones only.Default: true |
fallbackstringoptional | Value substituted when an optional parameter is not supplied — the substitution is reported in fallbacks on the delivery record. Without one the {{name}} token stays in the mail. |
originobjectoptional | Set only on a tenant's copy of a block shipped with the product. Absent means the block was authored in the tenant, and the boot reconciler never touches it. |
keystring | Stable key of the shipped default; unchanged even when the tenant renames the block. min length 1 |
versioninteger | Version of the definition this copy was written from. A lower one is replaced at boot, unless the tenant has edited the block since. > 0 |
createdBystring | Unique Coldwave resource name (CRN) that identifies a resource |
createdAtinteger | Unix timestamp in milliseconds when this resource was created. min 0 |
updatedAtintegeroptional | Unix timestamp in milliseconds when this resource was last modified. min 0 |
Errors
| Status | Description | Body |
|---|---|---|
403 | You do not have permission to perform this action | |
401 | No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key. | error: string |
500 | The request failed for a reason that is not the caller's to fix. Safe to retry. | error: string, message: string, statusCode: 50 |
Read one block
The block's HTML content and its declared parameters — what a template takes on when it references the block as {{block:<id>}}.
GET
/api/v1/email/blocks/0d7kq2m9xr4t9c5hPath Parameters
| Name | Description |
|---|---|
blockIdstring | Id of the content block. A template refers to it as {{block:<id>}}, so renaming the block leaves every reference intact. |
Response
| Name | Description |
|---|---|
idstring | Resource id: Base58 (9–10 characters) or a 16-character Crockford snowflake. Not an RFC 4122 UUID. Pattern: ^[0-9A-HJ-NP-Za-km-z]{9,16}$ |
namestring | Human-readable block name min length 1 |
contentstring | HTML content of the block. May contain {{param}} placeholders. |
parametersarrayoptional | Parameters expected by this block's content Default: [] |
namestring | Name of the parameter, written as {{name}} in the content.min length 1 |
requiredbooleanoptional | When true, a send without this parameter is refused; fallback is consulted for optional ones only.Default: true |
fallbackstringoptional | Value substituted when an optional parameter is not supplied — the substitution is reported in fallbacks on the delivery record. Without one the {{name}} token stays in the mail. |
originobjectoptional | Set only on a tenant's copy of a block shipped with the product. Absent means the block was authored in the tenant, and the boot reconciler never touches it. |
keystring | Stable key of the shipped default; unchanged even when the tenant renames the block. min length 1 |
versioninteger | Version of the definition this copy was written from. A lower one is replaced at boot, unless the tenant has edited the block since. > 0 |
createdBystring | Unique Coldwave resource name (CRN) that identifies a resource |
createdAtinteger | Unix timestamp in milliseconds when this resource was created. min 0 |
updatedAtintegeroptional | Unix timestamp in milliseconds when this resource was last modified. min 0 |
Errors
| Status | Description | Body |
|---|---|---|
403 | You do not have permission to perform this action | |
404 | The requested email block could not be found | code: EMAIL_BLOCK_NOT_FOUND |
400 | The request did not match the schema for this endpoint. The details field carries the specific failures. | error: Validation Error |
401 | No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key. | error: string |
500 | The request failed for a reason that is not the caller's to fix. Safe to retry. | error: string, message: string, statusCode: 50 |
Create a block
Stores a reusable HTML fragment and returns its id. Parameters declared here are demanded from every template that references the block.
POST
/api/v1/email/blocksBody
| Name | Description |
|---|---|
namestring | Name of the addressed item — a block or template, a device method, a recorded event. A method name is limited to letters, digits, _ and -, because it becomes a segment of the message CRN.min length 1 |
contentstring | The block's text. It is substituted into a template wherever {{block:<id>}} appears, and its own {{parameter}} slots are filled in the same pass as the template's. |
parametersarrayoptional | The parameters this template or block declares. A send must supply every required one, and the template type's own contract adds further parameters on top. Default: [] |
namestring | Name of the parameter, written as {{name}} in the subject, the body and any block.min length 1 |
requiredbooleanoptional | Whether a send is refused when this parameter is missing. Defaults to true. Default: true |
fallbackstringoptional | Value substituted when a send omits the parameter. It applies to optional parameters only — a missing required one refuses the send instead, and an optional one without a fallback leaves {{name}} standing in the mail. |
Response
| Name | Description |
|---|---|
idstring | Property id in canonical hexadecimal form, e.g. 0x0800. |
Errors
| Status | Description | Body |
|---|---|---|
403 | You do not have permission to perform this action | |
400 | The request did not match the schema for this endpoint. The details field carries the specific failures. | error: Validation Error |
401 | No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key. | error: string |
500 | The request failed for a reason that is not the caller's to fix. Safe to retry. | error: string, message: string, statusCode: 50 |
Update a block
Merges the given fields into the block. A new content is refused when a mapped template would then no longer print a required parameter of its type.
PATCH
/api/v1/email/blocks/0d7kq2m9xr4t9c5hPath Parameters
| Name | Description |
|---|---|
blockIdstring | Id of the content block. A template refers to it as {{block:<id>}}, so renaming the block leaves every reference intact. |
Body
| Name | Description |
|---|---|
namestringoptional | Name of the addressed item — a block or template, a device method, a recorded event. A method name is limited to letters, digits, _ and -, because it becomes a segment of the message CRN.min length 1 |
contentstringoptional | The block's text. It is substituted into a template wherever {{block:<id>}} appears, and its own {{parameter}} slots are filled in the same pass as the template's. |
parametersarrayoptional | The parameters this template or block declares. A send must supply every required one, and the template type's own contract adds further parameters on top. Default: [] |
namestring | Name of the parameter, written as {{name}} in the subject, the body and any block.min length 1 |
requiredbooleanoptional | Whether a send is refused when this parameter is missing. Defaults to true. Default: true |
fallbackstringoptional | Value substituted when a send omits the parameter. It applies to optional parameters only — a missing required one refuses the send instead, and an optional one without a fallback leaves {{name}} standing in the mail. |
Errors
| Status | Description | Body |
|---|---|---|
403 | You do not have permission to perform this action | |
404 | The requested email block could not be found | code: EMAIL_BLOCK_NOT_FOUND |
400 | The template's blocks and its {{block:…}} references disagree, or a locale does not print every required parameter of its type; details names the locale and the parameters. Also raised for a block edit that would break a mapped template. | code: EMAIL_TEMPLATE_NOT_CONFORMANT, details: string |
401 | No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key. | error: string |
500 | The request failed for a reason that is not the caller's to fix. Safe to retry. | error: string, message: string, statusCode: 50 |
Delete a block
Refused while a mapped template still declares the block; blockedBy names those templates. Templates without a mapping do not protect it — their {{block:…}} is left standing in the mail afterwards.
DELETE
/api/v1/email/blocks/0d7kq2m9xr4t9c5hPath Parameters
| Name | Description |
|---|---|
blockIdstring | Id of the content block. A template refers to it as {{block:<id>}}, so renaming the block leaves every reference intact. |
Errors
| Status | Description | Body |
|---|---|---|
403 | You do not have permission to perform this action | |
404 | The requested email block could not be found | code: EMAIL_BLOCK_NOT_FOUND |
409 | The block is still declared by a mapped template, or the template is still mapped to a type. blockedBy lists the templates holding a block — drop the mapping, or the reference, first. | code: EMAIL_RESOURCE_IN_USE, details: string |
400 | The request did not match the schema for this endpoint. The details field carries the specific failures. | error: Validation Error |
401 | No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key. | error: string |
500 | The request failed for a reason that is not the caller's to fix. Safe to retry. | error: string, message: string, statusCode: 50 |
List templates
Every template of the tenant the caller may read, with subject, body and translations in full. Which one is actually used per type is GET /email/mappings.
GET
/api/v1/email/templatesResponse
| Name | Description |
|---|---|
idstring | Resource id: Base58 (9–10 characters) or a 16-character Crockford snowflake. Not an RFC 4122 UUID. Pattern: ^[0-9A-HJ-NP-Za-km-z]{9,16}$ |
namestring | Human-readable template name min length 1 |
templateTypeenum | Which system contract the template serves: every locale must print the required parameters of that type, and only a template of this type can be mapped to it. Fixed at creation. Possible values: password_reset, account_validate, user_invite, system_notification, welcome, password_changed, custom |
categorystringoptional | Optional grouping label (e.g. branding-a, transactional) |
blocksarraystring | IDs of blocks referenced in the body via {{block:id}} placeholders |
subjectstring | Email subject. Supports {{text:key}} and {{param}} placeholders. |
bodystring | Email body HTML. Supports {{block:id}}, {{text:key}}, and {{param}} placeholders. Resolved in that order. |
translationsdictionary | Translations keyed by locale for {{text:key}} placeholders |
parametersarray | All parameters expected by this template, including those in referenced blocks |
namestring | Name of the parameter, written as {{name}} in subject and body.min length 1 |
requiredbooleanoptional | When true, a send without this parameter is refused; fallback is consulted for optional ones only.Default: true |
fallbackstringoptional | Value substituted when an optional parameter is not supplied — the substitution is reported in fallbacks on the delivery record. Without one the {{name}} token stays in the mail. |
originobjectoptional | Set only on a tenant's copy of a template shipped with the product. Absent means the template was authored in the tenant, and the boot reconciler never touches it. |
keystring | Stable key of the shipped default; unchanged even when the tenant renames the template. min length 1 |
versioninteger | Version of the definition this copy was written from. A lower one is replaced at boot, unless the tenant has edited the template since. > 0 |
createdBystring | Unique Coldwave resource name (CRN) that identifies a resource |
createdAtinteger | Unix timestamp in milliseconds when this resource was created. min 0 |
updatedAtintegeroptional | Unix timestamp in milliseconds when this resource was last modified. min 0 |
Errors
| Status | Description | Body |
|---|---|---|
403 | You do not have permission to perform this action | |
401 | No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key. | error: string |
500 | The request failed for a reason that is not the caller's to fix. Safe to retry. | error: string, message: string, statusCode: 50 |
Read one template
The stored template as it was written — parameters holds only its own declarations, not the merged contract a send has to satisfy.
GET
/api/v1/email/templates/0d7kq2m9xr4t8vbzPath Parameters
| Name | Description |
|---|---|
templateIdstring | Id of the template. On a send it is the alternative to templateType — exactly one of the two, since a type resolves to a template through the tenant's mapping. |
Response
| Name | Description |
|---|---|
idstring | Resource id: Base58 (9–10 characters) or a 16-character Crockford snowflake. Not an RFC 4122 UUID. Pattern: ^[0-9A-HJ-NP-Za-km-z]{9,16}$ |
namestring | Human-readable template name min length 1 |
templateTypeenum | Which system contract the template serves: every locale must print the required parameters of that type, and only a template of this type can be mapped to it. Fixed at creation. Possible values: password_reset, account_validate, user_invite, system_notification, welcome, password_changed, custom |
categorystringoptional | Optional grouping label (e.g. branding-a, transactional) |
blocksarraystring | IDs of blocks referenced in the body via {{block:id}} placeholders |
subjectstring | Email subject. Supports {{text:key}} and {{param}} placeholders. |
bodystring | Email body HTML. Supports {{block:id}}, {{text:key}}, and {{param}} placeholders. Resolved in that order. |
translationsdictionary | Translations keyed by locale for {{text:key}} placeholders |
parametersarray | All parameters expected by this template, including those in referenced blocks |
namestring | Name of the parameter, written as {{name}} in subject and body.min length 1 |
requiredbooleanoptional | When true, a send without this parameter is refused; fallback is consulted for optional ones only.Default: true |
fallbackstringoptional | Value substituted when an optional parameter is not supplied — the substitution is reported in fallbacks on the delivery record. Without one the {{name}} token stays in the mail. |
originobjectoptional | Set only on a tenant's copy of a template shipped with the product. Absent means the template was authored in the tenant, and the boot reconciler never touches it. |
keystring | Stable key of the shipped default; unchanged even when the tenant renames the template. min length 1 |
versioninteger | Version of the definition this copy was written from. A lower one is replaced at boot, unless the tenant has edited the template since. > 0 |
createdBystring | Unique Coldwave resource name (CRN) that identifies a resource |
createdAtinteger | Unix timestamp in milliseconds when this resource was created. min 0 |
updatedAtintegeroptional | Unix timestamp in milliseconds when this resource was last modified. min 0 |
Errors
| Status | Description | Body |
|---|---|---|
403 | You do not have permission to perform this action | |
404 | The requested email template could not be found | code: EMAIL_TEMPLATE_NOT_FOUND |
400 | The request did not match the schema for this endpoint. The details field carries the specific failures. | error: Validation Error |
401 | No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key. | error: string |
500 | The request failed for a reason that is not the caller's to fix. Safe to retry. | error: string, message: string, statusCode: 50 |
List a template's parameters
The merged parameter contract: the parameters of the templateType, the template's own, then those of every referenced block, first declaration winning. This is the list a send has to satisfy.
GET
/api/v1/email/templates/0d7kq2m9xr4t8vbz/parametersPath Parameters
| Name | Description |
|---|---|
templateIdstring | Id of the template. On a send it is the alternative to templateType — exactly one of the two, since a type resolves to a template through the tenant's mapping. |
Response
| Name | Description |
|---|---|
namestring | Parameter name, referenced as {{name}} in content min length 1 |
requiredbooleanoptional | If true, sending fails when this parameter is missing Default: true |
fallbackstringoptional | Value used when parameter is absent and required is false. Empty string removes the placeholder. |
Errors
| Status | Description | Body |
|---|---|---|
403 | You do not have permission to perform this action | |
404 | The requested email template could not be found | code: EMAIL_TEMPLATE_NOT_FOUND |
400 | The request did not match the schema for this endpoint. The details field carries the specific failures. | error: Validation Error |
401 | No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key. | error: string |
500 | The request failed for a reason that is not the caller's to fix. Safe to retry. | error: string, message: string, statusCode: 50 |
Create a template
Declared blocks and the {{block:…}} references in subject and body must match in both directions, and every locale in translations must print the required parameters of the templateType.
POST
/api/v1/email/templatesBody
| Name | Description |
|---|---|
namestring | Name of the addressed item — a block or template, a device method, a recorded event. A method name is limited to letters, digits, _ and -, because it becomes a segment of the message CRN.min length 1 |
templateTypeenum | The system mail this template stands for — password_reset, account_validate, user_invite and so on. A mapping binds one template per type and per tenant, which is how the backend's own mails find theirs and how a send may name a type instead of an id.Possible values: password_reset, account_validate, user_invite, system_notification, welcome, password_changed, custom |
categorystringoptional | Free-form label for grouping templates in an editor. Nothing on the rendering path reads it. |
blocksarraystring | Ids of the blocks this template may include. The list is an allowlist: a {{block:…}} naming anything outside it is left in the text exactly as written. |
subjectstring | Subject line, with the same {{block:…}}, {{text:…}} and {{parameter}} slots as the body. Unlike the body it is not HTML, so substituted values are not escaped. |
bodystring | The mail body, HTML. Blocks and translation texts are substituted first, parameter values last and HTML-escaped, so a value can never re-enter the earlier passes. |
translationsdictionary | Texts per locale: keyed by locale, then by the key a {{text:<key>}} slot names. Rendering and sending name one of these locales exactly — an unlisted one is refused rather than matched to a near neighbour. |
parametersarray | The parameters this template or block declares. A send must supply every required one, and the template type's own contract adds further parameters on top. |
namestring | Name of the parameter, written as {{name}} in the subject, the body and any block.min length 1 |
requiredbooleanoptional | Whether a send is refused when this parameter is missing. Defaults to true. Default: true |
fallbackstringoptional | Value substituted when a send omits the parameter. It applies to optional parameters only — a missing required one refuses the send instead, and an optional one without a fallback leaves {{name}} standing in the mail. |
Response
| Name | Description |
|---|---|
idstring | Property id in canonical hexadecimal form, e.g. 0x0800. |
Errors
| Status | Description | Body |
|---|---|---|
403 | You do not have permission to perform this action | |
400 | The template's blocks and its {{block:…}} references disagree, or a locale does not print every required parameter of its type; details names the locale and the parameters. Also raised for a block edit that would break a mapped template. | code: EMAIL_TEMPLATE_NOT_CONFORMANT, details: string |
401 | No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key. | error: string |
500 | The request failed for a reason that is not the caller's to fix. Safe to retry. | error: string, message: string, statusCode: 50 |
Update a template
Merges the given fields and re-checks the merged result; templateType cannot be changed. translations replaces the stored map wholesale, so a locale left out is deleted.
PATCH
/api/v1/email/templates/0d7kq2m9xr4t8vbzPath Parameters
| Name | Description |
|---|---|
templateIdstring | Id of the template. On a send it is the alternative to templateType — exactly one of the two, since a type resolves to a template through the tenant's mapping. |
Body
| Name | Description |
|---|---|
namestringoptional | Name of the addressed item — a block or template, a device method, a recorded event. A method name is limited to letters, digits, _ and -, because it becomes a segment of the message CRN.min length 1 |
categorystringoptional | Free-form label for grouping templates in an editor. Nothing on the rendering path reads it. |
blocksarraystringoptional | Ids of the blocks this template may include. The list is an allowlist: a {{block:…}} naming anything outside it is left in the text exactly as written. |
subjectstringoptional | Subject line, with the same {{block:…}}, {{text:…}} and {{parameter}} slots as the body. Unlike the body it is not HTML, so substituted values are not escaped. |
bodystringoptional | The mail body, HTML. Blocks and translation texts are substituted first, parameter values last and HTML-escaped, so a value can never re-enter the earlier passes. |
translationsdictionaryoptional | Texts per locale: keyed by locale, then by the key a {{text:<key>}} slot names. Rendering and sending name one of these locales exactly — an unlisted one is refused rather than matched to a near neighbour. |
parametersarrayoptional | The parameters this template or block declares. A send must supply every required one, and the template type's own contract adds further parameters on top. |
namestring | Name of the parameter, written as {{name}} in the subject, the body and any block.min length 1 |
requiredbooleanoptional | Whether a send is refused when this parameter is missing. Defaults to true. Default: true |
fallbackstringoptional | Value substituted when a send omits the parameter. It applies to optional parameters only — a missing required one refuses the send instead, and an optional one without a fallback leaves {{name}} standing in the mail. |
Errors
| Status | Description | Body |
|---|---|---|
403 | You do not have permission to perform this action | |
404 | The requested email template could not be found | code: EMAIL_TEMPLATE_NOT_FOUND |
400 | The template's blocks and its {{block:…}} references disagree, or a locale does not print every required parameter of its type; details names the locale and the parameters. Also raised for a block edit that would break a mapped template. | code: EMAIL_TEMPLATE_NOT_CONFORMANT, details: string |
401 | No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key. | error: string |
500 | The request failed for a reason that is not the caller's to fix. Safe to retry. | error: string, message: string, statusCode: 50 |
Delete a template
Refused while the template is mapped to a type; remove the mapping with DELETE /email/mappings/{templateType} first.
DELETE
/api/v1/email/templates/0d7kq2m9xr4t8vbzPath Parameters
| Name | Description |
|---|---|
templateIdstring | Id of the template. On a send it is the alternative to templateType — exactly one of the two, since a type resolves to a template through the tenant's mapping. |
Errors
| Status | Description | Body |
|---|---|---|
403 | You do not have permission to perform this action | |
404 | The requested email template could not be found | code: EMAIL_TEMPLATE_NOT_FOUND |
409 | The block is still declared by a mapped template, or the template is still mapped to a type. blockedBy lists the templates holding a block — drop the mapping, or the reference, first. | code: EMAIL_RESOURCE_IN_USE, details: string |
400 | The request did not match the schema for this endpoint. The details field carries the specific failures. | error: Validation Error |
401 | No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key. | error: string |
500 | The request failed for a reason that is not the caller's to fix. Safe to retry. | error: string, message: string, statusCode: 50 |
List type mappings
Which template each templateType currently resolves to. A type without a mapping is absent, and system mail of that type is then not sent at all.
GET
/api/v1/email/mappingsResponse
| Name | Description |
|---|---|
templateTypeenum | The system mail this template stands for — password_reset, account_validate, user_invite and so on. A mapping binds one template per type and per tenant, which is how the backend's own mails find theirs and how a send may name a type instead of an id.Possible values: password_reset, account_validate, user_invite, system_notification, welcome, password_changed, custom |
templateRistring | Unique Coldwave resource name (CRN) that identifies a resource |
Errors
| Status | Description | Body |
|---|---|---|
403 | You do not have permission to perform this action | |
401 | No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key. | error: string |
500 | The request failed for a reason that is not the caller's to fix. Safe to retry. | error: string, message: string, statusCode: 50 |
Set a type mapping
Points a templateType at one template, replacing any existing mapping. The template's own templateType must equal the path segment, and it must satisfy the type's parameter contract in every locale.
POST
/api/v1/email/mappings/password_resetPath Parameters
| Name | Description |
|---|---|
templateTypeenum | The system mail this template stands for — password_reset, account_validate, user_invite and so on. A mapping binds one template per type and per tenant, which is how the backend's own mails find theirs and how a send may name a type instead of an id.Possible values: password_reset, account_validate, user_invite, system_notification, welcome, password_changed, custom |
Body
| Name | Description |
|---|---|
templateIdstring | Id of the template. On a send it is the alternative to templateType — exactly one of the two, since a type resolves to a template through the tenant's mapping. |
Response
| Name | Description |
|---|---|
resourceIdentifierstring | Unique Coldwave resource name (CRN) that identifies a resource |
Errors
| Status | Description | Body |
|---|---|---|
403 | You do not have permission to perform this action | |
404 | The requested email template could not be found | code: EMAIL_TEMPLATE_NOT_FOUND |
400 | The template's own templateType is not the type it is being mapped to. Map it under its own type; templateType cannot be changed after creation. | code: EMAIL_MAPPING_BAD_REQUEST, details: string |
400 | The template's blocks and its {{block:…}} references disagree, or a locale does not print every required parameter of its type; details names the locale and the parameters. Also raised for a block edit that would break a mapped template. | code: EMAIL_TEMPLATE_NOT_CONFORMANT, details: string |
401 | No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key. | error: string |
500 | The request failed for a reason that is not the caller's to fix. Safe to retry. | error: string, message: string, statusCode: 50 |
Remove a type mapping
Unlinks the template from the type; the template itself stays. System mail of this type stops until another template is mapped.
DELETE
/api/v1/email/mappings/password_resetPath Parameters
| Name | Description |
|---|---|
templateTypeenum | The system mail this template stands for — password_reset, account_validate, user_invite and so on. A mapping binds one template per type and per tenant, which is how the backend's own mails find theirs and how a send may name a type instead of an id.Possible values: password_reset, account_validate, user_invite, system_notification, welcome, password_changed, custom |
Errors
| Status | Description | Body |
|---|---|---|
403 | You do not have permission to perform this action | |
404 | No template is mapped to this type in the tenant. GET /email/mappings lists the types that have one. | code: EMAIL_MAPPING_NOT_FOUND |
400 | The request did not match the schema for this endpoint. The details field carries the specific failures. | error: Validation Error |
401 | No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key. | error: string |
500 | The request failed for a reason that is not the caller's to fix. Safe to retry. | error: string, message: string, statusCode: 50 |
Preview a rendered template
Renders subject and body for one locale without sending. Parameters are passed as further query parameters; year is filled in automatically, a missing required one is refused.
GET
/api/v1/email/render/0d7kq2m9xr4t8vbz?locale=en-GBPath Parameters
| Name | Description |
|---|---|
templateIdstring | Id of the template. On a send it is the alternative to templateType — exactly one of the two, since a type resolves to a template through the tenant's mapping. |
Query Parameters
| Name | Description |
|---|---|
localestring | Pattern: ^[a-z]{2}(-[A-Z]{2})?$ |
Response
| Name | Description |
|---|---|
subjectstring | Subject line, with the same {{block:…}}, {{text:…}} and {{parameter}} slots as the body. Unlike the body it is not HTML, so substituted values are not escaped. |
bodystring | The mail body, HTML. Blocks and translation texts are substituted first, parameter values last and HTML-escaped, so a value can never re-enter the earlier passes. |
Errors
| Status | Description | Body |
|---|---|---|
403 | You do not have permission to perform this action | |
404 | The requested email template could not be found | code: EMAIL_TEMPLATE_NOT_FOUND |
400 | Failed to render the email template | code: EMAIL_RENDER_ERROR, details: string |
401 | No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key. | error: string |
500 | The request failed for a reason that is not the caller's to fix. Safe to retry. | error: string, message: string, statusCode: 50 |
List delivery records
Every attempt this module made to turn a notification into a mail, newest first — one row per recipient, and a single row where a notification reached none. Filtering by intentId follows a single graph firing across all its recipients; degraded marks mail that went out but not as intended.
GET
/api/v1/email/deliveries?page=1&pageSize=100Query Parameters
| Name | Description |
|---|---|
intentIdstringoptional | Exact id of one graph firing — the way to see every recipient of a single notification at once. |
outcomeoptionalenum | Restricts the page to one outcome; failed and degraded are the rows that need attention.Possible values: sent, degraded, dropped, failed |
fromintegeroptional | Lower bound on at, inclusive, UTC epoch milliseconds. |
tointegeroptional | Upper bound on at, inclusive, UTC epoch milliseconds. |
pageintegeroptional | 1-based page number. Rows are ordered newest first, so paging through a range that is still growing can show an entry twice. Default: 1min 1 |
pageSizeintegeroptional | Rows per page, 1 to 1000. Default: 100min 1 · max 1000 |
Response
| Name | Description |
|---|---|
resourceIdentifierstring | Unique Coldwave resource name (CRN) that identifies a resource |
atinteger | Unix timestamp in milliseconds since epoch. |
intentIdstring | The graph's own id for one firing, <node ref>:<template>:<fired at>; a firing produces one row per recipient. |
topicstring | Unique Coldwave resource name (CRN) that identifies a resource |
templateIdstring | The template the notification named, even where no mail was rendered from it. |
recipientstringoptional | The subscriber's CRN, never their address; null for an outcome reached before any recipient was known. |
localestringoptional | Locale of the subscription this row belongs to; null where no subscription was reached. |
outcomeenum | sent, degraded (sent, but not the mail that was meant), dropped (deliberately not sent) or failed.Possible values: sent, degraded, dropped, failed |
reasonstringoptional | Why it was dropped, or what failed — no subscribers on the topic, a lost read permission on the source devices, the hourly cap, or the transport's own message. Absent for a clean sent. |
droppedarray | Parameters that never reached the renderer, one entry each; a non-empty list is what turns an otherwise successful send into degraded. |
namestring | The parameter's name as the graph's author wrote it. |
stageenum | Where it was lost: kernel an {{input.<PORT>}} the graph never fed, firetime a device, meta or property token the cache could not answer, locale a schema or enum token no schema document describes.Possible values: kernel, firetime, locale |
reasonstring | The placeholder that failed, or a plain sentence where there is no token. |
fallbacksarraystring | Parameters filled from their declared fallback because nobody supplied a value. |
unresolvedarraystring | {{…}} still standing in the sent subject or body — parameter, block: and text: alike, since all three leave the token in place. |
Errors
| Status | Description | Body |
|---|---|---|
403 | You do not have permission to perform this action | |
400 | The request did not match the schema for this endpoint. The details field carries the specific failures. | error: Validation Error |
401 | No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key. | error: string |
500 | The request failed for a reason that is not the caller's to fix. Safe to retry. | error: string, message: string, statusCode: 50 |
Send an email
Renders one template and sends it to to; pass exactly one of templateId or templateType, which resolves through the tenant's mapping. A locale the template has no translation for is refused rather than falling back.
POST
/api/v1/emailBody
| Name | Description |
|---|---|
templateIdstringoptional | Id of the template. On a send it is the alternative to templateType — exactly one of the two, since a type resolves to a template through the tenant's mapping. |
templateTypeoptionalenum | The system mail this template stands for — password_reset, account_validate, user_invite and so on. A mapping binds one template per type and per tenant, which is how the backend's own mails find theirs and how a send may name a type instead of an id.Possible values: password_reset, account_validate, user_invite, system_notification, welcome, password_changed, custom |
tostring | Recipient address when sending a mail; on a history query the end of the range instead, in UTC epoch milliseconds, defaulting to now. |
localestring | Pattern: ^[a-z]{2}(-[A-Z]{2})?$ |
paramsdictionaryoptional | Parameters of the call: a device method takes one entry per parameter, each stating its own type; a mail template takes its parameter values keyed by name. Default: {} |
Errors
| Status | Description | Body |
|---|---|---|
403 | You do not have permission to perform this action | |
404 | The requested email template could not be found | code: EMAIL_TEMPLATE_NOT_FOUND |
400 | Failed to render the email template | code: EMAIL_RENDER_ERROR, details: string |
409 | No email configuration exists for this tenant, or one without transport or from; details says which. Set it with PUT /email/config. | code: EMAIL_CONFIG_NOT_SET, details: string |
502 | The mail was rendered and the transport refused it. details carries the transport's own reason — an unverified sender identity, a throttle or a bounce — and is the only text that says what to fix. | code: EMAIL_SEND_FAILED, details: string |
401 | No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key. | error: string |
500 | The request failed for a reason that is not the caller's to fix. Safe to retry. | error: string, message: string, statusCode: 50 |