Enviar un correo
/v1/mails/sendPara desarrolladores
Descripción
Envía un mensaje desde su sistema a uno o más destinatarios. La respuesta confirma que el mensaje se aceptó, no que se entregó.
Guía relacionada
- Enviar su primer correo — Haga el primer envío desde su sistema, por la API o por SMTP, con un ejemplo para copiar.
Autenticación
Authorizationbearer tokenheaderobligatorio- API key con scope
mail:send. Formato:Bearer <API_KEY>. X-Instance-Slugstringheaderobligatorio- Identificador de su instancia. Ver Autenticación.
Request
Body (Content-Type: application/json):
{
"subject": "Hello from Fidelizador",
"html": "<p>Hello, <strong>Alice</strong>!</p>",
"text": "Hello, Alice!",
"category": "transactional",
"headers": { "X-Order-Ref": "order-123" }
}
sender_emailstring (email)obligatorio- Dirección de un remitente ya registrado (ver `GET /v1/senders`; el alta es exclusiva del panel web).
toarray de objeto `{email, name?}`, mínimo 1 elementoobligatorio- Destinatarios, visibles para el resto.
template_idinteger | null- ID de una plantilla guardada. Si se omite,
subjectyhtmlpasan a ser requeridos. subjectstring | null- Asunto del mensaje, máx. 254 caracteres. No admite string vacío. Requerido cuando no se envía
template_id. htmlstring | null- Cuerpo HTML. No admite string vacío. Sin límite propio. Ver Límites de tamaño. Requerido cuando no se envía
template_id. Admite enlaces especiales (href="fide:unsubscribe"y otros). textstring | null- Versión en texto plano, que se muestra si el cliente de correo no soporta HTML. No admite string vacío ni tiene límite propio. Admite enlaces especiales escritos como token (
fide:unsubscribe). ccarray de objeto `{email, name?}` | null- Destinatarios en copia, visibles para el resto.
bccarray de objeto `{email, name?}` | null- Destinatarios en copia oculta.
headersobject (string→string) | null- Headers propios, incluidos en el mensaje que recibe el destinatario. Un valor largo se divide en varias líneas y puede quedar codificado (RFC 2047). Máx. 15; nombre ≤ 126 caracteres ASCII imprimibles, valor ≤ 995, y ≤ 996 entre ambos. Los headers que administra la plataforma se rechazan (ver Headers propios).
attachmentsarray de objeto `{type, content, filename}` | null- Adjuntos en base64.
type= MIME type con formatotipo/subtipo,content= base64 estricto,filename= nombre del archivo. Ninguno de los tres admite string vacío. Peso total limitado (ver Límites de tamaño). categorystring | null- Categoría del mensaje, una por llamada, máx. 255 caracteres. Se guarda en un formato simplificado: los acentos se quitan, las mayúsculas pasan a minúsculas y cualquier otro carácter se reemplaza por
-, así queEnvíos Masivosqueda registrada comoenvios-masivos. Se registra en su primer uso; no hace falta crearla antes. reply_toobjeto `{email, name?}` | null- Dirección a la que deben ir las respuestas. Si se omite, las respuestas van al remitente.
test_modeboolean- Si es
true, el mensaje se entrega igual, pero queda marcado como prueba y los reportes lo cuentan comotesten vez de tráfico real. Es el único modo en que se admite una plantilla en borrador. Default:false. custom_msg_idstring | null- Identificador propio del mensaje, máx. 254 caracteres. Se guarda tal cual y se devuelve en los reportes de actividad.
custom_group_codestring | null- Etiqueta de grupo arbitraria (campaña, batch), máx. 254 caracteres.
Response
message_idstringsiempre presente- Identificador del mensaje encolado. Uno por llamada, sin importar cuántos destinatarios lleve.
Un mensaje, un correo por destinatario
Una llamada es un mensaje con un solo message_id. Cada destinatario de to, cc y bcc
genera su propio correo, con un mail_id y un estado independientes: uno puede entregarse y
otro rebotar. Para ver el resultado por destinatario, filtre
GET /v1/mails?msg_id=<message_id>. Cada correo se cobra por separado.
Todas las direcciones de to y cc aparecen en los headers del correo que recibe cada
destinatario, como en cualquier correo con varios destinatarios. Para que no se vean entre sí,
use bcc o envíe una llamada por destinatario.
Errores
| Código | Cuándo |
|---|---|
| 402 | Se alcanzó el límite de envíos del plan para el período, o la instancia no tiene una suscripción activa (plan_send_limit_exceeded / subscription_inactive). |
| 404 | El sender_email no corresponde a un remitente registrado (sender_not_found), o el template_id no existe (mail_template_not_found). |
| 409 | Un remitente de prueba (*.sandbox.test) intentó enviar fuera de sandbox (sandbox_relay_restricted). El dominio del remitente no está verificado (domain_sender_not_verified). La plantilla no está publicada y el envío no lleva test_mode: true (template_not_public). Las plantillas se publican desde el panel. |
| 413 | El body del request excede el tamaño permitido, el contenido comprimido excede el tamaño permitido al descomprimirlo, un adjunto individual de multipart/form-data supera su límite, o el mensaje final excede el tamaño máximo aceptado para envío (request_too_large). |
| 422 | El request no cumple el contrato (validation_error). Faltan subject o html sin template_id, o se enviaron junto con template_id. Se superó un límite: asunto, destinatarios, adjuntos o peso de html/text. Un header está reservado o excede sus límites. Una dirección de email es inválida. Un adjunto no está en base64 válido, su type no tiene formato tipo/subtipo o, en multipart, llegó como campo de texto. Una categoría no tiene letras ni dígitos. |
| 429 | Se superó el límite de tasa. La respuesta incluye Retry-After. |
| 503 | El envío no pudo aceptarse de forma transitoria. Reintente la llamada (service_unavailable). |
Detalle completo en Errores de negocio (plan-send-limit-exceeded), Errores de negocio (subscription-inactive), Errores genéricos (not-found) y Errores genéricos (conflict).
Headers propios
Los headers de headers se incluyen en el mensaje que recibe el destinatario. Es el
mecanismo para correlacionar (X-Order-Ref), hilar conversaciones (In-Reply-To,
References) o pasar cualquier metadato que el cliente de correo deba ver.
Un valor largo se divide en varias líneas y, si no se puede dividir, se codifica según RFC 2047
(=?utf-8?q?...?=): un programa que lea el header sin procesar debe decodificarlo.
La plataforma administra un conjunto que no se puede fijar y que se rechaza con 422: los
que definen la estructura del mensaje (To, From, Cc, Bcc, Subject, Reply-To,
Content-Type, Content-Transfer-Encoding, Content-Disposition, MIME-Version) y los
que la plataforma escribe al entregar (Message-ID, Date, Return-Path, Received,
DKIM-Signature, List-Unsubscribe, List-Unsubscribe-Post, Bounce-To, Sender,
X-Mailer, Priority). El prefijo x-fd- también está reservado. Para esas capacidades, use el campo JSON correspondiente. El mensaje de error indica cuál.
Para hilar una conversación se usan In-Reply-To y References, no Message-ID: ese lo
asigna la plataforma.
Plantillas y contenido
Con template_id, el contenido lo aporta la plantilla. Enviar además subject, html o
text se rechaza con 422 en lugar de descartarse en silencio, para que un error del lado
del integrador no se confunda con un envío correcto.
El body queda entonces sin los campos de contenido. En eso se diferencia del ejemplo de Request, que los lleva porque no usa plantilla:
{
"template_id": 42,
"category": "transactional",
"custom_msg_id": "order-123"
}
El template_id se obtiene de GET /v1/mail-templates. La plantilla
debe estar publicada: una que no lo esté se rechaza con 409 template_not_public, salvo
cuando el envío lleva test_mode: true. Así se puede probar una plantilla antes de publicarla,
y ese envío queda marcado como de prueba en los reportes. Un template_id que no exista en la
instancia se rechaza con 404 mail_template_not_found.
Los cambios en una plantilla, incluida su eliminación, rigen para los envíos siguientes. Por lo general aplican de inmediato y, en casos excepcionales, tardan unos segundos. Los correos ya aceptados conservan el contenido que tenía la plantilla al momento de la llamada.
Límites de tamaño
| Qué | Límite | Qué pasa al excederlo |
|---|---|---|
subject | 254 caracteres | 422 al recibir el request. El límite existe porque un asunto más largo llegaría completo al destinatario pero aparecería truncado en los reportes. |
Destinatarios (to + cc + bcc) | 50 en total, sumando los tres campos | 422. Para llegar a más destinatarios, divida el envío en varias llamadas. Una dirección repetida cuenta cada vez que aparece. |
| Destinatarios repetidos | Sin límite propio | No se rechaza. Una dirección que aparece más de una vez recibe un solo correo y se cobra una sola vez. La comparación no distingue mayúsculas ([email protected] y [email protected] son la misma), y queda el primer campo donde aparece, en el orden to, cc, bcc. |
| Body del request | 20 MB | 413 (request_too_large) antes de procesar el contenido. Aplica al tamaño transferido y, si se envía comprimido, también al resultado de descomprimirlo. |
html, text | 5 MB cada uno | 422. Se mide en bytes UTF-8, no en caracteres: un texto con acentos o emoji ocupa más de un byte por carácter. |
html (recomendado) | bajo 100 KB | No se rechaza. Gmail recorta los mensajes de más de unos 102 KB de HTML y muestra un enlace para ver el resto. |
| Adjuntos (cantidad) | 20 | 422 (validation_error) al recibir el request: el correo no se acepta ni se envía. Cuenta cada elemento de attachments. En multipart/form-data, más de 20 archivos se rechaza con 400. |
| Adjuntos (peso técnico) | 10 MB sumados, ya decodificados | 422 al recibir el request. En multipart/form-data, además, cada archivo individual está acotado a 10 MB y se rechaza con 413. |
| Adjuntos (peso funcional) | 1 MB por defecto, configurable por instancia | El correo se acepta con 200 y se descarta después: termina en estado dropped y no se cobra. Para diagnosticarlo, consulte GET /v1/mails/{mail_id}/logs. Para ajustar el límite, contacte a soporte. |
| Mensaje final | 15 MB | 413. Es el tamaño del correo ya armado, con los adjuntos codificados. Rige igual para el envío por SMTP. |
El umbral de Gmail no es oficial. Para no llegar a él, mantenga el HTML bajo 80 KB. Cuentan el marcado y los estilos, no las imágenes enlazadas.
Los dos límites de adjuntos son distintos y conviven: el técnico es fijo y rechaza el request de entrada; el funcional se ajusta por instancia y opera después, descartando el correo.
Los límites de la tabla no son simultáneos. Cada campo puede llegar a su propio máximo, pero el conjunto comparte el presupuesto del mensaje final: un html de 5 MB junto con 10 MB de adjuntos supera los 15 MB del mensaje y se rechaza con 413. El límite que prevalece en la práctica es siempre el del mensaje.
Por la misma razón, un adjunto por encima de ~10 MB no es entregable: la codificación del mensaje lo agranda alrededor de un 37%, así que supera el máximo del mensaje antes de salir. Solicitar un ajuste del límite funcional por encima de ese valor no cambia el resultado.
Los mismos límites rigen para el envío por SMTP: un asunto de más de 254 caracteres se
rechaza en la sesión SMTP con 554 5.6.0, un mensaje de más de 15 MB se rechaza en la
sesión con 552 5.3.4, y el exceso de adjuntos termina igualmente en dropped.
Consentimiento
Los adjuntos no son el único motivo por el que un correo aceptado con 200 puede descartarse después. Si el remitente está configurado para exigir consentimiento, cada destinatario se evalúa por separado y el correo no sale hacia quien no lo tenga: queda en estado dropped, con el motivo visible en GET /v1/mails/{mail_id}/logs.
Ese motivo puede ser uno de dos: falta un consentimiento que el remitente exige, o el destinatario lo revocó explícitamente.
Los envíos con test_mode no pasan por esta evaluación. Para saber de antemano si un destinatario está habilitado, consulte GET /v1/consent-rules/resolve; el panorama completo está en la suite de consentimiento.
Normalización de direcciones
Las direcciones de sender_email, to, cc, bcc y reply_to se normalizan antes de
guardarse (se aplica la forma canónica del dominio). Una dirección con sintaxis inválida se
rechaza con 422.
Reintentos
El endpoint no es idempotente. No hay header Idempotency-Key ni otro mecanismo que evite duplicados entre llamadas, así que un reintento tras un timeout de red produce un segundo envío.
custom_msg_id no evita duplicados: solo sirve para correlacionar. Dos mensajes con el mismo valor se envían los dos.
Dentro de una misma llamada, en cambio, los destinatarios repetidos reciben un solo correo (ver Límites de tamaño).
Si su sistema reintenta de forma automática, el control de duplicados es suyo. Dos enfoques habituales:
- Registrar del lado del cliente qué mensajes ya se enviaron con éxito, antes de reintentar.
- Tratar un timeout como resultado desconocido: consultar
GET /v1/mailsfiltrando porcustom_msg_idpara averiguar si el envío llegó a registrarse, y recién ahí decidir si reintentar.
- Además de
application/json, el endpoint acepta el mismo body conContent-Encoding: gzip, recomendado para HTML grande o múltiples adjuntos. - También acepta
multipart/form-data, con los adjuntos como binario crudo y sin el aumento de tamaño de base64. En ese formato, los campos array u objeto comoto,ccyheadersvan serializados como string JSON dentro del form. - En
multipart/form-data, cada adjunto va como parte de archivo, con sufilename. Si llega como parte de texto, se rechaza con422. - Para consultar cada correo, obtenga su
mail_idconGET /v1/mails?msg_id=<message_id>y úselo enGET /v1/mails/{mail_id}o enGET /v1/mails/{mail_id}/logs.
curl -X POST "https://$API_HOST/v1/mails/send" \
-H "Authorization: Bearer $API_KEY" \
-H "X-Instance-Slug: $SLUG" \
-H "Content-Type: application/json" \
-d '{
"sender_email": "[email protected]",
"to": [
{
"email": "[email protected]",
"name": "Alice"
}
],
"subject": "Hello from Fidelizador",
"html": "<p>Hello, <strong>Alice</strong>!</p>"
}'
{
"message_id": "01HXYZ..."
}