Pular para o conteúdo principal

Coleta IMAP do MVP de e-mails

Esta etapa implementa a leitura somente leitura das contas IMAP cadastradas em email_accounts, com persistência idempotente em email_messages.

Biblioteca usada

  • webklex/laravel-imap

Dependências e extensões

  • PHP 8.2+
  • Laravel 12
  • webklex/laravel-imap
  • webklex/php-imap
  • Extensões recomendadas:
  • mbstring
  • openssl

Estratégia técnica

  • O client é montado em runtime a partir de EmailAccount.
  • Não existe conta fixa em config/imap.php.
  • A conexão sempre usa modo somente leitura.
  • A coleta usa FT_PEEK ou equivalente para evitar marcar mensagens como lidas.
  • O fluxo não executa ações destrutivas no servidor IMAP.

Services

  • App\Services\Email\ImapClientFactory
  • App\Services\Email\EmailConnectionService
  • App\Services\Email\EmailFolderSyncService
  • App\Services\Email\EmailInboxSyncService
  • App\Services\Email\EmailMessagePersistenceService
  • App\Services\Email\EmailSyncAuditService

Comandos Artisan

  • php artisan email:test-connection {accountId}
  • php artisan email:sync-folders {accountId}
  • php artisan email:sync-inbox {accountId} {--limit=50}
  • php artisan email:sync {accountId} {--limit=50} {--queue}

Fluxo de teste de conexão

  1. Localizar a conta.
  2. Validar active, host, porta, usuário e senha.
  3. Criar o client IMAP dinamicamente.
  4. Conectar.
  5. Desconectar.
  6. Registrar auditoria sanitizada.

Sincronização de pastas

  1. Conectar uma vez por conta.
  2. Listar pastas remotas.
  3. Normalizar name e display_name.
  4. Criar ou atualizar email_folders.
  5. Preservar sync_enabled quando a pasta já existir.
  6. Atualizar uid_validity e last_sync_at.
  7. Não apagar pastas locais nesta issue.

Coleta da INBOX

  1. Coletar somente INBOX.
  2. Usar last_uid + 1 quando existir cursor.
  3. Se não houver cursor, buscar os --limit mais recentes.
  4. Persistir mensagens com idempotência por email_account_id + folder + message_uid.
  5. Atualizar last_uid e last_sync_at somente após o lote concluir com sucesso.
  6. Não baixar anexos binários.
  7. Registrar apenas metadados dos anexos quando a biblioteca expuser isso sem download.

Idempotência

  • A barreira principal já está no banco.
  • O processo também usa updateOrCreate com tratamento de violação de chave única.
  • Rodar a sincronização duas vezes não deve duplicar mensagens.

Auditoria sanitizada

Eventos registrados:

  • email_connection_tested
  • email_connection_failed
  • email_folders_synced
  • email_inbox_synced
  • email_sync_failed

Metadados permitidos:

  • email_account_id
  • email_address
  • imap_host
  • imap_port
  • folder
  • phase
  • error_class
  • safe_message
  • messages_seen
  • messages_created
  • messages_skipped
  • last_uid

Risco de uid_validity

  • uid_validity é tratado como estado operacional.
  • Se mudar, a sincronização registra auditoria e pode reiniciar o cursor da INBOX.
  • O risco residual é que o servidor remapeie UIDs e a conta precise de reprocessamento manual.

Execução manual

  • Teste de conexão:
php artisan email:test-connection 1
  • Sincronizar pastas:
php artisan email:sync-folders 1
  • Sincronizar INBOX:
php artisan email:sync-inbox 1 --limit=50
  • Sincronizar tudo:
php artisan email:sync 1 --limit=50
  • Despachar para fila:
php artisan email:sync 1 --limit=50 --queue

O que fica para as próximas issues

  • Issue #15: classificação, resumo e sugestões com IA.
  • Issue #16: telas e revisão humana.
  • Issue #17: SMTP e envio de respostas aprovadas.