Pular para o conteúdo principal

Email

Um Email é uma mensagem individual recebida via AWS SES, registrada no Postgres e vinculada a uma inbox. O .eml original é preservado no S3 e acessível pela API.

Propósito​

  • Persistir toda mensagem que entra na plataforma, inclusive as rejeitadas por política.
  • Carregar o estado de processamento (received → approved | rejected → processing → processed | failed).
  • Servir de pivot para agent runs, system events e webhook deliveries.

Importante: sem escrita direta​

E-mails nunca são modificados via API pública, exceto pela ação explícita de reprocess. São escritos pelo handler de ingestão SES e pelo pipeline de worker; a única forma de criar um pela API é POST /emails, que envia um .eml bruto para esse mesmo pipeline (metadata.source fica api). DELETE /emails/{id} faz soft-delete.

Schema​

CampoTipoDescrição
idUUIDID interno.
workspace_idUUIDWorkspace dono.
inbox_idUUIDInbox que casou.
agent_idUUID | nullAgent associado à inbox no momento do processamento.
message_idstringmessageId do SES. Unique — garante idempotência.
raw_s3_keystringChave do .eml original no bucket raw.
subjectstring | nullSubject decodificado. Preenchido pelo worker após parse.
fromstringSender do envelope.
tostring[]Destinatários do payload SES.
ccstring[]Preenchido pelo worker.
bccstring[]Preenchido pelo worker (raramente populado — Bcc é removido em trânsito).
statusenumVeja o ciclo de vida.
rejection_reasonenum | nullDefinido quando status=rejected.
spam_verdictstring | nullDo SES (PASS, FAIL, GRAY, etc.).
virus_verdictstring | nullDo SES.
aws_ses_metadataobjectSPF/DKIM/DMARC + receipt info crus do SES.
custom_metadataobjectFree-form, setado por clientes.
agent_metadataobjectOutput escrito pelo agent run (campos extraídos, tags, etc.).
metadataobjectMetadata do sistema.
sent_atdatetime | nullDo header Date da mensagem.
received_atdatetimeQuando o SES recebeu.
processed_atdatetime | nullQuando o agente terminou.
created_at / updated_at / deleted_atdatetime

Status​

StatusSignificado
receivedHandler de ingestão gravou a linha; worker ainda não rodou.
approvedWorker fez parse do .eml, casou filtros e destinatários permitidos. Enfileirado pro agente.
rejectedWorker rejeitou. rejection_reason setado. Agente não roda.
processingAgent run começou.
processedAgente terminou com sucesso.
failedAgent run falhou. reprocess manual permitido.

Razões de rejeição​

RazãoSignificado
recipient_unknownNenhuma Inbox casou.
recipientsallowed_recipients rejeitou o endereço to.
attachmentsQuantidade, tamanho-por-arquivo ou total de anexos excedidos.
filtersPelo menos uma regra de filtro falhou.
encoding.eml não pôde ser parseado.
spamSES spamVerdict=FAIL (e política da inbox bloqueia FAIL).
virusSES virusVerdict=FAIL.
sender_rate_limitSender excedeu cap por-inbox.

Exemplo​

{
"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
}

Baixando o .eml​

GET /v1/emails/{id}/eml retorna um redirect 302 para uma URL S3 presigned de curta duração. A URL é válida por 15 minutos e serve a mensagem original com headers, partes MIME e anexos preservados.

Baixando o .eml a partir de um webhook​

Todo payload de webhook de evento email.* também traz um data.email.download_url pronto pra uso — um link que quem recebe o webhook pode seguir direto, sem nenhuma das credenciais acima:

https://app.inbox.kobana.com.br/eml/{id}/{token}
  • Ele não faz parte do /v1 e não é servido em api.inbox.*: aquele host exige certificado de cliente mTLS em toda requisição, e quem recebe o webhook não tem como apresentar um. Esse link vive no host do app (sem mTLS).
  • {token} é uma credencial emitida uma única vez, de curta duração (30 minutos), gerada especificamente para aquela entrega — não é o seu token de API, e dá acesso só àquele e-mail.
  • Seguir o link redireciona (302) direto para uma URL presigned do S3 válida por 5 minutos — o mesmo arquivo que GET /v1/emails/{id}/eml serve, só que alcançado sem mTLS/JWT.
  • Depois de 30 minutos o link para de funcionar; busque o e-mail de novo (GET /v1/emails/{id}, mTLS+JWT) e baixe pelo caminho normal.

Reprocessamento​

POST /v1/emails/{id}/reprocess reenfileira o e-mail para o agente. Só são elegíveis e-mails com status=approved, processed ou failed — qualquer outro status retorna 422 unprocessable_entity.