Skip to content

Email providers ​

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/region email provider registry (SMTP / log).

Endpoints ​

MethodPathSummary
GET/v1/email/providersEmail provider registry
POST/v1/email/providersRegister an email provider
GET/v1/email/providers/{id}Fetch one email provider
PATCH/v1/email/providers/{id}Update an email provider (scope immutable)
POST/v1/email/providers/{id}/retireRetire an email provider (rows never delete)

Email provider registry ​

http
GET /v1/email/providers

Responses

200 ​

Own rows + inherited empresa-wide rows.

FieldTypeRequiredDescription
email_providersarray of EmailProvideryes
email_providers[].idstring (uuid)no
email_providers[].account_idstring (uuid)no
email_providers[].tenant_idstring (uuid) | nullnonull = empresa-wide provider inherited by every sede.
email_providers[].scopeempresa · sedenoDerived from tenant_id.
email_providers[].adaptersmtp · logno
email_providers[].regionus · mx · euno
email_providers[].namestringno
email_providers[].from_addressstringno
email_providers[].hoststringno
email_providers[].portintegerno
email_providers[].usernamestringno
email_providers[].has_secretbooleannoThe SMTP secret NEVER rides the wire — this is the only signal a credential is stored.
email_providers[].has_webhook_secretbooleannoThe event-signing key NEVER rides the wire — this is the only signal one is stored.
email_providers[].statusactive · disabled · retiredno
email_providers[].created_atstring (date-time)no
email_providers[].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"
  }
}

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"
  }
}

Register an email provider ​

http
POST /v1/email/providers

Same conventions as /v1/sms/providers: scope: "empresa" creates an account-wide row and needs an account_admin (a member or a machine key gets 403); account_id/tenant_id are injected from the session. smtp needs host (a DNS name or IPv4 literal that passes the SSRF guard, re-checked at every send), port 465 or 587, username and secret. secret is WRITE-ONLY: responses carry has_secret/has_webhook_secret, never the secrets. The log adapter is refused unless the deployment allows it. There is no fallback provider: a tenant with no active row does not send. Retire-only — no DELETE route exists.

Request body (JSON, required)

FieldTypeRequiredDescription
adaptersmtp · logyes
regionus · mx · euyes
namestringyes
from_addressstring (email)yes
hoststringno
port465 · 587no
usernamestringno
secretstringnoWrite-only. SMTP password.
webhook_secretstringnoWrite-only. HMAC key of the provider's signed events; responses carry has_webhook_secret.
statusactive · disabled · retiredno
scopesede · empresanoRequest-only field; responses derive it from tenant_id.
json
{
  "adapter": "smtp",
  "region": "us",
  "name": "example-email-provider",
  "from_address": "[email protected]",
  "host": "smtp.example.com",
  "port": 587,
  "username": "example-user",
  "secret": "example-smtp-password-not-a-real-one"
}

Responses

201 ​

Registered provider (has_secret, never the secret).

FieldTypeRequiredDescription
email_providerobject (EmailProvider)yes
email_provider.idstring (uuid)no
email_provider.account_idstring (uuid)no
email_provider.tenant_idstring (uuid) | nullnonull = empresa-wide provider inherited by every sede.
email_provider.scopeempresa · sedenoDerived from tenant_id.
email_provider.adaptersmtp · logno
email_provider.regionus · mx · euno
email_provider.namestringno
email_provider.from_addressstringno
email_provider.hoststringno
email_provider.portintegerno
email_provider.usernamestringno
email_provider.has_secretbooleannoThe SMTP secret NEVER rides the wire — this is the only signal a credential is stored.
email_provider.has_webhook_secretbooleannoThe event-signing key NEVER rides the wire — this is the only signal one is stored.
email_provider.statusactive · disabled · retiredno
email_provider.created_atstring (date-time)no
email_provider.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"
  }
}

Fetch one email provider ​

http
GET /v1/email/providers/{id}

Parameters

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

Responses

200 ​

The provider.

FieldTypeRequiredDescription
email_providerobject (EmailProvider)yes
email_provider.idstring (uuid)no
email_provider.account_idstring (uuid)no
email_provider.tenant_idstring (uuid) | nullnonull = empresa-wide provider inherited by every sede.
email_provider.scopeempresa · sedenoDerived from tenant_id.
email_provider.adaptersmtp · logno
email_provider.regionus · mx · euno
email_provider.namestringno
email_provider.from_addressstringno
email_provider.hoststringno
email_provider.portintegerno
email_provider.usernamestringno
email_provider.has_secretbooleannoThe SMTP secret NEVER rides the wire — this is the only signal a credential is stored.
email_provider.has_webhook_secretbooleannoThe event-signing key NEVER rides the wire — this is the only signal one is stored.
email_provider.statusactive · disabled · retiredno
email_provider.created_atstring (date-time)no
email_provider.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"
  }
}

Update an email provider (scope immutable) ​

http
PATCH /v1/email/providers/{id}

Editing an empresa-wide row takes an account_admin session, as creating it does. A retired row is terminal. scope is not accepted here: it is 400.

Parameters

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

Request body (JSON, required)

FieldTypeRequiredDescription
adaptersmtp · logno
regionus · mx · euno
namestringno
from_addressstring (email)no
hoststringno
port465 · 587no
usernamestringno
secretstringnoWrite-only.
webhook_secretstring,nullnoWrite-only. A new value rotates the key; null or an empty string clears it, so the row stops accepting events.
statusactive · disabled · retiredno
json
{
  "name": "postman-email-renamed",
  "status": "disabled"
}

Responses

200 ​

Updated provider.

FieldTypeRequiredDescription
email_providerobject (EmailProvider)yes
email_provider.idstring (uuid)no
email_provider.account_idstring (uuid)no
email_provider.tenant_idstring (uuid) | nullnonull = empresa-wide provider inherited by every sede.
email_provider.scopeempresa · sedenoDerived from tenant_id.
email_provider.adaptersmtp · logno
email_provider.regionus · mx · euno
email_provider.namestringno
email_provider.from_addressstringno
email_provider.hoststringno
email_provider.portintegerno
email_provider.usernamestringno
email_provider.has_secretbooleannoThe SMTP secret NEVER rides the wire — this is the only signal a credential is stored.
email_provider.has_webhook_secretbooleannoThe event-signing key NEVER rides the wire — this is the only signal one is stored.
email_provider.statusactive · disabled · retiredno
email_provider.created_atstring (date-time)no
email_provider.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"
  }
}

Retire an email provider (rows never delete) ​

http
POST /v1/email/providers/{id}/retire

Retiring an empresa-wide row takes an account_admin session.

Parameters

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

Responses

200 ​

The retired provider.

FieldTypeRequiredDescription
email_providerobject (EmailProvider)yes
email_provider.idstring (uuid)no
email_provider.account_idstring (uuid)no
email_provider.tenant_idstring (uuid) | nullnonull = empresa-wide provider inherited by every sede.
email_provider.scopeempresa · sedenoDerived from tenant_id.
email_provider.adaptersmtp · logno
email_provider.regionus · mx · euno
email_provider.namestringno
email_provider.from_addressstringno
email_provider.hoststringno
email_provider.portintegerno
email_provider.usernamestringno
email_provider.has_secretbooleannoThe SMTP secret NEVER rides the wire — this is the only signal a credential is stored.
email_provider.has_webhook_secretbooleannoThe event-signing key NEVER rides the wire — this is the only signal one is stored.
email_provider.statusactive · disabled · retiredno
email_provider.created_atstring (date-time)no
email_provider.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"
  }
}

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