Correo transaccional (para integradores)

Última actualización: Aug 31, 2026Sección: Integraciones

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 Subscriber para el correo del destinatario. Si unsubscribedAt está establecido, el envío se omite (409, estado SKIPPED_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 templateId o bien subject + text/html en 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 de text o html son 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; Message y webhook de entrega BOUNCED pueden 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.firstName o primer token de name
  • ``unsubLink`` --- URL de cancelación de suscripción de un clic (secreto de firma por integración + MAILOO_PUBLIC_APP_URL de la API)
  • ``affirmationText``, ``writeUrl``, ``streak`` --- de vars cuando 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.