Skip to content

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.

GETRead the email configuration/api/v1/email/config
PUTSet the email configuration/api/v1/email/config
GETList blocks/api/v1/email/blocks
GETRead one block/api/v1/email/blocks/:blockId
POSTCreate a block/api/v1/email/blocks
PATCHUpdate a block/api/v1/email/blocks/:blockId
DELETEDelete a block/api/v1/email/blocks/:blockId
GETList templates/api/v1/email/templates
GETRead one template/api/v1/email/templates/:templateId
GETList a template's parameters/api/v1/email/templates/:templateId/parameters
POSTCreate a template/api/v1/email/templates
PATCHUpdate a template/api/v1/email/templates/:templateId
DELETEDelete a template/api/v1/email/templates/:templateId
GETList type mappings/api/v1/email/mappings
POSTSet a type mapping/api/v1/email/mappings/:templateType
DELETERemove a type mapping/api/v1/email/mappings/:templateType
GETPreview a rendered template/api/v1/email/render/:templateId
GETList delivery records/api/v1/email/deliveries
POSTSend 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/config

Response

NameDescription
transportoptionalHow mail leaves the backend — an SMTP server or AWS SES, told apart by type. Absent means nothing is sent at all.
Alternative 1objectSMTP transport — host, port and credentials of the server the backend hands each message to.
typeDiscriminator of the transport union; smtp selects this variant.
hoststringA hostname or host:port combination
min length 1
portintegeroptionalPort of the SMTP server; 587 for STARTTLS, 465 together with secure.
Default: 587
> 0
securebooleanoptionalConnect over TLS immediately — true for port 465, false to upgrade the plain connection via STARTTLS.
Default: false
authobjectoptionalCredentials for the SMTP server; omit for a server that asks for none.
userstringLogin name on the SMTP server.
passstringPassword on the SMTP server. GET /email/config returns it as ***, so write a real value back, never the mask.
tlsobjectoptionalTLS options for the connection; omit to keep the defaults.
rejectUnauthorizedbooleanoptionalWhether a certificate that does not validate aborts the connection. False accepts a self-signed certificate — and every other one just as well.
Default: true
poolbooleanoptionalKeep connections open and reuse them; maxConnections and maxMessages take effect only while this is true.
Default: false
maxConnectionsintegeroptionalUpper bound of simultaneously open connections while pooling.
Default: 5
> 0
maxMessagesintegeroptionalHow many messages one pooled connection carries before it is replaced.
Default: 100
> 0
Alternative 2objectAWS SES transport — the backend calls SESv2 itself instead of talking to a mail server.
typeDiscriminator of the transport union; ses selects this variant.
regionstringAWS region the SES client is created for.
min length 1
endpointstringoptionalReplaces the endpoint the SDK would derive from region; omit for the regional one.
min length 1
credentialsobjectoptionalStatic AWS credentials. Omit them together with resolveCredentialsFromInstanceProfile to let the SDK resolve credentials on its own.
accessKeyIdstringAccess key id of the IAM identity that sends.
min length 1
secretAccessKeystringSecret access key of that identity. GET /email/config returns it as ***, so write a real value back, never the mask.
min length 1
resolveCredentialsFromInstanceProfilebooleanoptionalTake credentials from the EC2 instance metadata; when set it wins over anything given in credentials.
Default: false
fromstringoptionalDefault sender address (e.g. App Name <noreply@example.com>)

Errors

StatusDescriptionBody
403You do not have permission to perform this action
409No 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
401No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key.error: string
500The 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/config

Body

NameDescription
transportoptionalHow mail leaves the backend — an SMTP server or AWS SES, told apart by type. Absent means nothing is sent at all.
Alternative 1objectSMTP transport — host, port and credentials of the server the backend hands each message to.
typeDiscriminator of the transport union; smtp selects this variant.
hoststringA hostname or host:port combination
min length 1
portintegeroptionalPort of the SMTP server; 587 for STARTTLS, 465 together with secure.
Default: 587
> 0
securebooleanoptionalConnect over TLS immediately — true for port 465, false to upgrade the plain connection via STARTTLS.
Default: false
authobjectoptionalCredentials for the SMTP server; omit for a server that asks for none.
userstringLogin name on the SMTP server.
passstringPassword on the SMTP server. GET /email/config returns it as ***, so write a real value back, never the mask.
tlsobjectoptionalTLS options for the connection; omit to keep the defaults.
rejectUnauthorizedbooleanoptionalWhether a certificate that does not validate aborts the connection. False accepts a self-signed certificate — and every other one just as well.
Default: true
poolbooleanoptionalKeep connections open and reuse them; maxConnections and maxMessages take effect only while this is true.
Default: false
maxConnectionsintegeroptionalUpper bound of simultaneously open connections while pooling.
Default: 5
> 0
maxMessagesintegeroptionalHow many messages one pooled connection carries before it is replaced.
Default: 100
> 0
Alternative 2objectAWS SES transport — the backend calls SESv2 itself instead of talking to a mail server.
typeDiscriminator of the transport union; ses selects this variant.
regionstringAWS region the SES client is created for.
min length 1
endpointstringoptionalReplaces the endpoint the SDK would derive from region; omit for the regional one.
min length 1
credentialsobjectoptionalStatic AWS credentials. Omit them together with resolveCredentialsFromInstanceProfile to let the SDK resolve credentials on its own.
accessKeyIdstringAccess key id of the IAM identity that sends.
min length 1
secretAccessKeystringSecret access key of that identity. GET /email/config returns it as ***, so write a real value back, never the mask.
min length 1
resolveCredentialsFromInstanceProfilebooleanoptionalTake credentials from the EC2 instance metadata; when set it wins over anything given in credentials.
Default: false
fromstringoptionalDefault sender address (e.g. App Name <noreply@example.com>)

Response

NameDescription
resourceIdentifierstringUnique Coldwave resource name (CRN) that identifies a resource

Errors

StatusDescriptionBody
403You do not have permission to perform this action
400The request did not match the schema for this endpoint. The details field carries the specific failures.error: Validation Error
401No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key.error: string
500The 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/blocks

Response

NameDescription
idstringResource 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}$
namestringHuman-readable block name
min length 1
contentstringHTML content of the block. May contain {{param}} placeholders.
parametersarrayoptionalParameters expected by this block's content
Default: []
namestringName of the parameter, written as {{name}} in the content.
min length 1
requiredbooleanoptionalWhen true, a send without this parameter is refused; fallback is consulted for optional ones only.
Default: true
fallbackstringoptionalValue 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.
originobjectoptionalSet 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.
keystringStable key of the shipped default; unchanged even when the tenant renames the block.
min length 1
versionintegerVersion of the definition this copy was written from. A lower one is replaced at boot, unless the tenant has edited the block since.
> 0
createdBystringUnique Coldwave resource name (CRN) that identifies a resource
createdAtintegerUnix timestamp in milliseconds when this resource was created.
min 0
updatedAtintegeroptionalUnix timestamp in milliseconds when this resource was last modified.
min 0

Errors

StatusDescriptionBody
403You do not have permission to perform this action
401No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key.error: string
500The 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/0d7kq2m9xr4t9c5h

Path Parameters

NameDescription
blockIdstringId of the content block. A template refers to it as {{block:<id>}}, so renaming the block leaves every reference intact.

Response

NameDescription
idstringResource 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}$
namestringHuman-readable block name
min length 1
contentstringHTML content of the block. May contain {{param}} placeholders.
parametersarrayoptionalParameters expected by this block's content
Default: []
namestringName of the parameter, written as {{name}} in the content.
min length 1
requiredbooleanoptionalWhen true, a send without this parameter is refused; fallback is consulted for optional ones only.
Default: true
fallbackstringoptionalValue 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.
originobjectoptionalSet 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.
keystringStable key of the shipped default; unchanged even when the tenant renames the block.
min length 1
versionintegerVersion of the definition this copy was written from. A lower one is replaced at boot, unless the tenant has edited the block since.
> 0
createdBystringUnique Coldwave resource name (CRN) that identifies a resource
createdAtintegerUnix timestamp in milliseconds when this resource was created.
min 0
updatedAtintegeroptionalUnix timestamp in milliseconds when this resource was last modified.
min 0

Errors

StatusDescriptionBody
403You do not have permission to perform this action
404The requested email block could not be foundcode: EMAIL_BLOCK_NOT_FOUND
400The request did not match the schema for this endpoint. The details field carries the specific failures.error: Validation Error
401No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key.error: string
500The 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/blocks

Body

NameDescription
namestringName 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
contentstringThe 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.
parametersarrayoptionalThe 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: []
namestringName of the parameter, written as {{name}} in the subject, the body and any block.
min length 1
requiredbooleanoptionalWhether a send is refused when this parameter is missing. Defaults to true.
Default: true
fallbackstringoptionalValue 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

NameDescription
idstringProperty id in canonical hexadecimal form, e.g. 0x0800.

Errors

StatusDescriptionBody
403You do not have permission to perform this action
400The request did not match the schema for this endpoint. The details field carries the specific failures.error: Validation Error
401No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key.error: string
500The 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/0d7kq2m9xr4t9c5h

Path Parameters

NameDescription
blockIdstringId of the content block. A template refers to it as {{block:<id>}}, so renaming the block leaves every reference intact.

Body

NameDescription
namestringoptionalName 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
contentstringoptionalThe 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.
parametersarrayoptionalThe 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: []
namestringName of the parameter, written as {{name}} in the subject, the body and any block.
min length 1
requiredbooleanoptionalWhether a send is refused when this parameter is missing. Defaults to true.
Default: true
fallbackstringoptionalValue 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

StatusDescriptionBody
403You do not have permission to perform this action
404The requested email block could not be foundcode: EMAIL_BLOCK_NOT_FOUND
400The 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
401No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key.error: string
500The 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/0d7kq2m9xr4t9c5h

Path Parameters

NameDescription
blockIdstringId of the content block. A template refers to it as {{block:<id>}}, so renaming the block leaves every reference intact.

Errors

StatusDescriptionBody
403You do not have permission to perform this action
404The requested email block could not be foundcode: EMAIL_BLOCK_NOT_FOUND
409The 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
400The request did not match the schema for this endpoint. The details field carries the specific failures.error: Validation Error
401No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key.error: string
500The 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/templates

Response

NameDescription
idstringResource 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}$
namestringHuman-readable template name
min length 1
templateTypeenumWhich 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
categorystringoptionalOptional grouping label (e.g. branding-a, transactional)
blocksarraystringIDs of blocks referenced in the body via {{block:id}} placeholders
subjectstringEmail subject. Supports {{text:key}} and {{param}} placeholders.
bodystringEmail body HTML. Supports {{block:id}}, {{text:key}}, and {{param}} placeholders. Resolved in that order.
translationsdictionaryTranslations keyed by locale for {{text:key}} placeholders
parametersarrayAll parameters expected by this template, including those in referenced blocks
namestringName of the parameter, written as {{name}} in subject and body.
min length 1
requiredbooleanoptionalWhen true, a send without this parameter is refused; fallback is consulted for optional ones only.
Default: true
fallbackstringoptionalValue 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.
originobjectoptionalSet 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.
keystringStable key of the shipped default; unchanged even when the tenant renames the template.
min length 1
versionintegerVersion of the definition this copy was written from. A lower one is replaced at boot, unless the tenant has edited the template since.
> 0
createdBystringUnique Coldwave resource name (CRN) that identifies a resource
createdAtintegerUnix timestamp in milliseconds when this resource was created.
min 0
updatedAtintegeroptionalUnix timestamp in milliseconds when this resource was last modified.
min 0

Errors

StatusDescriptionBody
403You do not have permission to perform this action
401No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key.error: string
500The 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/0d7kq2m9xr4t8vbz

Path Parameters

NameDescription
templateIdstringId 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

NameDescription
idstringResource 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}$
namestringHuman-readable template name
min length 1
templateTypeenumWhich 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
categorystringoptionalOptional grouping label (e.g. branding-a, transactional)
blocksarraystringIDs of blocks referenced in the body via {{block:id}} placeholders
subjectstringEmail subject. Supports {{text:key}} and {{param}} placeholders.
bodystringEmail body HTML. Supports {{block:id}}, {{text:key}}, and {{param}} placeholders. Resolved in that order.
translationsdictionaryTranslations keyed by locale for {{text:key}} placeholders
parametersarrayAll parameters expected by this template, including those in referenced blocks
namestringName of the parameter, written as {{name}} in subject and body.
min length 1
requiredbooleanoptionalWhen true, a send without this parameter is refused; fallback is consulted for optional ones only.
Default: true
fallbackstringoptionalValue 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.
originobjectoptionalSet 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.
keystringStable key of the shipped default; unchanged even when the tenant renames the template.
min length 1
versionintegerVersion of the definition this copy was written from. A lower one is replaced at boot, unless the tenant has edited the template since.
> 0
createdBystringUnique Coldwave resource name (CRN) that identifies a resource
createdAtintegerUnix timestamp in milliseconds when this resource was created.
min 0
updatedAtintegeroptionalUnix timestamp in milliseconds when this resource was last modified.
min 0

Errors

StatusDescriptionBody
403You do not have permission to perform this action
404The requested email template could not be foundcode: EMAIL_TEMPLATE_NOT_FOUND
400The request did not match the schema for this endpoint. The details field carries the specific failures.error: Validation Error
401No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key.error: string
500The 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/parameters

Path Parameters

NameDescription
templateIdstringId 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

NameDescription
namestringParameter name, referenced as {{name}} in content
min length 1
requiredbooleanoptionalIf true, sending fails when this parameter is missing
Default: true
fallbackstringoptionalValue used when parameter is absent and required is false. Empty string removes the placeholder.

Errors

StatusDescriptionBody
403You do not have permission to perform this action
404The requested email template could not be foundcode: EMAIL_TEMPLATE_NOT_FOUND
400The request did not match the schema for this endpoint. The details field carries the specific failures.error: Validation Error
401No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key.error: string
500The 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/templates

Body

NameDescription
namestringName 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
templateTypeenumThe 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
categorystringoptionalFree-form label for grouping templates in an editor. Nothing on the rendering path reads it.
blocksarraystringIds 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.
subjectstringSubject 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.
bodystringThe 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.
translationsdictionaryTexts 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.
parametersarrayThe 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.
namestringName of the parameter, written as {{name}} in the subject, the body and any block.
min length 1
requiredbooleanoptionalWhether a send is refused when this parameter is missing. Defaults to true.
Default: true
fallbackstringoptionalValue 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

NameDescription
idstringProperty id in canonical hexadecimal form, e.g. 0x0800.

Errors

StatusDescriptionBody
403You do not have permission to perform this action
400The 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
401No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key.error: string
500The 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/0d7kq2m9xr4t8vbz

Path Parameters

NameDescription
templateIdstringId 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

NameDescription
namestringoptionalName 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
categorystringoptionalFree-form label for grouping templates in an editor. Nothing on the rendering path reads it.
blocksarraystringoptionalIds 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.
subjectstringoptionalSubject 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.
bodystringoptionalThe 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.
translationsdictionaryoptionalTexts 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.
parametersarrayoptionalThe 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.
namestringName of the parameter, written as {{name}} in the subject, the body and any block.
min length 1
requiredbooleanoptionalWhether a send is refused when this parameter is missing. Defaults to true.
Default: true
fallbackstringoptionalValue 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

StatusDescriptionBody
403You do not have permission to perform this action
404The requested email template could not be foundcode: EMAIL_TEMPLATE_NOT_FOUND
400The 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
401No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key.error: string
500The 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/0d7kq2m9xr4t8vbz

Path Parameters

NameDescription
templateIdstringId 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

StatusDescriptionBody
403You do not have permission to perform this action
404The requested email template could not be foundcode: EMAIL_TEMPLATE_NOT_FOUND
409The 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
400The request did not match the schema for this endpoint. The details field carries the specific failures.error: Validation Error
401No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key.error: string
500The 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/mappings

Response

NameDescription
templateTypeenumThe 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
templateRistringUnique Coldwave resource name (CRN) that identifies a resource

Errors

StatusDescriptionBody
403You do not have permission to perform this action
401No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key.error: string
500The 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_reset

Path Parameters

NameDescription
templateTypeenumThe 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

NameDescription
templateIdstringId 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

NameDescription
resourceIdentifierstringUnique Coldwave resource name (CRN) that identifies a resource

Errors

StatusDescriptionBody
403You do not have permission to perform this action
404The requested email template could not be foundcode: EMAIL_TEMPLATE_NOT_FOUND
400The 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
400The 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
401No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key.error: string
500The 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_reset

Path Parameters

NameDescription
templateTypeenumThe 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

StatusDescriptionBody
403You do not have permission to perform this action
404No template is mapped to this type in the tenant. GET /email/mappings lists the types that have one.code: EMAIL_MAPPING_NOT_FOUND
400The request did not match the schema for this endpoint. The details field carries the specific failures.error: Validation Error
401No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key.error: string
500The 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-GB

Path Parameters

NameDescription
templateIdstringId 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

NameDescription
localestringPattern: ^[a-z]{2}(-[A-Z]{2})?$

Response

NameDescription
subjectstringSubject 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.
bodystringThe 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

StatusDescriptionBody
403You do not have permission to perform this action
404The requested email template could not be foundcode: EMAIL_TEMPLATE_NOT_FOUND
400Failed to render the email templatecode: EMAIL_RENDER_ERROR, details: string
401No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key.error: string
500The 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=100

Query Parameters

NameDescription
intentIdstringoptionalExact id of one graph firing — the way to see every recipient of a single notification at once.
outcomeoptionalenumRestricts the page to one outcome; failed and degraded are the rows that need attention.
Possible values: sent, degraded, dropped, failed
fromintegeroptionalLower bound on at, inclusive, UTC epoch milliseconds.
tointegeroptionalUpper bound on at, inclusive, UTC epoch milliseconds.
pageintegeroptional1-based page number. Rows are ordered newest first, so paging through a range that is still growing can show an entry twice.
Default: 1
min 1
pageSizeintegeroptionalRows per page, 1 to 1000.
Default: 100
min 1 · max 1000

Response

NameDescription
resourceIdentifierstringUnique Coldwave resource name (CRN) that identifies a resource
atintegerUnix timestamp in milliseconds since epoch.
intentIdstringThe graph's own id for one firing, <node ref>:<template>:<fired at>; a firing produces one row per recipient.
topicstringUnique Coldwave resource name (CRN) that identifies a resource
templateIdstringThe template the notification named, even where no mail was rendered from it.
recipientstringoptionalThe subscriber's CRN, never their address; null for an outcome reached before any recipient was known.
localestringoptionalLocale of the subscription this row belongs to; null where no subscription was reached.
outcomeenumsent, degraded (sent, but not the mail that was meant), dropped (deliberately not sent) or failed.
Possible values: sent, degraded, dropped, failed
reasonstringoptionalWhy 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.
droppedarrayParameters that never reached the renderer, one entry each; a non-empty list is what turns an otherwise successful send into degraded.
namestringThe parameter's name as the graph's author wrote it.
stageenumWhere 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
reasonstringThe placeholder that failed, or a plain sentence where there is no token.
fallbacksarraystringParameters 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

StatusDescriptionBody
403You do not have permission to perform this action
400The request did not match the schema for this endpoint. The details field carries the specific failures.error: Validation Error
401No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key.error: string
500The 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/email

Body

NameDescription
templateIdstringoptionalId 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.
templateTypeoptionalenumThe 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
tostringRecipient address when sending a mail; on a history query the end of the range instead, in UTC epoch milliseconds, defaulting to now.
localestringPattern: ^[a-z]{2}(-[A-Z]{2})?$
paramsdictionaryoptionalParameters 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

StatusDescriptionBody
403You do not have permission to perform this action
404The requested email template could not be foundcode: EMAIL_TEMPLATE_NOT_FOUND
400Failed to render the email templatecode: EMAIL_RENDER_ERROR, details: string
409No 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
502The 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
401No valid access token was presented, or the DPoP proof accompanying it was missing, expired or bound to a different key.error: string
500The request failed for a reason that is not the caller's to fix. Safe to retry.error: string, message: string, statusCode: 50