# Recepción de correos (/es/guides/receiving-emails)

Cualquier dirección en un dominio verificado puede convertirse en una bandeja de entrada: Bitelio acepta el correo entrante, lo guarda en tu proyecto, y dispara un evento `email.received` al que los flujos de trabajo pueden reaccionar. Esa es la base para respuestas automáticas, sistemas de tickets, reenvío condicional — cualquier automatización de "cuando llega un correo, haz X".

## Qué ocurre cuando llega un correo

Para cada mensaje enviado a una dirección en tu dominio verificado, Bitelio:

1. Lo analiza y lo guarda como un registro `Email` entrante, visible en el feed de Actividad del proyecto.
2. Da de alta (upsert) al remitente como contacto — creado (suscrito por defecto) si es nuevo, actualizado en caso contrario.
3. Registra un evento `email.received` en ese contacto, disponible como disparador para cualquier flujo de trabajo.

Antes de guardarse, el cuerpo HTML pasa por sanitización: se eliminan scripts, iframes, manejadores de eventos y URI `javascript:`. Si el mensaje no tiene parte HTML, el texto plano se conserva sin cambios.

## Configuración

import {Step, Steps} from 'fumadocs-ui/components/steps';

<Steps>
  <Step>
    ### Verifica tu dominio para envío

    Un dominio solo acepta correo entrante una vez que está completamente verificado para envío (DKIM + SPF + el MX de retroalimentación de rebotes). Trabaja primero la guía de [Verificación de dominios](/es/guides/verifying-domains) antes de continuar.
  </Step>

  <Step>
    ### Añade el registro MX de entrada

    En la pestaña **Dominios** de tu proyecto, expande el dominio verificado y busca la sección **Correo entrante**. Muestra el valor exacto del registro MX para tu DNS — cópialo y crea el registro MX en tu dominio.

    <Callout title="Conflicto con correo existente" type="warn">
      Un dominio solo puede tener un destino MX de entrada primario. Si ya usas Google Workspace, Microsoft 365, u otro proveedor para recibir correo en el dominio raíz, tu correo existente se romperá si cambias el MX para que apunte a Bitelio. La solución habitual es recibir el correo entrante de Bitelio en un subdominio (p. ej. `mail.yourdomain.com` o `support.yourdomain.com`) para poder mantener tu buzón principal en el proveedor existente. El subdominio todavía necesita estar verificado para envío en Bitelio antes de que se acepte su MX.
    </Callout>
  </Step>

  <Step>
    ### Espera la propagación del DNS

    La propagación puede tardar desde minutos hasta 48 horas. Para confirmar que el registro MX ya está activo:

    ```bash
    dig MX yourdomain.com
    ```

    El valor del panel debería aparecer en la salida.
  </Step>

  <Step>
    ### Envía un correo de prueba

    Desde cualquier cuenta externa, envía un correo a una dirección arbitraria en tu dominio (p. ej. `anything@yourdomain.com`) y verifica que:

    * El feed de Actividad del proyecto muestra una nueva fila `Email` de tipo **Entrante**.
    * El remitente aparece en la pestaña **Contactos**.
    * Ese contacto tiene registrado un evento `email.received`.
  </Step>
</Steps>

## Qué se guarda

El correo entrante aceptado aterriza en el feed de Actividad del proyecto justo al lado de tus correos salientes, con las mismas herramientas de búsqueda, filtro e inspección.

Lo que Bitelio guarda: el cuerpo HTML analizado y sanitizado (recurriendo a texto plano cuando no hay HTML), los encabezados listados en el payload de evento de abajo, los veredictos de autenticación y spam, y el ID del mensaje.

Lo que no guarda: el mensaje original en bruto, los adjuntos, los encabezados de hilo (`In-Reply-To`, `References`), o cualquier encabezado más allá de los del evento. Cuando necesites eso, haz que el flujo de trabajo disparado reenvíe el correo a tu propio servicio mediante un paso `WEBHOOK`.

## El evento `email.received`

El evento aterriza en el registro de contacto del remitente (creado sobre la marcha si es necesario), con el mensaje completamente analizado en su campo de datos:

import {TypeTable} from 'fumadocs-ui/components/type-table';

<TypeTable
  type={{
  messageId: { type: 'string', description: 'Identificador único de mensaje para el correo recibido.' },
  from: { type: 'string', description: 'Dirección de correo del remitente (sobre / encabezado `From`).' },
  fromHeader: { type: 'string', description: 'Encabezado "From" completo incluyendo el nombre para mostrar, p. ej. `"Ada <ada@example.com>"`.' },
  to: { type: 'string', description: 'Destinatario principal en tu dominio verificado.' },
  recipients: { type: 'array of strings', description: 'Cada dirección destinataria (cubre `To`, `Cc`, y destinatarios de sobre BCC).' },
  subject: { type: 'string', description: 'Línea de asunto.' },
  timestamp: { type: 'string', description: 'Marca de tiempo ISO 8601 de recepción.' },
  hasContent: { type: 'boolean', description: '`true` cuando un cuerpo se analizó correctamente.' },
  body: { type: 'string', description: 'Cuerpo HTML sanitizado, o texto plano si no hay parte HTML. Ver las reglas de sanitización arriba.' },
  spamVerdict: { type: 'string', description: 'Resultado de la comprobación de spam. Uno de `PASS`, `FAIL`, `GRAY`, o `PROCESSING_FAILED`.' },
  virusVerdict: { type: 'string', description: 'Resultado del análisis de virus. Uno de `PASS`, `FAIL`, `GRAY`, o `PROCESSING_FAILED`.' },
  spfVerdict: { type: 'string', description: 'Resultado de autenticación SPF.' },
  dkimVerdict: { type: 'string', description: 'Resultado de autenticación DKIM.' },
  dmarcVerdict: { type: 'string', description: 'Resultado de autenticación DMARC.' },
  processingTimeMillis: { type: 'number', description: 'Cuánto tardó en procesarse el correo.' },
}}
/>

La emisión es **incondicional** — los veredictos de spam, virus y autenticación nunca hacen que Bitelio retenga el evento. Cualquier filtrado corre por tu cuenta dentro del flujo de trabajo que dispara.

Dentro de plantillas y pasos de flujo de trabajo, estos campos viven bajo el espacio de nombres de variable de evento: `{{event.subject}}`, `{{event.from}}`, `{{event.body}}`, y así sucesivamente.

## Construir flujos de trabajo sobre correo entrante

### Respuesta automática

La respuesta automática más simple posible para `support@yourdomain.com`:

1. **Disparador**: `email.received`.
2. **Condición**: continuar solo si `event.to` es igual a `support@yourdomain.com` y `event.spamVerdict == "PASS"` y `event.virusVerdict == "PASS"`.
3. **Enviar correo**:
   * **Para**: `{{event.from}}`
   * **Asunto**: `Re: {{event.subject}}`
   * **Cuerpo**: `Thanks for your message. We've received your email and will respond within one business day.`

Dale a la respuesta automática una plantilla `TRANSACTIONAL` para que no apliquen las comprobaciones de suscripción — los remitentes se suscriben automáticamente, pero si uno se da de baja después, tu respuesta debería seguir saliendo.

### Enrutamiento por destinatario

Un flujo de trabajo puede repartir distintas direcciones hacia distintas acciones:

1. **Disparador**: `email.received`.
2. **Condición**: ramifica sobre `event.to`:
   * `support@…` → webhook de tickets → respuesta automática.
   * `sales@…`   → webhook de CRM → notificar Slack.
   * `billing@…` → webhook del sistema de facturación.

### Reenviar a tu API

Cuando el procesamiento necesita más de lo que Bitelio ofrece (clasificación NLP, creación de tickets, adjuntos que Bitelio no captura), entrega el correo a tu propio backend:

1. **Disparador**: `email.received`.
2. **Webhook**:
   * **URL**: `https://api.example.com/inbound`
   * **Método**: `POST`
   * **Cuerpo**:
     ```json
     {
       "from": "{{event.from}}",
       "subject": "{{event.subject}}",
       "body": "{{event.body}}",
       "messageId": "{{event.messageId}}",
       "timestamp": "{{event.timestamp}}",
       "verdicts": {
         "spam": "{{event.spamVerdict}}",
         "virus": "{{event.virusVerdict}}",
         "spf": "{{event.spfVerdict}}",
         "dkim": "{{event.dkimVerdict}}",
         "dmarc": "{{event.dmarcVerdict}}"
       }
     }
     ```

### Filtrar spam y virus antes de procesar

Adopta el hábito de colocar un paso `CONDITION` cerca del inicio de cada flujo de trabajo de correo entrante que descarte mensajes cuyo `spamVerdict` o `virusVerdict` sea `FAIL` — Bitelio no hace ningún prefiltrado en tu nombre.

## Dominios en múltiples proyectos

Cuando varios proyectos han verificado el mismo dominio, cada correo entrante se reparte a **todos ellos** — cada proyecto obtiene su propio registro `Email`, upsert de contacto, y evento `email.received`. Eso es intencional: permite que staging y producción compartan una bandeja de entrada, o que varios equipos consuman el mismo flujo de entrada.

Para evitar el reparto, mantén el dominio verificado en un solo proyecto a la vez.

## Facturación

Cada correo recibido consume **1 crédito** del uso de correo de tu proyecto, exactamente igual que un envío saliente. Los niveles gratuito y de pago se nutren del mismo pool.

En **Facturación → Límites** puedes poner un tope por proyecto sobre el correo entrante. Cuando se alcanza el tope, **el correo entrante de ese proyecto se descarta silenciosamente** hasta que el tope se restablece — nada se pone en cola ni se reproduce después. Un tope en un proyecto no tiene efecto sobre otros proyectos verificados en el mismo dominio.

## Consideraciones de seguridad

* **Trata el cuerpo como entrada de usuario no confiable.** La sanitización cierra las rutas obvias de inyección de scripts, pero los enlaces de phishing, el texto de ingeniería social y los caracteres unicode similares sobreviven a ella. Nunca renderices el cuerpo tal cual en tu propia interfaz sin escaparlo apropiadamente para ese contexto.
* **Los veredictos de autenticación son informativos.** Los resultados de SPF / DKIM / DMARC se registran en el evento, no se aplican. La política es tuya para definir — para bandejas sensibles (facturación, cambios de cuenta), rechazar correo no autenticado en el primer paso `CONDITION` del flujo de trabajo es una base razonable.
* **Los remitentes se suscriben automáticamente.** Todo remitente entrante se une a tu audiencia como contacto suscrito. Para excluirlos, termina el flujo de trabajo de entrada con un paso `UPDATE_CONTACT` que establezca `subscribed: false`.
* **Riesgo de bucle de respuesta.** Una respuesta automática dirigida a un dominio en el que también recibes (o a una dirección de lista) puede entrar en bucle indefinidamente. Prevénlo con una `CONDITION` que descarte mensajes cuyo `event.from` esté en tu propio dominio.

## Limitaciones

* **Solo catch-all**: toda dirección en el dominio converge en el mismo manejador; distinguir destinatarios ocurre dentro del flujo de trabajo vía `event.to`.
* **Sin adjuntos**: el análisis los descarta. Reenvía a tu propio servicio si los necesitas.
* **Sin MIME en bruto**: el mensaje original no se conserva.
* **Sin hilos**: los mensajes entrantes no se agrupan en hilos ni se emparejan con respuestas salientes.
* **Tope de 40 MB**: cualquier cosa por encima de 40 MB se rechaza antes de que empiece el procesamiento.

## Solución de problemas

import {Accordion, Accordions} from 'fumadocs-ui/components/accordion';

<Accordions type="single">
  <Accordion title="No se están recibiendo correos">
    1. **DNS**: ejecuta `dig MX yourdomain.com` y comprueba que el valor del panel aparece en la respuesta.
    2. **Verificación de dominio**: asegúrate de que el dominio está completamente verificado para envío — el MX de entrada de un dominio no verificado no procesa nada.
    3. **El flujo de trabajo no se dispara**: crea un flujo de trabajo de prueba desnudo (solo un disparador `email.received` más un webhook a algo como webhook.site) para establecer si los eventos se emiten en absoluto. Si es así, tu lógica de flujo de trabajo es el problema; si no, el problema está más arriba.
    4. **Caché DNS del remitente**: las consultas MX pueden quedar en caché durante horas del lado del envío — inténtalo de nuevo desde un proveedor de correo distinto.
  </Accordion>

  <Accordion title="El correo llega a Actividad pero ningún flujo de trabajo se ejecuta">
    * Confirma que el nombre del evento disparador es exactamente `email.received`.
    * Confirma que el flujo de trabajo está **habilitado** — los flujos de trabajo nuevos empiezan deshabilitados.
    * Confirma que los pasos `CONDITION` no están rechazando todo (las comprobaciones de veredicto suelen hacer esto cuando el dominio del remitente tiene autenticación débil).
  </Accordion>

  <Accordion title="El correo entrante se descarta">
    * Busca en **Facturación → Límites** un tope de entrada que se haya agotado.
    * Confirma que el propio proyecto está activo en el panel — los proyectos deshabilitados ignoran el correo entrante.
  </Accordion>

  <Accordion title="Los veredictos son consistentemente FAIL">
    La causa está casi siempre del lado del remitente, no del tuyo:

    * **Fallos de SPF**: el dominio del remitente carece de un registro SPF, o el registro omite su IP de envío.
    * **Fallos de DKIM**: el dominio del remitente no tiene DKIM, o el mensaje se alteró en tránsito (los servicios de reenvío suelen romper DKIM).
    * **Fallos de DMARC**: tanto SPF como DKIM fallan, o el reenvío rompió una política de alineación estricta.

    Si conviene descartar o procesar estos mensajes de todas formas es una decisión que corresponde a tu flujo de trabajo.
  </Accordion>
</Accordions>
