Integración de formularios web

Última actualización: Sep 21, 2026Sección: Integraciones

Conecta los formularios de suscripción de tu sitio web a Mailoo. Cada envío válido crea un mensaje entrante y actualiza o crea un suscriptor. Usa una integración FORM y llama a la API desde tu servidor o BFF exclusivamente.

Inicio rápido

  1. Crea un proyecto y una integración de tipo Form en el panel de control de Mailoo (o vía MCP create_integration con type=FORM).
  2. Genera una clave API. Para claves RESTRICTED, incluye webhook.form-submission. Los agentes también pueden configurar Connection & settings, plantillas, suscriptores, campañas y mensajes con herramientas MCP manage_form_* (ámbito form.manage / token MCP) --- ver /development/mailoo-mcp{.interpreted-text role="doc"}.
  3. Almacena la clave y los IDs en variables de entorno del servidor.
  4. Envía los datos del formulario a tu propia ruta; esa ruta los reenvía a Mailoo.
  5. Confirma el mensaje y el suscriptor en el panel de control.

:::: important ::: title Important :::

Solo del lado del servidor. El navegador no debe tener X-API-Key. No pongas credenciales de Mailoo en JavaScript del lado del cliente. ::::

:::: note ::: title Note :::

Legado (no recomendado): Llamar a los webhooks de Mailoo directamente desde el navegador puede funcionar si CORS y los orígenes permitidos están configurados, pero expone la clave API. No uses esto para nuevas integraciones. ::::

Next.js con @mailoo/forms

Para hosts con Next.js App Router, prefiere el paquete @mailoo/forms: fábricas de rutas BFF del mismo origen, cuerpos de envío tipados y hooks del cliente. Consulta website-forms-nextjs-example{.interpreted-text role="doc"}.

Para el registro o la activación de un usuario identificado (no el envío anónimo de boletín), emite eventos de ciclo de vida desde tu servidor --- consulta lifecycle-events{.interpreted-text role="doc"}.

Crear la integración

  1. Ve a Panel de control → Proyectos → [Tu proyecto] → Integraciones.
  2. Crea una nueva integración y selecciona Integración de formulario.
  3. Establece Nombre, Estado (Activo) y Orígenes permitidos opcionales (esquema + host, p. ej. https://example.com). Si la lista está vacía, no se verifica el Origin. La autenticación sigue siendo la clave API. Un BFF que reenvía el Origin del navegador sigue aplicando una lista no vacía.

Cuerpo de la solicitud

Tu servidor envía JSON a Mailoo:

  • email --- obligatorio
  • name --- opcional
  • subject --- opcional (valor por defecto como "New subscription")
  • content --- opcional (valor por defecto como "New subscription from {email}")
  • source, metadata --- opcionales, almacenados con el mensaje

No se requiere una campaña ni una plantilla de mensaje para que el formulario acepte envíos. El mensaje entrante se construye a partir del cuerpo de la solicitud (o valores por defecto).

La redirección del usuario después de un envío exitoso es responsabilidad exclusiva de tu sitio. Mailoo no proporciona una URL de redirección.

Endpoint de la API

POST /api/v1/webhooks/forms/{projectUid}/{integrationId}

Cabeceras: Content-Type: application/json, X-API-Key (obligatorio). Origin opcional cuando usas una lista de orígenes permitidos.

Ejemplo del cuerpo:

{
  "email": "john@example.com",
  "name": "John Doe",
  "source": "https://yoursite.com/newsletter",
  "metadata": {
    "type": "newsletter_subscription"
  }
}

Nuevo suscriptor --- mensaje creado:

{
  "success": true,
  "messageId": "msg_abc123def456",
  "message": "Form submission processed successfully"
}

Ya suscrito (mismo correo) --- sin nuevo mensaje; unsubscribedAt se limpia si es necesario; la respuesta omite messageId:

{
  "success": true,
  "message": "Already subscribed"
}

Los errores de validación devuelven error y message (por ejemplo "Email is required", "Invalid email format").

Ejemplo del lado del servidor (Express)

El formulario envía a tu ruta. Tu ruta llama a Mailoo:

app.post('/api/subscribe', async (req, res) => {
  const { email, name } = req.body
  if (!email || !email.includes('@')) {
    return res.status(400).json({ error: 'Valid email is required' })
  }

  const response = await fetch(
    `${process.env.MAILOO_API_URL}/api/v1/webhooks/forms/${process.env.PROJECT_UID}/${process.env.INTEGRATION_ID}`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-API-Key': process.env.MAILOO_API_KEY,
        Origin: process.env.WEBSITE_ORIGIN || '',
      },
      body: JSON.stringify({
        email: email.trim(),
        name: name?.trim() || undefined,
        source: req.headers.referer || 'server-side-form',
      }),
    }
  )

  const result = await response.json()
  return res.status(response.status).json(result)
})

Variables de entorno (ejemplo):

MAILOO_API_URL=https://api.mailoo.app
PROJECT_UID=your-project-uid
INTEGRATION_ID=your-form-integration-id
MAILOO_API_KEY=your-api-key
WEBSITE_ORIGIN=https://yourdomain.com

Para Next.js, prefiere @mailoo/forms --- consulta website-forms-nextjs-example{.interpreted-text role="doc"}.

Formulario del navegador (envía solo a tu backend)

Campos HTML mínimos: email (obligatorio), name (opcional). JavaScript debe hacer POST de JSON a tu ruta del mismo origen (por ejemplo /api/subscribe), nunca a Mailoo con una clave activa.

SMTP saliente y correo de bienvenida

El correo de bienvenida o respuesta opcional usa SMTP saliente en la integración. No hay respaldo SMTP de la plataforma. Consulta outbound-smtp{.interpreted-text role="doc"} y transactional-email{.interpreted-text role="doc"}.

Suscriptores y plantillas (solo panel de control)

Suscriptores

: Se listan en el panel de control (correo, nombre, fechas). Por fila: Cancelar suscripción, Eliminar o Restaurar para direcciones dadas de baja. Los sitios externos no pueden leer la lista de suscriptores mediante la API; solo envían a través del webhook de formulario.

Plantillas de mensaje

: Pestaña Plantillas de email de la integración (bloques HTML + Liquid). Las campañas nuevas eligen una plantilla; los campos del formulario salen de los slots campaign. La campaña ON_EVENT se usa para bienvenida y respuesta en el panel. El formulario no necesita plantilla para aceptar envíos. Renderizado con LiquidJS.

Localización (estilo BLOG)

: Bloques, plantillas y valores de slots de campaña: campos canónicos (EN) más locales JSON opcional. Las claves Liquid no se localizan. Pase locale en form / transactional / lifecycle; si falta o no hay overlay, se usa el canónico y se registra en el log. El envío masivo usa Subscriber.locale. En la pestaña de plantillas de correo, cada tarjeta de locale muestra una vista previa en vivo del overlay (o del canónico si el campo del overlay está vacío).

Cancelación de suscripción

  1. ``{{unsubLink}}`` transaccional --- preferido cuando se envía mediante transactional-email{.interpreted-text role="doc"}.

  2. Página de un clic --- https://mailoo.app/{locale}/unsubscribe/confirm con parámetros de consulta firmados.

  3. API (servidor, con clave API):

    POST /api/v1/webhooks/unsubscribe
    Content-Type: application/json
    X-API-Key: your-api-key
    
    {"email": "user@example.com", "integrationId": "your-integration-id"}
    

Campañas (panel de control)

Las campañas viven dentro de la integración de formulario (pestaña Campañas). Puedes crear campañas ONE_TIME, ON_EVENT o RECURRING a partir de una plantilla de mensaje y usar Revisar para ver ajustes, la lista viva de suscriptores activos, una vista previa por suscriptor y un envío de prueba opcional (una dirección con el mismo remitente de campaña). Revisar no guarda la audiencia.

Cáscara de plantilla. Cada plantilla puede guardar shellWidthPx (ancho de columna, predeterminado 600, rango 320--800) y shellBackground (fondo de página en hex, predeterminado #f4f4f5). Los bloques son fragmentos HTML; Mailoo los envuelve en esta cáscara al compilar para envío y vista previa.

La bienvenida On-Event tras una nueva suscripción al formulario puede enviarse mediante el SMTP saliente de la integración cuando el estado de la campaña es ``SCHEDULED`` (las nuevas empiezan en DRAFT). El registro y la activación de usuarios identificados usan eventos de ciclo de vida (user.registered / user.activated) --- consulta lifecycle-events{.interpreted-text role="doc"} (misma regla SCHEDULED para USER_REGISTERED / USER_ACTIVATED).

Envío masivo ONE_TIME. En Setup configure el ritmo de envío (config.campaignSend.delayBetweenEmailsMs, entero 0...600000; obligatorio --- sin valor por defecto). Opcionalmente configure filtros de destinatarios en la campaña (p. ej. registrados antes de una fecha) --- se guardan en recipientFilter y se aplican al listar la audiencia en Revisar y al copiar suscriptores para el envío. Send Now llama POST …/campaigns/{id}/send. Si aún no hay filas CampaignRecipient, la API copia los suscriptores actuales activos que coinciden con el filtro (unsubscribedAt null más las reglas de recipientFilter) a la campaña y pone sendRequestedAt. El worker toma un advisory lock de PostgreSQL, pone SENDING, espera un breve retraso tras el claim y envía destinatarios pending con la pausa configurada. Varios pods de API no pueden reenviar la misma campaña. Si falla: FAILED con lastError completo; Retry send (POST …/retry) solo remapea failed a pending (los exitosos no se reenvían). PATCH status=SENDING está prohibido (400). El envío de prueba (POST …/test-send) usa el mismo remitente para un suscriptor activo y no pone la campaña en cola. RECURRING aún no está cableado.

Tras un envío, la pestaña Campañas muestra totales por destinatario (pendientes, enviados, fallidos, entregados, abiertos, clics). Abre ese resumen (o Destinatarios) para ver la tabla por dirección con estado, marcas de tiempo y errores.

Solución de problemas

401 --- Clave API inválida

: Verifica la clave, el UID del proyecto y que la clave esté activa. Las claves RESTRICTED necesitan webhook.form-submission.

403 --- Origin no permitido

: Añade el origen que envía a Orígenes permitidos, o limpia la lista si no necesitas verificaciones de Origin.

429 --- Límite de velocidad

: Reduce la frecuencia y reintenta; encola en tu servidor para tráfico alto.

Errores de validación

: Solo email es obligatorio. name, subject, content son opcionales. No envíes un campo message esperando que sea el cuerpo del buzón---usa content.

Seguridad

  • Mantén las claves API en el servidor; rótalas regularmente.
  • Valida el correo electrónico en el cliente y servidor; usa HTTPS.
  • Proporciona rutas claras de suscripción y cancelación.

Siguientes pasos

  • website-forms-nextjs-example{.interpreted-text role="doc"} --- BFF con @mailoo/forms
  • lifecycle-events{.interpreted-text role="doc"} --- eventos de registro / activación en la app
  • outbound-smtp{.interpreted-text role="doc"} / transactional-email{.interpreted-text role="doc"}
  • contact-feedback-form{.interpreted-text role="doc"} --- CONTACT_FORM para mensajes de soporte
  • OpenAPI: https://api.mailoo.app/docs/v1