feat(mail) : intégration mail OVH IMAP — boîte partagée, lecture, création/lien tâche #5
Reference in New Issue
Block a user
Delete Branch "feat/mail-integration"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Intégration complète d'un client mail OVH IMAP dans Lesstime : boîte mail partagée unique avec lecture inbox/dossiers, création de tâche depuis un mail, et lien mail ↔ tâche existante. Livrée en 7 phases TDD distinctes.
Spec source :
docs/superpowers/specs/2026-05-19-mail-integration-design.mdMaster plan :
docs/superpowers/plans/2026-05-19-mail-integration-master-plan.mdDécoupage en phases
MailProviderInterface, exception, fixture OVHwebklex/php-imap6.2,symfony/lock,ImapMailProvider,MailSyncService(syncAll/syncFolder/syncFolderStructure avec garde 50% suppressions), commande `app:mail:sync`, cible Makefile `make mail-sync`, doc cronTotal : ~61 commits dont 7 docs (master plan + 7 plans détaillés Phase 1→7) et 7 documents de plan distincts.
Sécurité
Stack ajoutée
Backend (composer) :
⚠️ La 6.2 de `webklex/php-imap` co-install `illuminate/*` (Laravel) via `carbon ^3`. Pas bloquant mais ajoute des deps Laravel au projet Symfony — à valider.
Frontend (npm) :
Validation
Migrations / cron à appliquer post-merge
Test plan
🤖 Generated with Claude Code
- endpoint ROLE_ADMIN qui teste la connexion IMAP via listFolders - retourne ok:bool + foldersCount ou error sanitise (pas de leak interne) - priority:1 obligatoire pour eviter conflit avec route API Platform {id} Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>- recupere headers + body + attachments via ImapMailProvider::fetchMessage - cache Symfony pool cache.app, cle mail_body_{md5(messageId)}, TTL 300s - attachments serialises sans contenu binaire, avec downloadId base64url(messageDbId:partNumber) - 503 si IMAP indisponible, 404 si message inconnu - les tests read/flag ROLE_CLIENT/auth seront ajoutes en Task 10 (route deja existante) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>- POST /api/mail/messages/{id}/read body {read: bool} - synchro IMAP + BDD - POST /api/mail/messages/{id}/flag body {flagged: bool} - synchro IMAP + BDD - IMAP-side non bloquant : BDD est mise a jour meme si IMAP fail (resync au prochain cycle) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>- POST /api/mail/messages/{id}/link-task body {taskId} : cree TaskMailLink (idempotent) - DELETE /api/mail/messages/{id}/link-task/{taskId} : supprime le lien (204) - GET /api/tasks/{id}/mails : liste les mails lies a une tache - securite via MailAccessChecker, tests fonctionnels 401/403 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>Le service appelait GET /mail/messages?folder=X (404) au lieu de la vraie route /mail/folders/{path}/messages. Ajoute aussi une couche de mapping backend→DTO (messages→items, fromAddress→fromEmail, toAddresses→toRecipients, détail plat→imbriqué) pour réconcilier la dérive de contrat entre Phase 3 (API) et Phase 4 (DTOs front). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>Quatre bugs révélés en testant contre une vraie boîte OVH (les tests mockaient le provider, donc jamais exercés) : - requête sans critère → "BAD parse error: zero-length content" : ajout de whereAll() - getDate()/getSubject() renvoient des Attribute webklex v6, pas des scalaires : casts explicites - séquence par défaut ST_MSGN → le peek() de webklex faisait un STORE par numéro de séquence rejeté par OVH ("flag could not be removed") : force ST_UID sur toutes les requêtes - snippet via getTextBody() forçait un fetch de corps par mail (sync 179s + peek) : setFetchBody(false) au listing, snippet désormais optionnel Sync INBOX : 9 messages en 1,6s (avant : échec en 179s). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>