REST API
Envíe correos electrónicos utilizando la REST API de Mail.Land con referencia completa de solicitud y respuesta
Envíe correos electrónicos transaccionales mediante POST /v1/transactional/send. Esta es la forma principal de enviar correo electrónico a través de MailLand.
Autenticación
Incluya su clave API en el encabezado X-API-Key:
X-API-Key: ml_pet_Ab3Kx9mN2pQ7rT4wL8yD...Las credenciales deben tener el canal Api habilitado y ser credenciales de producto Sending.
Enviar un correo electrónico único
POST https://api.mail.land/v1/transactional/sendcurl -X POST https://api.mail.land/v1/transactional/send \
-H "X-API-Key: ml_pet_Ab3Kx9mN2pQ7rT4wL8yD..." \
-H "Content-Type: application/json" \
-d '{
"from": {"email": "[email protected]"},
"to": [{"email": "[email protected]"}],
"subject": "Hello from MailLand",
"htmlContent": "<p>Your email content here</p>"
}'Cuerpo de la solicitud
{
"from": {
"email": "[email protected]",
"name": "Your App"
},
"to": [
{
"email": "[email protected]",
"name": "Jane Doe",
"variables": {
"firstName": "Jane",
"orderNumber": "ORD-12345"
}
}
],
"subject": "Your order #{{orderNumber}} has shipped",
"htmlContent": "<h1>Hi {{firstName}},</h1><p>Your order #{{orderNumber}} is on its way.</p>",
"textContent": "Hi {{firstName}}, Your order #{{orderNumber}} is on its way.",
"replyTo": "[email protected]",
"tags": ["order-shipped", "transactional"],
"customHeaders": {
"X-Entity-Ref-ID": "order-12345"
},
"tracking": {
"opens": true,
"clicks": true
},
"priority": "High",
"idempotencyKey": "order-12345-shipped-v1"
}Referencia de campos
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
from | object | Sí | Dirección del remitente |
from.email | string | Sí | Correo del remitente (debe estar en un dominio verificado) |
from.name | string | No | Nombre para mostrar |
to | array | Sí | Lista de destinatarios |
to[].email | string | Sí | Dirección de correo del destinatario |
to[].name | string | No | Nombre para mostrar del destinatario |
to[].variables | object | No | Variables de plantilla por destinatario |
subject | string | Condicional | Requerido a menos que se use una plantilla |
templateId | string | No | Usar una plantilla almacenada en lugar de contenido en línea |
htmlContent | string | Condicional | Cuerpo HTML (se requiere htmlContent o textContent si no hay templateId) |
textContent | string | No | Texto plano de respaldo |
variables | object | No | Variables de plantilla globales. Se aplican tanto si se define templateId como si no; ante una clave superpuesta, gana el valor propio del destinatario en to[].variables |
replyTo | string | No | Dirección de correo para respuesta |
tags | array | No | Etiquetas para filtrado en análisis y webhooks |
customHeaders | object | No | Encabezados de correo personalizados (pares clave-valor) |
tracking | object | No | Anular configuración de seguimiento |
tracking.opens | boolean | No | Pedir seguimiento de aperturas para este mensaje (por defecto: desactivado en ambos flujos). Solo se usa mientras el dominio de envío no haya elegido; la elección del propio dominio por flujo, hecha en la página Dominios, prevalece. false siempre lo desactiva |
tracking.clicks | boolean | No | Activar seguimiento de clics (por defecto: false en flujos transaccionales salvo que su cuenta lo active, true en marketing) |
ipPoolId | string | No | Reservado para pools de IP dedicadas. Aún no disponible: enviarlo devuelve 400 IP_POOL_NOT_FOUND. Omítalo. |
scheduledAt | datetime | No | Programar para entrega futura (UTC), hasta 7 días de antelación |
idempotencyKey | string | No | Prevenir envíos duplicados para la misma clave. Prefiere la cabecera X-Idempotency-Key |
attachments | array | No | Archivos adjuntos (máx. 10, máx. 10 MB decodificados en total) |
priority | enum | No | Low, Normal (por defecto), High o Critical |
Archivos adjuntos
Cada archivo adjunto requiere tres campos:
{
"attachments": [
{
"fileName": "invoice.pdf",
"contentType": "application/pdf",
"content": "JVBERi0xLjQKJ...",
"inline": false
},
{
"fileName": "logo.png",
"contentType": "image/png",
"content": "iVBORw0KGgoAAAA...",
"contentId": "company-logo",
"inline": true
}
]
}| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
fileName | string | Sí | Nombre del archivo con extensión |
contentType | string | Sí | Tipo MIME (p.ej. application/pdf) |
content | string | Sí | Contenido del archivo en Base64 estándar: con relleno (padding), sin espacios ni saltos de línea |
contentId | string | No | ID de contenido para archivos adjuntos en línea (referenciado en HTML como cid:company-logo) |
inline | boolean | No | Si es una imagen en línea (por defecto: false) |
Como máximo 10 archivos adjuntos por correo y como máximo 10 MB decodificados (tras Base64) en total. Un contentType ausente, un Base64 no canónico, o superar el número o el tamaño permitido devuelve 400 VALIDATION_FAILED. POST /v1/transactional/send-batch no tiene campo attachments.
Respuesta
Estado: 202 Accepted
{
"success": true,
"jobId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"messageIds": ["t_a1b2c3d4e5f6..."],
"acceptedRecipients": ["[email protected]"],
"rejectedRecipients": [],
"queuedAt": "2026-02-17T10:30:00Z",
"isSandboxed": false
}| Campo | Tipo | Descripción |
|---|---|---|
success | boolean | Si la solicitud fue aceptada |
jobId | string | Identificador único del trabajo para seguimiento |
messageIds | array | IDs de mensaje por destinatario (prefijo t_ para transaccional, m_ para marketing) |
acceptedRecipients | array | Direcciones de correo aceptadas para entrega |
rejectedRecipients | array | Destinatarios rechazados |
queuedAt | datetime | Cuándo se puso en cola el correo |
isSandboxed | boolean | true si la solicitud usó una credencial sandbox (vea Modo Sandbox) |
sandboxReason | string | credential_sandbox_mode (registrado, no entregado) o all_recipients_not_in_sandbox_allowlist (ningún destinatario estaba en la lista de permitidos); ausente en otros casos |
Destinatarios rechazados
Los destinatarios pueden ser rechazados por varias razones:
{
"success": true,
"jobId": "...",
"acceptedRecipients": ["[email protected]"],
"rejectedRecipients": [
{
"email": "[email protected]",
"reason": "Suppressed"
},
{
"email": "[email protected]",
"reason": "domain_typo",
"suggestion": "[email protected]"
}
]
}Razones comunes de rechazo:
Suppressed: la dirección está en la lista de supresión (rebotes/quejas previas)disposable_domain: proveedor de correo desechable detectadodomain_typo: probable error tipográfico en el dominio (incluye camposuggestion)
Verificación previa
Verifique su capacidad de envío antes de realizar la solicitud real:
GET https://api.mail.land/v1/transactional/send/check?recipientCount=1curl https://api.mail.land/v1/transactional/send/check?recipientCount=1 \
-H "X-API-Key: ml_pet_Ab3Kx9mN2pQ7rT4wL8yD..."{
"canSend": true,
"rateLimitStatus": {
"currentSecond": 2,
"limitPerSecond": 50,
"currentHour": 450,
"limitPerHour": 5000,
"currentDay": 8200,
"limitPerDay": 50000
},
"monthlyUsed": 125000,
"monthlyLimit": 500000
}Si canSend es false, el campo reason explica el motivo (límite de velocidad, cuota, estado de la cuenta, etc.).
Idempotencia
Reintentar un envío tras un tiempo de espera de red no debe entregar el correo dos veces. Con una clave de idempotencia, el reintento reproduce la respuesta original en lugar de volver a enviar.
Envía la clave como cabecera (recomendado):
X-Idempotency-Key: order-12345-shipped-v1O usa el campo idempotencyKey del cuerpo. Ambos se aceptan, y puedes enviar ambos solo si los valores son idénticos.
Reglas
- Alcance.
X-Idempotency-Keysolo se acepta enPOST /v1/transactional/sendyPOST /v1/transactional/send-batch. Ningún otro endpoint la lee. - Formato. 1–255 caracteres: letras, dígitos,
_y-. Las claves se comparan exactamente, sin recortes ni cambios de mayúsculas. - Ventana. Una clave permanece reproducible durante 24 horas. Después, la misma clave se trata como una petición nueva.
- Reproducción. Un reintento dentro de la ventana devuelve la respuesta original tal cual (mismos ID, mismo estado) y no envía nada.
- Unicidad. Las claves son únicas por cuenta. Reutilizar una clave en otro endpoint se rechaza con
409; usa una clave distinta por operación.
ID de correlación
Rastree solicitudes entre sistemas incluyendo un ID de correlación:
X-Correlation-ID: my-app-order-12345o
X-Request-ID: my-app-order-12345El ID de correlación aparece en los registros y en el flujo de actividad.
Encabezados de límite de velocidad
Las respuestas exitosas incluyen información de límite de velocidad por IP:
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 295Cuando el motor de políticas de envío aplica el límite (429), un encabezado Retry-After indica los segundos a esperar:
Retry-After: 30Una solicitud transaccional puede incluir hasta 5 destinatarios, y cada uno cuenta en sus límites de velocidad. Si eso supera su límite por segundo (por ejemplo, 1 por segundo en un plan de 60 por minuto), la solicitud se acepta cuando no se aceptó ninguna otra en el mismo segundo; de lo contrario recibe 429 con Retry-After: 1 y tiene éxito al reintentarla en el segundo siguiente, dentro de su límite por minuto. Consulte Límites de Velocidad.
Un 429 a nivel de IP también incluye X-RateLimit-Reset como hora UTC en formato ISO-8601.
Respuestas de error
Todos los errores devuelven un cuerpo JSON:
{
"code": "ERROR_CODE",
"message": "Human-readable description",
"timestamp": "2026-02-17T10:30:00Z",
"traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
}Códigos de error
| Estado | Código | Descripción |
|---|---|---|
400 | INVALID_REQUEST | Cuerpo de solicitud malformado, o scheduledAt con más de 7 días de antelación ("Cannot schedule email more than 7 days in advance") |
400 | VALIDATION_FAILED | Sin htmlContent, textContent ni templateId; o un archivo adjunto no pasa la validación (ver los límites de adjuntos arriba). La respuesta incluye un array errors[] con el campo y la regla que falló. (Anteriormente: MISSING_CONTENT: obsoleto desde 2026-04.) |
413 | CONTENT_TOO_LARGE | "The message is too large to send: it would exceed the platform's message-size limit. Reduce the HTML, text, attachments or recipients.": El mensaje supera el límite de tamaño de la plataforma. POST /v1/transactional/send y send-batch lo devuelven como 413; no se pone nada en cola. Reduzca el mensaje. En la importación CSV es 400 |
400 | transactional_recipient_limit_exceeded | Más de 5 destinatarios en una credencial transaccional (1.000 en marketing) |
400 | TEMPLATE_NOT_FOUND | templateId no nombra ninguna plantilla activa de esta cuenta (también se devuelve para una plantilla eliminada) |
400 | IP_POOL_NOT_FOUND | ipPoolId no nombra ningún pool asignado a esta cuenta (actualmente siempre, ya que aún no se puede asignar ningún pool: ver la referencia de campos arriba) |
422 | TEMPLATE_VARIABLE_MISSING | El contenido tiene un {{placeholder}} sin valor; details.missingVariables lista los nombres. No se encola nada |
400 | IDEMPOTENCY_KEY_INVALID | La clave está vacía, es inválida, supera 255 caracteres, o la cabecera se envió más de una vez |
400 | IDEMPOTENCY_KEY_CONFLICT | Se enviaron a la vez la cabecera y el campo idempotencyKey y difieren |
400 | SEND_FAILED | Todos los destinatarios están suprimidos (mensaje: "All recipients are suppressed") |
409 | IDEMPOTENCY_KEY_REUSED | La clave ya está en uso para otro endpoint de esta cuenta |
401 | UNAUTHORIZED | X-API-Key ausente, inválida, vencida o revocada |
403 | ACCOUNT_SUSPENDED | La cuenta ha sido suspendida |
403 | ACCOUNT_DISABLED | La cuenta ha sido deshabilitada |
403 | ACCOUNT_NOT_ACTIVE | La cuenta no está activa |
403 | NO_ACTIVE_SUBSCRIPTION | La cuenta no tiene una suscripción activa |
403 | SUBSCRIPTION_EXPIRED | La suscripción de la cuenta ha vencido |
403 | IP_NOT_ALLOWED | IP de la solicitud no está en la lista permitida de credenciales |
403 | SENDING_BLOCKED | El envío está bloqueado para esta cuenta |
403 | ACTION_REQUIRED | Se requiere una acción en la cuenta antes de enviar |
403 | TENANT_APPROVAL_REQUIRED | La cuenta está pendiente de revisión antes de poder enviar. Reintentar no ayuda; contacte con soporte |
422 | DOMAIN_NOT_VERIFIED | El dominio del remitente no está verificado |
422 | DOMAIN_NOT_ALLOWED_FOR_CREDENTIAL | Dominio fuera del alcance de las credenciales |
422 | DOMAIN_NOT_FOUND | Dominio del remitente no encontrado |
429 | RATE_LIMIT_EXCEEDED | Se superó el límite de velocidad |
429 | QUOTA_EXCEEDED | Cuota mensual agotada |
429 | WARMUP_LIMIT_EXCEEDED | Se alcanzó el límite diario de arranque de envíos de la cuenta; se restablece a las 00:00 UTC |
429 | DAILY_LIMIT_EXCEEDED | Se alcanzó el límite diario de envío |
429 | WARMUP_POOL_BUDGET_EXCEEDED | "Sending is temporarily paused for today. It resumes automatically at 00:00 UTC; no action is needed." / "Sending is briefly delayed. Please retry in a few minutes." |
429 | WARMUP_POOL_EMPTY | "Sending is temporarily paused. It resumes automatically; no action is needed." |
429 | PLATFORM_DAILY_CAPACITY_REACHED | "Platform sending capacity for today is fully booked. This is a temporary platform-wide limit, not your account quota, and it resets at midnight UTC." |
429 | PLATFORM_CAPACITY_PROTECTION | "New sending is briefly paused to protect platform capacity. Mail already accepted is not affected. This is transient and clears on its own; retry shortly." |
429 | PROBATION_CAP_REACHED | "New accounts can send to N recipients per rolling 24 hours until our team has reviewed the account. Capacity returns as earlier sends age out of the 24-hour window; contact support to request a review." |
429 | PAYMENT_DAILY_LIMIT_EXCEEDED | Límite diario alcanzado en una cuenta con pago vencido |
429 | MARKETING_PAUSED | Envío de marketing en pausa para esta cuenta |
429 | SENDING_BLOCKED | El nivel de verificación de la cuenta no permite este envío (rechazos de sandbox/nivel de verificación; un bloqueo de envío estricto devuelve 403 en su lugar, ver arriba) |
503 | SERVICE_UNAVAILABLE | Servicio de correo temporalmente no disponible |
Límite de destinatarios transaccionales
Las credenciales transaccionales imponen un máximo de 5 destinatarios por solicitud de envío. Esto previene el uso indebido de flujos transaccionales para envíos masivos. Si necesita más destinatarios, use una credencial de Marketing con envío por lotes.
Uso de plantillas
En lugar de HTML en línea, haga referencia a una plantilla almacenada:
{
"from": { "email": "[email protected]" },
"to": [
{
"email": "[email protected]",
"variables": {
"firstName": "Jane",
"resetLink": "https://app.example.com/reset?token=abc123"
}
}
],
"templateId": "t8x2m4q9zk7wpr",
"variables": {
"companyName": "Acme Inc"
}
}El subject proviene de la plantilla. Ante una clave superpuesta, el valor propio del destinatario en to[].variables anula las variables globales: la misma regla que con el contenido en línea. Consulte Plantillas para crear y administrar plantillas.
Próximos pasos
- Envío por lotes: enviar a múltiples destinatarios a la vez
- Relay SMTP: enviar vía SMTP en lugar de REST
- Plantillas: crear plantillas de correo reutilizables
- Correos programados: programar correos para entrega futura