Envía un correo saliente por destinatario desde una integración FORM o CONTACT_FORM usando el SMTP outboundMail de esa integración. La llamada se ejecuta de servidor a servidor con X-API-Key y alcance ``transactional.send``.
Esto no es un relay SMTP sin estado. Mailoo crea un ciclo de vida completo de envío dentro de la integración: TransactionalSend, Message (OUTBOUND), sincronización opcional de Subscriber, seguimiento de apertura/clic y webhooks de entrega opcionales. Consulta outbound-smtp{.interpreted-text role="doc"} para ver cómo esto difiere de exponer credenciales SMTP a integradores.
Relacionado: Capa SMTP --- outbound-smtp{.interpreted-text role="doc"}; tokens de cancelación de suscripción --- website-forms{.interpreted-text role="doc"}; OpenAPI --- https://api.mailoo.app/docs/v1.
Arquitectura
- Activador, no relay: Tu backend llama a Mailoo; Mailoo renderiza el mensaje, persiste los registros y envía mediante ``integration.config.outboundMail`` (mismo SMTP que las respuestas del panel y el correo de bienvenida).
- Secretos: Usa ``X-API-Key`` con ``transactional.send`` (o FULL) desde tu servidor o BFF exclusivamente --- nunca en paquetes del navegador.
- Idempotencia: Cada solicitud requiere ``idempotencyKey`` (máx. 256 caracteres, único por integración). El primer éxito devuelve 201; las repeticiones devuelven 200 con
idempotentReplay: true. - Suscriptores: Mailoo actualiza o crea un
Subscriberpara el correo del destinatario. SiunsubscribedAtestá establecido, el envío se omite (409, estadoSKIPPED_UNSUBSCRIBED). - Eventos de entrega: Webhooks salientes opcionales (
delivered,opened,clicked,unsubscribed,bounced) con firma HMAC y reintentos --- consulta Webhooks de entrega más abajo.
Endpoint de envío
``POST {baseUrl}/api/v1/webhooks/transactional/{projectUid}/{integrationId}/send``
Cabeceras: Content-Type: application/json, X-API-Key (alcance ``transactional.send`` o FULL)
Cuerpo (JSON):
- ``idempotencyKey`` (obligatorio) --- clave estable para este envío lógico (p. ej.
daily-2026-06-20-user123) - ``to`` (obligatorio) --- correo del destinatario
- ``name`` (opcional) --- nombre para mostrar; usado para
{{User.name}}/{{firstName}} - ``templateId`` (opcional) --- id de campaña (campaña del panel en esta integración); usa o bien
templateIdo biensubject+text/htmlen línea - ``vars`` (opcional) --- objeto de variables Mustache string/número/booleano (ver abajo)
- ``subject``, ``text``, ``html`` (opcionales) --- contenido en línea cuando no se usa
templateId; subject y al menos uno detextohtmlson obligatorios para envíos en línea
Respuestas:
- 201 --- correo enviado (
status: SENT,idempotentReplay: false) - 200 --- repetición idempotente de un envío anterior
- 409 --- destinatario canceló suscripción (
status: SKIPPED_UNSUBSCRIBED) - 502 --- envío SMTP falló (
status: FAILED;Messagey webhook de entregaBOUNCEDpueden estar registrados) - 400 --- error de validación
- 403 --- la clave API no tiene ``transactional.send``
- 404 --- proyecto/integración no encontrado o no es FORM/CONTACT_FORM
Ejemplo (en línea):
curl -X POST "https://api.mailoo.app/api/v1/webhooks/transactional/{projectUid}/{integrationId}/send" \
-H "Content-Type: application/json" \
-H "X-API-Key: mai_live_..." \
-d '{
"idempotencyKey": "daily-2026-06-20-user1",
"to": "user@example.com",
"name": "Jane Doe",
"subject": "Hi {{firstName}}",
"html": "<p>{{affirmationText}} <a href=\"{{writeUrl}}\">Write</a></p><p><a href=\"{{unsubLink}}\">Unsubscribe</a></p>",
"vars": {
"affirmationText": "You are enough.",
"writeUrl": "https://app.example.com/write"
}
}'
Respuesta de ejemplo (201):
{
"success": true,
"data": {
"sendId": "clx...",
"messageId": "clx...",
"status": "SENT",
"idempotentReplay": false
}
}
Variables Mustache
Las plantillas (campaña templateId o subject/text/html en línea) se renderizan con Mustache antes del envío SMTP.
Contexto integrado:
- ``User.email``, ``User.name`` --- de
to/name - ``firstName`` --- de
vars.firstNameo primer token dename - ``unsubLink`` --- URL de cancelación de suscripción de un clic (secreto de firma por integración +
MAILOO_PUBLIC_APP_URLde la API) - ``affirmationText``, ``writeUrl``, ``streak`` --- de
varscuando se proporcionan (comunes en flujos de retención/recordatorio)
Las claves adicionales de string/número/booleano en vars se fusionan en el contexto.
Seguimiento de apertura y clic
Cuando el cuerpo HTML no está vacío, Mailoo inyecta:
- Un píxel de seguimiento de apertura de 1×1
- Enlaces
https://(y//) reescritos mediante redirecciones de seguimiento de clic
Rutas públicas (sin clave API):
- ``GET /api/v1/public/transactional/open/{trackingToken}`` --- devuelve GIF transparente; registra la primera apertura
- ``GET /api/v1/public/transactional/click/{trackingToken}?u={urlencodedTarget}`` --- registra el clic, redirección 302 al destino
Establece ``MAILOO_PUBLIC_API_URL`` en el host de la API para que las URLs de píxel y clic apunten a tu API pública. Establece ``MAILOO_PUBLIC_APP_URL`` cuando los enlaces de cancelación de suscripción y de la aplicación deban apuntar al origen del panel de control.
Webhooks de entrega
Cuando están configurados, Mailoo hace POST de eventos JSON a tu endpoint de notificación.
Configuración (por integración):
integration.config.deliveryWebhook.url y .secret --- se configuran en el panel de control (Webhooks de entrega en la pestaña Setup de la integración) o mediante PATCH …/integrations/{id} con deliveryWebhook.
Eventos: delivered (después de SMTP exitoso), opened, clicked, unsubscribed (después del webhook de cancelación), bounced (fallo SMTP).
Estructura del payload (JSON):
{
"event": "delivered",
"integrationId": "...",
"messageId": "...",
"idempotencyKey": "...",
"recipientEmail": "user@example.com",
"timestamp": "2026-06-20T12:00:00.000Z",
"metadata": {}
}
Firma: Cabecera ``X-Mailoo-Signature: t={unixSeconds},v1={hex}`` donde v1 es HMAC-SHA256 de "{timestamp}.{rawBody}" usando el secreto del webhook. Cabecera ``X-Mailoo-Event`` repite el nombre del enum (p. ej. DELIVERED).
Reintentos: El worker de bandeja de salida (intervalo de 10 s en el proceso de la API) reintenta entregas fallidas hasta 8 intentos con retroceso exponencial (máx. 5 minutos). Establece ``DISABLE_DELIVERY_WEBHOOK_WORKER=1`` solo para pruebas o si ejecutas un procesador separado.
Requisitos previos
- Integración FORM o CONTACT_FORM con estado ACTIVE
- ``outboundMail`` configurado (panel de control → Conexión y ajustes → Correo saliente)
- ``MAIL_SMTP_ENCRYPTION_KEY`` en la API cuando la contraseña SMTP o el secreto de cancelación de suscripción se almacena cifrado
- Clave API RESTRICTED o FULL con ``transactional.send``
Firma de cancelación de suscripción
Cada integración FORM/CONTACT_FORM almacena un secreto HMAC de cancelación de suscripción cifrado en integration.config. Mailoo genera {{unsubLink}} automáticamente en las plantillas transaccionales. Los propietarios pueden rotar el secreto en el panel de control (invalida los enlaces del correo ya enviado). Los backends externos que firmen sus propios enlaces necesitan el texto plano rotado una vez, o deberían llamar a ``POST /api/v1/webhooks/unsubscribe`` con una clave API en su lugar.