Referencia de la API
POST /api/v1/send es toda la API de envío transaccional — un único endpoint versionado y estable. A diferencia del resto de las rutas de MailInApp (que solo tienen a nuestro propio frontend como llamador y pueden cambiar libremente), esta es un contrato del que depende código de terceros, así que está versionada desde el primer día.
Autenticación
Authorization: Bearer mia_live_...
Una clave faltante o inválida devuelve 401. Las claves se gestionan bajo Desarrolladores en el panel — mira la guía rápida. Una clave revocada deja de funcionar de inmediato.
Solicitud
Content-Type: application/json. Dos formas de solicitud comparten el endpoint — cuál aplica lo decide si projectId está presente.
Formato libre
| Campo | Tipo | Obligatorio | Notas |
| --- | --- | --- | --- |
| to | string | sí | Una sola dirección de destinatario. |
| subject | string | sí | Truncado a 200 caracteres. |
| html | string | sí | Enviado tal cual — sin renderizado, sin etiquetas de combinación, sin pipeline del estudio. |
| text | string | sí | La parte de texto plano. |
Plantilla
| Campo | Tipo | Obligatorio | Notas |
| --- | --- | --- | --- |
| to | string | sí | Una sola dirección de destinatario. |
| projectId | string | sí | Debe ser un proyecto que poseas — la URL del estudio es /studio/<projectId>. |
| mergeData | object | no | Objeto plano { key: value }, hasta 100 campos. Hace las veces de una fila de fuente de datos — se resuelve en las etiquetas de combinación {{field}} del proyecto. Los objetos/arreglos anidados se descartan; null/undefined se convierten en cadenas vacías; todo lo demás se convierte a texto. |
| subject | string | no | Por defecto usa el nombre del proyecto si se omite. Las etiquetas de combinación en el asunto se resuelven desde mergeData. Truncado a 200 caracteres. |
Un cuerpo que no coincida con ninguna de las dos formas (por ejemplo, sin to, o sin html/text ni projectId) devuelve 400.
Campos comunes
| Campo | Tipo | Obligatorio | Notas |
| --- | --- | --- | --- |
| type | "transactional" | "marketing" | no | Por defecto "transactional". Mira abajo. |
| from | object | no | Sobrescritura por llamada de la dirección/nombre del remitente — { "email": string, "name"?: string }. Mira abajo. |
| senderId | string | no | Sobrescritura por llamada eligiendo una de las identidades de remitente guardadas de tu cuenta en lugar de detallar from en línea. Debe pertenecer a tu cuenta, si no, 400. from tiene prioridad si se dan ambos. |
| replyTo | string | no | Dirección Reply-To por llamada. Mira abajo. |
Encabezados
| Encabezado | Obligatorio | Notas |
| --- | --- | --- |
| Authorization | sí | Bearer <apiKey>. |
| Idempotency-Key | no | Mira idempotencia. |
Respuesta
{ "id": "abc123", "status": "sent" }
status es uno de:
| Estado | Significado |
| --- | --- |
| sent | Entregado con éxito a tu método de envío (relé SMTP o envío nativo). |
| suppressed | El destinatario está en tu lista de supresión — mira transaccional vs. marketing. No se envió ningún correo; esto no es un error. |
| failed | El intento de envío falló (por ejemplo, tu relé SMTP no está configurado, o rechazó el mensaje). Un campo error lleva una razón legible por humanos. |
Códigos de error
| Estado | Significado |
| --- | --- |
| 400 | JSON mal formado, una solicitud que no coincide ni con la forma libre ni con la de plantilla, un type inválido, un from inválido o no permitido (mira Dirección de remitente), un replyTo inválido, o (modo plantilla) un projectId que no existe o no es tuyo. También se devuelve cuando la cuenta no tiene un método de envío funcional configurado. |
| 401 | Encabezado Authorization faltante o inválido, o la clave ha sido revocada. |
| 404 | (Modo plantilla) el proyecto no existe o no pertenece a tu cuenta — deliberadamente la misma respuesta que "no existe" para evitar filtrar qué IDs de proyecto son válidos para otras cuentas. |
| 429 | Límite de tasa excedido — mira límites de tasa. |
Se devuelve un 200 con status: "failed" (en lugar de un estado que no sea 2xx) cuando la propia solicitud era válida pero el intento de envío real falló más adelante. Si necesitas distinguir "rechazamos tu solicitud" de "lo intentamos y no salió", revisa status en el cuerpo, no solo el código de estado HTTP.
Transaccional vs. marketing
El campo type controla qué lista de supresión se verifica, reflejando cómo los ESP separan los flujos transaccional y de marketing:
type: "transactional"(por defecto) — omite la supresión por cancelación de suscripción. Un restablecimiento de contraseña o un recibo de pedido no deberían bloquearse solo porque el destinatario canceló la suscripción a tu boletín. Nunca omite la supresión por rebote — una dirección muerta está muerta sin importar la intención.type: "marketing"— se comporta exactamente como un envío de campaña desde el panel: bloqueado tanto por la supresión de cancelación de suscripción como por la de rebote.
Ambos casos devuelven status: "suppressed" en lugar de un error cuando se bloquean.
Dirección de remitente
Por defecto, cada envío usa la identidad de remitente configurada de tu cuenta — la tarjeta "Dirección de remitente" de Configuración → Dominios si estás en envío nativo (SES), o la dirección de remitente configurada de tu relé SMTP en caso contrario. Pasa from para sobrescribirla en una llamada:
{
"to": "[email protected]",
"subject": "Your one-time code",
"html": "<p>Your code is 123456</p>",
"text": "Your code is 123456",
"from": { "email": "[email protected]", "name": "FitConsent Sales Team" }
}
from.email es obligatorio siempre que from esté presente — no hay sobrescritura de solo nombre, así que un valor aquí siempre reemplaza por completo tanto la dirección como el nombre visible para esa llamada. from.name es opcional; omítelo para enviar solo con la dirección simple.
Si tu cuenta envía a través de un dominio verificado (envío nativo/SES), from.email debe ser una dirección de uno de tus propios dominios de envío verificados (por ejemplo, [email protected], no [email protected]) — una cuenta puede verificar más de un dominio, así que cualquiera de ellos está permitido, solo nunca el de alguien más. El envío nativo se ejecuta sobre una única cuenta de AWS de la plataforma compartida entre todos los clientes de MailInApp, así que esta restricción es lo que impide que una cuenta envíe correo que parezca provenir del dominio verificado de otra. En el envío por SMTP no existe tal restricción: el envío sale a través de tu propio relé/credenciales, así que ya es de confianza de la misma forma en que ya lo son las propias reglas de verificación de remitente de tu relé.
senderId (mira la tabla de campos comunes arriba) suele ser la opción más simple cuando ya configuraste una identidad de remitente en el panel. Se resuelve en el propio {email, name, reply-to} de esa identidad sin que tengas que repetirlos en cada llamada.
Un from inválido o no permitido devuelve 400 antes de intentar cualquier envío.
Dirección de Reply-To
Por defecto, las respuestas van a tu Reply-To predeterminado configurado, definido en la página Sending o Domains bajo Configuración según tu método de envío, o a ningún lado si no has configurado uno. Pasa replyTo para sobrescribirlo en una llamada — útil cuando el from visible es una dirección de no-responder pero aun así quieres que una persona vea las respuestas:
{
"to": "[email protected]",
"subject": "Your order shipped",
"html": "<p>Your order is on its way.</p>",
"text": "Your order is on its way.",
"from": { "email": "[email protected]", "name": "FitConsent" },
"replyTo": "[email protected]"
}
A diferencia de from.email, replyTo no tiene restricción de dominio en el envío nativo (SES) — nunca afecta la identidad de envío ni la reputación de entregabilidad, es solo un encabezado que el cliente de correo del destinatario respeta cuando responde. Un replyTo inválido devuelve 400 antes de intentar cualquier envío.
Idempotencia
Pasa un encabezado Idempotency-Key en cualquier llamada que pueda reintentarse — una reentrega de webhook de checkout, un consumidor de cola que vuelve a procesar un mensaje. Una llamada reintentada con la misma clave contra la misma cuenta devuelve el {id, status} de la llamada original sin enviar un segundo correo, incluso si la primera llamada todavía está en curso.
Las claves están vinculadas por cuenta y no tienen vencimiento; reutilizar una clave que ya usaste para un payload diferente es tu propia responsabilidad evitarlo (aun así devolverá el resultado de la primera llamada, no enviará el nuevo payload). Si no pasas una clave, cada llamada envía.
Límites de tasa
Se aplican tres topes independientes:
- Por clave de API: un tope de solicitudes por minuto. Excederlo devuelve
429para esa clave específicamente — otras claves de la misma cuenta no se ven afectadas. - Por cuenta, cuota de la API de envío: tu plan incluye un número de llamadas a la API de envío por mes calendario (500/mes en Free). Excederlo devuelve
429hasta que la cuota se reinicia el día 1. - Por cuenta, volumen de envío: el tope acumulado de volumen de envío mensual de tu plan, compartido entre todas las vías de envío (envíos desde el panel, envíos programados, correos de ciclo de vida, y esta API). Este es el mismo tope que protege el resto del envío de tu cuenta — la API de envío no obtiene un presupuesto separado.
Detalles del modo de plantilla
Un envío en modo de plantilla renderiza el proyecto vinculado exactamente como un envío normal a un destinatario: los tres niveles del motor de alternativas, los bloques interactivos, y un enlace de vista en vivo personal y firmado construido a partir de mergeData en lugar de una fila de contacto almacenada. Todo lo que sigue se comporta igual que en un envío impulsado por el estudio:
- Los votos de encuesta, calificaciones, envíos de formulario y aperturas se registran y aparecen en la vista de Respuestas del proyecto, atribuidos a esta llamada específica de la API en lugar de a una fila de contacto.
- El webhook de tu proyecto se dispara por cada evento de interacción, igual que con cualquier otro destinatario.
- Las llamadas recientes (estado, destinatario, marca de tiempo) se listan en la página del panel Desarrolladores como un registro de auditoría.
Lo único que es distinto de un envío desde el panel: no hay una fila de fuente de datos almacenada detrás de la interacción, así que los cruces de datos de tu propio lado deberían basarse en el identificador de destinatario de la respuesta en lugar de en un índice de fila.