Skip to content

Email templates ​

Generated from DialerDigital/core commit 0fc18008b3c8 — spec openapi/dialer-v1.yaml v0.134.0. Do not edit by hand; see How this reference is built. Download the OpenAPI spec.

Español

Per-tenant e-mail templates (static subject + body with typed variables), append-only versions, account_admin approval and retirement.

Endpoints ​

MethodPathSummary
GET/v1/email-templatesE-mail template versions
POST/v1/email-templatesCreate a draft (the next version when name already exists)
GET/v1/email-templates/{id}One template version
PATCH/v1/email-templates/{id}Edit a draft (subject / body / variables / creditor_names)
POST/v1/email-templates/{id}/approveApprove a draft (account_admin only)
POST/v1/email-templates/{id}/retireRetire a version (account_admin only; a draft is discarded by retiring it)

E-mail template versions ​

http
GET /v1/email-templates

Every version of every e-mail template of the tenant, newest version first per name.

Parameters

NameInTypeRequiredDescription
statusquerydraft · approved · retiredno
namequerystringno

Responses

200 ​

The tenant's template versions.

FieldTypeRequiredDescription
email_templatesarray of EmailTemplateyes
email_templates[].idstring (uuid)no
email_templates[].namestringno
email_templates[].versionintegerno
email_templates[].statusdraft · approved · retiredno
email_templates[].subjectstringno
email_templates[].bodystringno
email_templates[].variablesarray of EmailTemplateVariableno
email_templates[].variables[].namestringyes
email_templates[].variables[].typestring · number · dateyes
email_templates[].variables[].requiredbooleannoAn optional variable that is not sent renders empty.
email_templates[].creditor_namesarray of stringno
email_templates[].approved_bystring (uuid) | nullno
email_templates[].approved_atstring (date-time) | nullno
email_templates[].retired_atstring (date-time) | nullno
email_templates[].created_atstring (date-time)no
email_templates[].updated_atstring (date-time)no

400 — BadRequest ​

Malformed request (shape/type errors, invalid filters, bad timestamps).

json
{
  "error": {
    "code": "bad_request",
    "message": "from must be RFC-3339"
  }
}

401 — Unauthorized ​

Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature). A dashboard session (ddu_) that a newer login of the same person ended answers the code session_superseded instead (tenant flag single_session_enforced), so the screen can say why it was signed out; a logout, an expired or an unknown token stay unauthorized.

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

403 — Forbidden ​

The tenant is suspended. A dashboard session (ddu_) of a tenant whose login policy (ADR 227) is in mode enforce answers 403 with the code login_policy_denied on a request from an address or at an hour the policy does not allow — the same body for both; the session is not revoked and a later request inside the policy goes through. A widget token (ddw_) of a seat is held to the same policy on every request (core#2406, flag widget_login_honors_policy). A synthetic (demo) tenant answers 403 with the code synthetic_tenant on every route that would reach a third party — manual dial, external transfer or conference leg, SMS send (core#2251, ADR #280 addendum). No payment changes it.

json
{
  "error": {
    "code": "forbidden",
    "message": "tenant is suspended"
  }
}

429 — ApiKeyRateLimited ​

This API key is over its request quota, or the tenant's keys together are over the tenant ceiling (core#2503) (code rate_limited). The request was not processed. Wait Retry-After seconds and retry; a refusal is not counted against the budget, so that wait is always enough. Bounded per API key and, above the keys, per tenant (api_tenant_requests_per_minute): a sibling key is unaffected until the tenant's keys together reach that ceiling. The quota applies to every operation reachable with a dd_ key, so the operations that name a more specific 429 (call control, dispositions, SMS, …) can ALSO answer this one with its X-RateLimit-* headers.

json
{
  "error": {
    "code": "rate_limited",
    "message": "API key request quota exceeded; retry after the interval in Retry-After"
  }
}

Create a draft (the next version when name already exists) ​

http
POST /v1/email-templates

The body uses {{variable}} markers; every marker must be declared in variables and every declared variable used. The subject is STATIC text (no markers) and must pass the subject policy against the declared creditor_names: it may not name the debt, an amount or a creditor. The static body must already carry the unsubscribe notice and the collector declaration (the same rules the send enforces). A name that already has versions opens the next version as a new draft: an approved version is never edited. tenant_id, version and status are never body input.

Request body (JSON, required)

FieldTypeRequiredDescription
namestringyesTemplate family; an existing name opens its next version.
subjectstringyesStatic text: no {{}} markers; it may not name the debt, an amount or a creditor.
bodystringyes
variablesarray of EmailTemplateVariableno
variables[].namestringyes
variables[].typestring · number · dateyes
variables[].requiredbooleannoAn optional variable that is not sent renders empty.
creditor_namesarray of stringnoCreditor names the subject policy checks the subject against (trimmed, de-duplicated).
json
{
  "name": "payment-reminder",
  "subject": "Un mensaje importante para usted",
  "body": "Hola {{first_name}}. Este mensaje proviene de una agencia de cobranza. Para dejar de recibir correos, responda BAJA.",
  "variables": [
    {
      "name": "first_name",
      "type": "string"
    }
  ],
  "creditor_names": []
}

Responses

201 ​

The new draft.

FieldTypeRequiredDescription
email_templateobject (EmailTemplate)yes
email_template.idstring (uuid)no
email_template.namestringno
email_template.versionintegerno
email_template.statusdraft · approved · retiredno
email_template.subjectstringno
email_template.bodystringno
email_template.variablesarray of EmailTemplateVariableno
email_template.variables[].namestringyes
email_template.variables[].typestring · number · dateyes
email_template.variables[].requiredbooleannoAn optional variable that is not sent renders empty.
email_template.creditor_namesarray of stringno
email_template.approved_bystring (uuid) | nullno
email_template.approved_atstring (date-time) | nullno
email_template.retired_atstring (date-time) | nullno
email_template.created_atstring (date-time)no
email_template.updated_atstring (date-time)no

400 — BadRequest ​

Malformed request (shape/type errors, invalid filters, bad timestamps).

json
{
  "error": {
    "code": "bad_request",
    "message": "from must be RFC-3339"
  }
}

401 — Unauthorized ​

Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature). A dashboard session (ddu_) that a newer login of the same person ended answers the code session_superseded instead (tenant flag single_session_enforced), so the screen can say why it was signed out; a logout, an expired or an unknown token stay unauthorized.

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

403 — Forbidden ​

The tenant is suspended. A dashboard session (ddu_) of a tenant whose login policy (ADR 227) is in mode enforce answers 403 with the code login_policy_denied on a request from an address or at an hour the policy does not allow — the same body for both; the session is not revoked and a later request inside the policy goes through. A widget token (ddw_) of a seat is held to the same policy on every request (core#2406, flag widget_login_honors_policy). A synthetic (demo) tenant answers 403 with the code synthetic_tenant on every route that would reach a third party — manual dial, external transfer or conference leg, SMS send (core#2251, ADR #280 addendum). No payment changes it.

json
{
  "error": {
    "code": "forbidden",
    "message": "tenant is suspended"
  }
}

409 — Conflict ​

Invalid state transition, uniqueness conflict, or a seat that is busy/reserved.

json
{
  "error": {
    "code": "conflict",
    "message": "invalid state transition"
  }
}

429 — ApiKeyRateLimited ​

This API key is over its request quota, or the tenant's keys together are over the tenant ceiling (core#2503) (code rate_limited). The request was not processed. Wait Retry-After seconds and retry; a refusal is not counted against the budget, so that wait is always enough. Bounded per API key and, above the keys, per tenant (api_tenant_requests_per_minute): a sibling key is unaffected until the tenant's keys together reach that ceiling. The quota applies to every operation reachable with a dd_ key, so the operations that name a more specific 429 (call control, dispositions, SMS, …) can ALSO answer this one with its X-RateLimit-* headers.

json
{
  "error": {
    "code": "rate_limited",
    "message": "API key request quota exceeded; retry after the interval in Retry-After"
  }
}

One template version ​

http
GET /v1/email-templates/{id}

Parameters

NameInTypeRequiredDescription
idpathstring (uuid)yesResource UUID. Malformed or cross-tenant ids read as 404.

Responses

200 ​

The version.

FieldTypeRequiredDescription
email_templateobject (EmailTemplate)yes
email_template.idstring (uuid)no
email_template.namestringno
email_template.versionintegerno
email_template.statusdraft · approved · retiredno
email_template.subjectstringno
email_template.bodystringno
email_template.variablesarray of EmailTemplateVariableno
email_template.variables[].namestringyes
email_template.variables[].typestring · number · dateyes
email_template.variables[].requiredbooleannoAn optional variable that is not sent renders empty.
email_template.creditor_namesarray of stringno
email_template.approved_bystring (uuid) | nullno
email_template.approved_atstring (date-time) | nullno
email_template.retired_atstring (date-time) | nullno
email_template.created_atstring (date-time)no
email_template.updated_atstring (date-time)no

401 — Unauthorized ​

Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature). A dashboard session (ddu_) that a newer login of the same person ended answers the code session_superseded instead (tenant flag single_session_enforced), so the screen can say why it was signed out; a logout, an expired or an unknown token stay unauthorized.

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

403 — Forbidden ​

The tenant is suspended. A dashboard session (ddu_) of a tenant whose login policy (ADR 227) is in mode enforce answers 403 with the code login_policy_denied on a request from an address or at an hour the policy does not allow — the same body for both; the session is not revoked and a later request inside the policy goes through. A widget token (ddw_) of a seat is held to the same policy on every request (core#2406, flag widget_login_honors_policy). A synthetic (demo) tenant answers 403 with the code synthetic_tenant on every route that would reach a third party — manual dial, external transfer or conference leg, SMS send (core#2251, ADR #280 addendum). No payment changes it.

json
{
  "error": {
    "code": "forbidden",
    "message": "tenant is suspended"
  }
}

404 — NotFound ​

Unknown id, malformed (non-UUID) id OR another tenant's id — RLS returns zero rows, so all three are indistinguishable by design (no existence leak, never a 403 for foreign ids).

json
{
  "error": {
    "code": "not_found",
    "message": "resource not found for this tenant"
  }
}

429 — ApiKeyRateLimited ​

This API key is over its request quota, or the tenant's keys together are over the tenant ceiling (core#2503) (code rate_limited). The request was not processed. Wait Retry-After seconds and retry; a refusal is not counted against the budget, so that wait is always enough. Bounded per API key and, above the keys, per tenant (api_tenant_requests_per_minute): a sibling key is unaffected until the tenant's keys together reach that ceiling. The quota applies to every operation reachable with a dd_ key, so the operations that name a more specific 429 (call control, dispositions, SMS, …) can ALSO answer this one with its X-RateLimit-* headers.

json
{
  "error": {
    "code": "rate_limited",
    "message": "API key request quota exceeded; retry after the interval in Retry-After"
  }
}

Edit a draft (subject / body / variables / creditor_names) ​

http
PATCH /v1/email-templates/{id}

Only a draft can change; an approved or retired version answers 409 (version it instead).

Parameters

NameInTypeRequiredDescription
idpathstring (uuid)yesResource UUID. Malformed or cross-tenant ids read as 404.

Request body (JSON, required)

FieldTypeRequiredDescription
subjectstringno
bodystringno
variablesarray of EmailTemplateVariableno
variables[].namestringyes
variables[].typestring · number · dateyes
variables[].requiredbooleannoAn optional variable that is not sent renders empty.
creditor_namesarray of stringno

Responses

200 ​

The updated draft.

FieldTypeRequiredDescription
email_templateobject (EmailTemplate)yes
email_template.idstring (uuid)no
email_template.namestringno
email_template.versionintegerno
email_template.statusdraft · approved · retiredno
email_template.subjectstringno
email_template.bodystringno
email_template.variablesarray of EmailTemplateVariableno
email_template.variables[].namestringyes
email_template.variables[].typestring · number · dateyes
email_template.variables[].requiredbooleannoAn optional variable that is not sent renders empty.
email_template.creditor_namesarray of stringno
email_template.approved_bystring (uuid) | nullno
email_template.approved_atstring (date-time) | nullno
email_template.retired_atstring (date-time) | nullno
email_template.created_atstring (date-time)no
email_template.updated_atstring (date-time)no

400 — BadRequest ​

Malformed request (shape/type errors, invalid filters, bad timestamps).

json
{
  "error": {
    "code": "bad_request",
    "message": "from must be RFC-3339"
  }
}

401 — Unauthorized ​

Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature). A dashboard session (ddu_) that a newer login of the same person ended answers the code session_superseded instead (tenant flag single_session_enforced), so the screen can say why it was signed out; a logout, an expired or an unknown token stay unauthorized.

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

403 — Forbidden ​

The tenant is suspended. A dashboard session (ddu_) of a tenant whose login policy (ADR 227) is in mode enforce answers 403 with the code login_policy_denied on a request from an address or at an hour the policy does not allow — the same body for both; the session is not revoked and a later request inside the policy goes through. A widget token (ddw_) of a seat is held to the same policy on every request (core#2406, flag widget_login_honors_policy). A synthetic (demo) tenant answers 403 with the code synthetic_tenant on every route that would reach a third party — manual dial, external transfer or conference leg, SMS send (core#2251, ADR #280 addendum). No payment changes it.

json
{
  "error": {
    "code": "forbidden",
    "message": "tenant is suspended"
  }
}

404 — NotFound ​

Unknown id, malformed (non-UUID) id OR another tenant's id — RLS returns zero rows, so all three are indistinguishable by design (no existence leak, never a 403 for foreign ids).

json
{
  "error": {
    "code": "not_found",
    "message": "resource not found for this tenant"
  }
}

409 — Conflict ​

Invalid state transition, uniqueness conflict, or a seat that is busy/reserved.

json
{
  "error": {
    "code": "conflict",
    "message": "invalid state transition"
  }
}

429 — ApiKeyRateLimited ​

This API key is over its request quota, or the tenant's keys together are over the tenant ceiling (core#2503) (code rate_limited). The request was not processed. Wait Retry-After seconds and retry; a refusal is not counted against the budget, so that wait is always enough. Bounded per API key and, above the keys, per tenant (api_tenant_requests_per_minute): a sibling key is unaffected until the tenant's keys together reach that ceiling. The quota applies to every operation reachable with a dd_ key, so the operations that name a more specific 429 (call control, dispositions, SMS, …) can ALSO answer this one with its X-RateLimit-* headers.

json
{
  "error": {
    "code": "rate_limited",
    "message": "API key request quota exceeded; retry after the interval in Retry-After"
  }
}

Approve a draft (account_admin only) ​

http
POST /v1/email-templates/{id}/approve

Approves the draft exactly as it stands and records who and when (approved_by, approved_at). Only an account_admin login may approve: a member or a dd_ machine key gets 403. The database enforces it too. A version that is not a draft answers 409.

Parameters

NameInTypeRequiredDescription
idpathstring (uuid)yesResource UUID. Malformed or cross-tenant ids read as 404.

Responses

200 ​

The approved version.

FieldTypeRequiredDescription
email_templateobject (EmailTemplate)yes
email_template.idstring (uuid)no
email_template.namestringno
email_template.versionintegerno
email_template.statusdraft · approved · retiredno
email_template.subjectstringno
email_template.bodystringno
email_template.variablesarray of EmailTemplateVariableno
email_template.variables[].namestringyes
email_template.variables[].typestring · number · dateyes
email_template.variables[].requiredbooleannoAn optional variable that is not sent renders empty.
email_template.creditor_namesarray of stringno
email_template.approved_bystring (uuid) | nullno
email_template.approved_atstring (date-time) | nullno
email_template.retired_atstring (date-time) | nullno
email_template.created_atstring (date-time)no
email_template.updated_atstring (date-time)no

401 — Unauthorized ​

Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature). A dashboard session (ddu_) that a newer login of the same person ended answers the code session_superseded instead (tenant flag single_session_enforced), so the screen can say why it was signed out; a logout, an expired or an unknown token stay unauthorized.

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

403 — Forbidden ​

The tenant is suspended. A dashboard session (ddu_) of a tenant whose login policy (ADR 227) is in mode enforce answers 403 with the code login_policy_denied on a request from an address or at an hour the policy does not allow — the same body for both; the session is not revoked and a later request inside the policy goes through. A widget token (ddw_) of a seat is held to the same policy on every request (core#2406, flag widget_login_honors_policy). A synthetic (demo) tenant answers 403 with the code synthetic_tenant on every route that would reach a third party — manual dial, external transfer or conference leg, SMS send (core#2251, ADR #280 addendum). No payment changes it.

json
{
  "error": {
    "code": "forbidden",
    "message": "tenant is suspended"
  }
}

404 — NotFound ​

Unknown id, malformed (non-UUID) id OR another tenant's id — RLS returns zero rows, so all three are indistinguishable by design (no existence leak, never a 403 for foreign ids).

json
{
  "error": {
    "code": "not_found",
    "message": "resource not found for this tenant"
  }
}

409 — Conflict ​

Invalid state transition, uniqueness conflict, or a seat that is busy/reserved.

json
{
  "error": {
    "code": "conflict",
    "message": "invalid state transition"
  }
}

429 — ApiKeyRateLimited ​

This API key is over its request quota, or the tenant's keys together are over the tenant ceiling (core#2503) (code rate_limited). The request was not processed. Wait Retry-After seconds and retry; a refusal is not counted against the budget, so that wait is always enough. Bounded per API key and, above the keys, per tenant (api_tenant_requests_per_minute): a sibling key is unaffected until the tenant's keys together reach that ceiling. The quota applies to every operation reachable with a dd_ key, so the operations that name a more specific 429 (call control, dispositions, SMS, …) can ALSO answer this one with its X-RateLimit-* headers.

json
{
  "error": {
    "code": "rate_limited",
    "message": "API key request quota exceeded; retry after the interval in Retry-After"
  }
}

Retire a version (account_admin only; a draft is discarded by retiring it) ​

http
POST /v1/email-templates/{id}/retire

Terminal. A retired version can no longer be sent; an already retired one answers 409. Only an account_admin login may retire (a member or a dd_ machine key gets 403): retiring takes an approved template out of use, so it carries the same authority as approving it.

Parameters

NameInTypeRequiredDescription
idpathstring (uuid)yesResource UUID. Malformed or cross-tenant ids read as 404.

Responses

200 ​

The retired version.

FieldTypeRequiredDescription
email_templateobject (EmailTemplate)yes
email_template.idstring (uuid)no
email_template.namestringno
email_template.versionintegerno
email_template.statusdraft · approved · retiredno
email_template.subjectstringno
email_template.bodystringno
email_template.variablesarray of EmailTemplateVariableno
email_template.variables[].namestringyes
email_template.variables[].typestring · number · dateyes
email_template.variables[].requiredbooleannoAn optional variable that is not sent renders empty.
email_template.creditor_namesarray of stringno
email_template.approved_bystring (uuid) | nullno
email_template.approved_atstring (date-time) | nullno
email_template.retired_atstring (date-time) | nullno
email_template.created_atstring (date-time)no
email_template.updated_atstring (date-time)no

401 — Unauthorized ​

Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature). A dashboard session (ddu_) that a newer login of the same person ended answers the code session_superseded instead (tenant flag single_session_enforced), so the screen can say why it was signed out; a logout, an expired or an unknown token stay unauthorized.

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

403 — Forbidden ​

The tenant is suspended. A dashboard session (ddu_) of a tenant whose login policy (ADR 227) is in mode enforce answers 403 with the code login_policy_denied on a request from an address or at an hour the policy does not allow — the same body for both; the session is not revoked and a later request inside the policy goes through. A widget token (ddw_) of a seat is held to the same policy on every request (core#2406, flag widget_login_honors_policy). A synthetic (demo) tenant answers 403 with the code synthetic_tenant on every route that would reach a third party — manual dial, external transfer or conference leg, SMS send (core#2251, ADR #280 addendum). No payment changes it.

json
{
  "error": {
    "code": "forbidden",
    "message": "tenant is suspended"
  }
}

404 — NotFound ​

Unknown id, malformed (non-UUID) id OR another tenant's id — RLS returns zero rows, so all three are indistinguishable by design (no existence leak, never a 403 for foreign ids).

json
{
  "error": {
    "code": "not_found",
    "message": "resource not found for this tenant"
  }
}

409 — Conflict ​

Invalid state transition, uniqueness conflict, or a seat that is busy/reserved.

json
{
  "error": {
    "code": "conflict",
    "message": "invalid state transition"
  }
}

429 — ApiKeyRateLimited ​

This API key is over its request quota, or the tenant's keys together are over the tenant ceiling (core#2503) (code rate_limited). The request was not processed. Wait Retry-After seconds and retry; a refusal is not counted against the budget, so that wait is always enough. Bounded per API key and, above the keys, per tenant (api_tenant_requests_per_minute): a sibling key is unaffected until the tenant's keys together reach that ceiling. The quota applies to every operation reachable with a dd_ key, so the operations that name a more specific 429 (call control, dispositions, SMS, …) can ALSO answer this one with its X-RateLimit-* headers.

json
{
  "error": {
    "code": "rate_limited",
    "message": "API key request quota exceeded; retry after the interval in Retry-After"
  }
}

Nothing in these docs is legal advice — always confirm compliance posture with your own counsel.