Saltar al contenido principal

Enviar un correo

POST/v1/mails/send

Para 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):

{
"sender_email": "[email protected]",
"to": [{ "email": "[email protected]", "name": "Alice" }],
"subject": "Hello from Fidelizador",
"html": "<p>Hello, <strong>Alice</strong>!</p>",
"text": "Hello, Alice!",
"category": "transactional",
"reply_to": { "email": "[email protected]", "name": "Soporte" },
"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, subject y html pasan 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 formato tipo/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í que Envíos Masivos queda registrada como envios-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 como test en 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​

200 OKDevuelve un objeto con los campos que siguen.
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ódigoCuándo
402Se 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).
404El sender_email no corresponde a un remitente registrado (sender_not_found), o el template_id no existe (mail_template_not_found).
409Un 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.
413El 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).
422El 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.
429Se superó el límite de tasa. La respuesta incluye Retry-After.
503El 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:

{
"sender_email": "[email protected]",
"to": [{ "email": "[email protected]", "name": "Alice" }],
"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ímiteQué pasa al excederlo
subject254 caracteres422 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 campos422. Para llegar a más destinatarios, divida el envío en varias llamadas. Una dirección repetida cuenta cada vez que aparece.
Destinatarios repetidosSin límite propioNo 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 request20 MB413 (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, text5 MB cada uno422. 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 KBNo 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)20422 (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 decodificados422 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 instanciaEl 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 final15 MB413. 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/mails filtrando por custom_msg_id para averiguar si el envío llegó a registrarse, y recién ahí decidir si reintentar.
Notas
  • Además de application/json, el endpoint acepta el mismo body con Content-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 como to, cc y headers van serializados como string JSON dentro del form.
  • En multipart/form-data, cada adjunto va como parte de archivo, con su filename. Si llega como parte de texto, se rechaza con 422.
  • Para consultar cada correo, obtenga su mail_id con GET /v1/mails?msg_id=<message_id> y úselo en GET /v1/mails/{mail_id} o en GET /v1/mails/{mail_id}/logs.
Request
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>"
}'
Response
{
"message_id": "01HXYZ..."
}