Saltar al contenido principal

Email

Un Email es un único mensaje entrante recibido a través de AWS SES, registrado en Postgres y vinculado a un inbox. El .eml original se conserva en S3 y es accesible mediante la API.

Propósito​

  • Persistir todo mensaje que ingresa a la plataforma, incluyendo los rechazados por política.
  • Llevar el estado de procesamiento (received → approved | rejected → processing → processed | failed).
  • Servir como fila de unión para agent runs, system events y webhook deliveries.

Importante: sin escrituras directas​

Los correos nunca se modifican vía la API pública, excepto mediante la acción explícita reprocess. Los escriben el handler de ingesta de SES y el pipeline del worker; la única forma de crear uno mediante la API es POST /emails, que envía un .eml en bruto a ese mismo pipeline (metadata.source queda en api). DELETE /emails/{id} hace soft-delete.

Schema​

CampoTipoDescripción
idUUIDID interno.
workspace_idUUIDWorkspace propietario.
inbox_idUUIDInbox que coincidió.
agent_idUUID | nullAgente asociado al inbox en el momento del procesamiento.
message_idstringmessageId de SES. Único — garantiza idempotencia.
raw_s3_keystringClave del .eml original en el bucket raw.
subjectstring | nullSubject decodificado. Rellenado por el worker tras el parseo.
fromstringRemitente del envelope.
tostring[]Destinatarios del payload de SES.
ccstring[]Rellenado por el worker.
bccstring[]Rellenado por el worker (rara vez con datos — el Bcc se elimina en tránsito).
statusenumVer ciclo de vida.
rejection_reasonenum | nullEstablecido cuando status=rejected.
spam_verdictstring | nullDe SES (PASS, FAIL, GRAY, etc.).
virus_verdictstring | nullDe SES.
aws_ses_metadataobjectSPF/DKIM/DMARC crudos + info de recepción de SES.
custom_metadataobjectFormato libre, establecido por los clientes.
agent_metadataobjectSalida escrita por el agent run (campos extraídos, tags, etc.).
metadataobjectMetadata del sistema.
sent_atdatetime | nullDel header Date del mensaje.
received_atdatetimeCuándo lo recibió SES.
processed_atdatetime | nullCuándo terminó el agente.
created_at / updated_at / deleted_atdatetime

Estado​

EstadoSignificado
receivedEl handler de ingesta escribió la fila; el worker aún no se ha ejecutado.
approvedEl worker parseó el .eml, coincidió con filtros y destinatarios permitidos. En cola para el agente.
rejectedEl worker rechazó el correo. rejection_reason está establecido. El agente no se ejecuta.
processingEl agent run se inició.
processedEl agente terminó exitosamente.
failedEl agent run falló. Se permite reprocess manual.

Razones de rechazo​

RazónSignificado
recipient_unknownNingún Inbox coincidió.
recipientsallowed_recipients rechazó la dirección to.
attachmentsSe excedió la cantidad, el tamaño por archivo o el tamaño total de adjuntos.
filtersAl menos una regla de filtro falló.
encodingEl .eml no pudo parsearse.
spamSES spamVerdict=FAIL (y la política del inbox trata FAIL como bloqueo).
virusSES virusVerdict=FAIL.
sender_rate_limitEl remitente excedió el tope por inbox.

Ejemplo​

{
"id": "e5f6a7b8-9c0d-4e1f-2a3b-4c5d6e7f8a9b",
"workspace_id": "a8b2c1d4-9e7f-4a3b-8c5d-1f2e3a4b5c6d",
"inbox_id": "b3c4d5e6-7a89-4b0c-9d1e-2f3a4b5c6d7e",
"agent_id": "c4d5e6f7-8a9b-4c0d-9e1f-2a3b4c5d6e7f",
"message_id": "01HZ9X8K4N0P2Q5R7S9T1V3W5X",
"raw_s3_key": "incoming/01HZ9X8K4N0P2Q5R7S9T1V3W5X.eml",
"subject": "Invoice 4123",
"from": "billing@acme.com",
"to": ["triage-3f4a@inbox.kobana.com.br"],
"cc": [],
"bcc": [],
"status": "processed",
"rejection_reason": null,
"spam_verdict": "PASS",
"virus_verdict": "PASS",
"aws_ses_metadata": {
"spfVerdict": { "status": "PASS" },
"dkimVerdict": { "status": "PASS" },
"dmarcVerdict": { "status": "PASS" }
},
"custom_metadata": {},
"agent_metadata": {
"type": "invoice",
"amount": 4123.5,
"due_date": "2026-06-01"
},
"metadata": {},
"sent_at": "2026-05-17T10:00:00.000Z",
"received_at": "2026-05-17T10:00:14.123Z",
"processed_at": "2026-05-17T10:00:18.927Z",
"created_at": "2026-05-17T10:00:14.123Z",
"updated_at": "2026-05-17T10:00:18.927Z",
"deleted_at": null
}

Descargando el .eml​

GET /v1/emails/{id}/eml devuelve un redirect 302 a una URL presigned de S3 de corta duración. La URL es válida por 15 minutos y sirve el mensaje original con headers, partes MIME y adjuntos preservados.

Descargando el .eml desde un webhook​

Todo payload de webhook de evento email.* también trae un data.email.download_url listo para usar — un enlace que quien recibe el webhook puede seguir directamente, sin ninguna de las credenciales anteriores:

https://app.inbox.kobana.com.br/eml/{id}/{token}
  • No forma parte de /v1 y no se sirve en api.inbox.*: ese host exige certificado de cliente mTLS en cada request, y quien recibe el webhook no tiene forma de presentarlo. Este enlace vive en el host de la app (sin mTLS).
  • {token} es una credencial emitida una sola vez, de corta duración (30 minutos), generada específicamente para esa entrega — no es tu token de API, y da acceso solo a ese correo.
  • Seguirlo redirige (302) directo a una URL presigned de S3 válida por 5 minutos — el mismo archivo que sirve GET /v1/emails/{id}/eml, solo que alcanzado sin mTLS/JWT.
  • Pasados los 30 minutos el enlace deja de funcionar; vuelve a obtener el correo (GET /v1/emails/{id}, mTLS+JWT) y descárgalo por la vía habitual.

Reprocesamiento​

POST /v1/emails/{id}/reprocess vuelve a encolar el correo para el agente. Solo son elegibles los correos con status=approved, processed o failed — los correos aún en received, processing o rejected devuelven 422 unprocessable_entity.