Enviar un correo transaccional
Admite un correo transaccional para entrega. Nunca llama al proveedor antes de responder: cada dirección queda claimed o suppressed en el momento.
Toda llamada necesita un bearer token de una clave de API activa. Ver Autenticación. Crea o administra claves desde el panel de claves de API.
Parámetros de ruta
Ninguno.
Parámetros de consulta
Ninguno.
Encabezados
AuthorizationstringObligatorioBearer ek_live_<clave>.
Atributos de la solicitud
idempotencyKeystringObligatorioEl mismo valor con el mismo cuerpo responde lo que ya se resolvió, sin reenviar; con un cuerpo distinto responde conflict.
tostring[]OpcionalHasta 50 direcciones de correo. Al menos una dirección tiene que aparecer entre to, cc y bcc.
ccstring[]OpcionalHasta 50 direcciones de correo, la misma regla que to.
bccstring[]OpcionalHasta 50 direcciones de correo, la misma regla que to.
fromstringObligatorioUna dirección de un remitente ya registrado en esta organización — su propio nombre es lo que sale en el encabezado From, nunca un valor por llamada.
replyTostringOpcionalUna dirección de correo. Si se envía, gana; si se omite, se llena con la dirección real del remitente cuando el envío sale sustituido, y queda vacía en caso contrario.
contentobjectObligatorioEl cuerpo del mensaje. Se envía en crudo o se compila desde una plantilla guardada, y type es lo que distingue las dos — nunca las dos a la vez.
Mostrar 2 variantesOcultar 2 variantes
type: "raw"subjectstringObligatorioEl asunto del mensaje. No puede quedar vacío.
htmlstringObligatorioEl cuerpo en HTML, ya compuesto. No se sustituye nada en él.
textstringObligatorioEl cuerpo alternativo en texto plano, para el cliente que no pinte HTML.
type: "template"templateIdstringObligatorioEl id de una plantilla activa de esta organización. Cópialo desde el encabezado de la plantilla o desde el menú de su fila — mira Enviar con una plantilla, más abajo en esta página.
variablesmapa de cadenas a cadenasObligatorioTu propio catálogo para esta llamada — no hay una lista detrás de un envío transaccional, así que las claves que pongas aquí son las únicas que la plantilla tiene para resolver sus etiquetas de fusión.
Atributos de la respuesta
resultsobject[]ObligatorioUna entrada por destinatario — to, cc y bcc por igual.
Mostrar 3 propiedadesOcultar 3 propiedades
addressstringObligatorioLa dirección de ese destinatario.
messageIdstringObligatorioEl id que también acepta GET /v1/email/:messageId.
statusstringObligatorioSolo claimed o suppressed: el enum compartido publica cinco valores, pero el proveedor todavía no fue llamado cuando se arma esta respuesta.
Enviar con una plantilla
En vez de { type: "raw", subject, html, text }, el content puede llevar una plantilla ya
guardada en Templates, con type: "template":
curl -X POST https://api.e-comms.dev/v1/email \
-H "Authorization: Bearer ek_live_<tu-clave>" \
-H "Content-Type: application/json" \
-d '{
"idempotencyKey": "bienvenida-plantilla-ana-2026-09-19",
"to": ["ana@example.com"],
"from": "hola@tuempresa.com",
"content": {
"type": "template",
"templateId": "01a0ba31-6b2c-7a9f-8c3d-2e5f7a1b9c60",
"variables": { "firstName": "Ana", "planName": "Pro" }
}
}'const response = await fetch("https://api.e-comms.dev/v1/email", {
method: "POST",
headers: {
Authorization: "Bearer ek_live_<tu-clave>",
"Content-Type": "application/json",
},
body: JSON.stringify({
idempotencyKey: "bienvenida-plantilla-ana-2026-09-19",
to: ["ana@example.com"],
from: "hola@tuempresa.com",
content: {
type: "template",
templateId: "01a0ba31-6b2c-7a9f-8c3d-2e5f7a1b9c60",
variables: { firstName: "Ana", planName: "Pro" },
},
}),
})
const body = await response.json()import requests
response = requests.post(
"https://api.e-comms.dev/v1/email",
headers={"Authorization": "Bearer ek_live_<tu-clave>"},
json={
"idempotencyKey": "bienvenida-plantilla-ana-2026-09-19",
"to": ["ana@example.com"],
"from": "hola@tuempresa.com",
"content": {
"type": "template",
"templateId": "01a0ba31-6b2c-7a9f-8c3d-2e5f7a1b9c60",
"variables": {"firstName": "Ana", "planName": "Pro"},
},
},
)
body = response.json()La respuesta tiene la misma forma que la de cualquier otro envío.
De dónde sale el templateId
Cópialo desde la propia plantilla. Abre la plantilla en Templates → editar y usa el botón de copiar junto al id, bajo su nombre, o abre el menú de acciones de su fila en la lista de plantillas y elige Copiar ID. Cualquier rol que pueda ver las plantillas puede copiarlo.
Solo una plantilla activa puede usarse para enviar. Una inactiva responde conflict, el
mismo código que una idempotencyKey repetida con otro cuerpo.
Qué responde una variable que falta
Si el documento de la plantilla usa una etiqueta que no está entre las claves de variables,
la llamada responde bad_request, con el nombre de la etiqueta en details:
{
"code": "bad_request",
"message": "Unknown merge tag key: planName",
"details": {
"keys": ["planName"]
},
"requestId": "01a0ba31-3fe4-7cc4-a1c6-64bdaaa5d99c"
}No hay una sustitución parcial ni un valor por defecto silencioso: la plantilla compila entera o no envía nada.
Notas
fromtiene que coincidir exactamente con un remitente, no solo con un dominio: regístralo antes en la configuración de Envío de la app. Un remitente probado con su propio código de un solo uso envía tal cual está escrito; uno cuyo dominio todavía no está autenticado ahí igual envía, conservando la parte local y sustituyendo el dominio, conreplyTollevando la dirección real salvo que la llamada ya haya enviado una.- Límite de solicitudes: 100 por segundo por clave, en todo
/v1. Por encima responde429 rate_limited, sin encabezadoRetry-Aftery sin ningún encabezado de cuota — el límite no se anuncia por adelantado. /v1no responde ningún encabezado CORS. Esta superficie no es invocable desde un navegador, solo desde un servidor a servidor.- El cuerpo es estricto: un campo que no está en esta lista responde
validation_failed, nunca se ignora en silencio. - La
idempotencyKeyes del cuerpo, no un encabezado. La misma clave con el mismo cuerpo devuelve exactamente la respuesta ya resuelta, sin volver a encolar el envío. La misma clave con un cuerpo distinto responde409 conflict. Dos solicitudes concurrentes con la misma clave: la que pierde la carrera también responde409, nunca un envío duplicado.
La referencia de errores trae el resto de los códigos que esta superficie puede responder, incluidos los que ninguna ruta declara por sí sola.
Errores
| Estado | Código | Causa |
|---|---|---|
| 403 | forbidden | La clave no tiene el permiso messages:send. |
| 404 | not_found | templateId no existe para esta organización, o from no nombra ningún remitente de esta organización. |
| 409 | conflict | La idempotencyKey ya se usó con otro cuerpo, la plantilla referenciada no está activa, o el remitente nombrado por from todavía no probó esa dirección. |
| 400 | bad_request | Ninguna dirección entre to, cc y bcc, o el documento de la plantilla usa una etiqueta que no está entre las claves de variables. |
| 402 | payment_required | La organización superó el límite de envíos de su plan. |