# Mail Integration — Master Plan > **Master plan** : ce document décrit le découpage en phases. Chaque phase aura son propre plan détaillé (rédigé par un subagent rédacteur) puis sera implémentée par un subagent codeur, en cycle. **Spec source** : `docs/superpowers/specs/2026-05-19-mail-integration-design.md` **Goal** : Ajouter à Lesstime un client mail intégré pour une boîte partagée OVH (IMAP/SMTP), avec lecture inbox/dossiers et création/lien tâche depuis un mail. **Stratégie** : 7 phases séquentielles, dépendances claires, chaque phase = working software testable. Cycle par phase : rédacteur → codeur → review humaine → phase suivante. --- ## Cartographie des phases ``` Phase 1 (Backend foundations) ──┐ ├─→ Phase 2 (IMAP provider + sync) ──┐ │ ├─→ Phase 3 (API backend) ──┐ │ │ │ └─→─────────────────────────────────────────────────────────────────┤ │ Phase 4 (Frontend services + store) ←──────────────────────────────────────────────────────────────┘ │ ├─→ Phase 5 (UI principale 3 colonnes) │ ├─→ Phase 6 (Intégration tâches : modals, onglet TaskDrawer) │ └─→ Phase 7 (Admin config + sidebar + polish) ``` Chaque phase produit du logiciel fonctionnel (testable, mergeable) sans casser les précédentes. --- ## Phase 1 — Backend Foundations **Plan détaillé attendu** : `docs/superpowers/plans/2026-05-19-mail-phase1-foundations.md` **Scope** : - Entité `MailConfiguration` (singleton, fields complets de la spec, `encryptedPassword` via `TokenEncryptor`) - Entité `MailFolder` - Entité `MailMessage` - Entité `TaskMailLink` (avec unique constraint) - Repositories : `MailConfigurationRepository::findSingleton()`, `MailFolderRepository`, `MailMessageRepository`, `TaskMailLinkRepository` - Migration Doctrine unique créant les 4 tables (raw SQL) - DTOs sous `src/Mail/Dto/` : `MailFolderDto`, `MailMessageHeaderDto`, `MailMessageDetailDto`, `MailAttachmentDto` - Interface `App\Mail\MailProviderInterface` (signatures uniquement, pas d'impl) - Exception `App\Mail\Exception\MailProviderException` - Tests unitaires repositories (au moins le pattern singleton) **Critère d'acceptation** : - `make migration-migrate` passe sans erreur - `php bin/console doctrine:schema:validate` OK - `make test` vert (au moins les tests créés) - Fixture `MailConfiguration` désactivée (OVH defaults) ajoutée **Dépendances** : aucune (point d'entrée). --- ## Phase 2 — IMAP Provider + Sync **Plan détaillé attendu** : `docs/superpowers/plans/2026-05-19-mail-phase2-imap-sync.md` **Scope** : - Ajout dépendance Composer `webklex/php-imap` (vérifier compat PHP 8.4) - Implémentation `App\Mail\ImapMailProvider implements MailProviderInterface` - Lecture config via `MailConfigurationRepository::findSingleton()` - Déchiffrement password via `TokenEncryptor` - `listFolders`, `listMessages`, `fetchMessage`, `markRead`, `markFlagged`, `moveMessage`, `fetchAttachment` - Wrapping erreurs en `MailProviderException` - `App\Service\MailSyncService` - `syncAll(): MailSyncReport` - `syncFolder(string $folderPath): MailSyncReport` - `syncFolderStructure(): void` - Algorithme exact de la spec (UID FETCH lastUid+1:*, resync flags N=200 derniers, detect suppressions avec garde 50%) - DTO `MailSyncReport` (count créés / mis à jour / supprimés / errors) - Symfony Lock (`mail.sync`, TTL 10 min) - Commande console `app:mail:sync` (avec option `--folder=...`) - Documentation cron OS + cible Makefile `make mail-sync` - Tests : ImapMailProvider mocké via fixture serveur ou interface, MailSyncService avec provider mocké **Critère d'acceptation** : - `php bin/console app:mail:sync --dry-run` fonctionne contre une fake config - Tests `make test` verts - `make mail-sync` documentée dans Makefile **Dépendances** : Phase 1. --- ## Phase 3 — API Backend **Plan détaillé attendu** : `docs/superpowers/plans/2026-05-19-mail-phase3-api.md` **Scope** : - API Platform ressources : - `GET /api/mail/configuration` (ROLE_ADMIN) — singleton provider - `PATCH /api/mail/configuration` (ROLE_ADMIN) — processor (jamais retourner password en clair, accepter nouveau password à chiffrer) - Custom controllers (priority: 1) : - `POST /api/mail/configuration/test` (ROLE_ADMIN) — test connexion - `GET /api/mail/folders` (ROLE_USER, refus ROLE_CLIENT explicite) — arbre + unreadCount depuis BDD - `GET /api/mail/folders/{path}/messages?page&limit` — pagination cursor `sentAt DESC, id DESC` - `GET /api/mail/messages/{id}` — fetch live IMAP + cache Symfony `mail_body_{messageId}` TTL 5 min - `POST /api/mail/messages/{id}/read` (body `{ read: bool }`) - `POST /api/mail/messages/{id}/flag` - `POST /api/mail/messages/{id}/create-task` (body `{ projectId, taskGroupId?, priority? }`) - `POST /api/mail/messages/{id}/link-task` (body `{ taskId }`) - `DELETE /api/mail/messages/{id}/link-task/{taskId}` - `GET /api/tasks/{id}/mails` - `GET /api/mail/attachments/{id}` — stream, `Content-Disposition: attachment`, jamais inline - `POST /api/mail/sync` — async via Messenger - Message + Handler Symfony Messenger `MailSyncRequested` - Sécurité : `#[IsGranted('IS_AUTHENTICATED_FULLY')]` + check `ROLE_USER && !ROLE_CLIENT` explicite - Tests fonctionnels endpoints (auth, format réponses, ROLE_CLIENT refusé) **Critère d'acceptation** : - Tous endpoints répondent corrects status/format - Tests `make test` verts - ROLE_CLIENT refusé sur 100% des endpoints mail - Password jamais leak dans les réponses **Dépendances** : Phase 1, Phase 2. --- ## Phase 4 — Frontend Services + Store **Plan détaillé attendu** : `docs/superpowers/plans/2026-05-19-mail-phase4-frontend-services.md` **Scope** : - Install npm `dompurify` + types - `frontend/services/dto/mail.ts` : tous les types TS - `frontend/services/mail.ts` : méthodes API (suivre pattern `tasks.ts`) - `listFolders`, `listMessages`, `getMessage`, `markRead`, `markFlagged` - `createTaskFromMail`, `linkTask`, `unlinkTask`, `listMailsForTask` - `triggerSync` - `getConfiguration`, `updateConfiguration`, `testConfiguration` - `downloadAttachment` (retourne Blob) - Store Pinia `frontend/stores/useMailStore.ts` - State : `folders`, `selectedFolderPath`, `messages[]`, `selectedMessageId`, `selectedMessageDetail`, `loading`, `syncing`, `globalUnreadCount` - Actions correspondantes - Polling `pollUnreadCount()` toutes les 30s (start/stop) - Sanitization helper `frontend/utils/sanitizeMailHtml.ts` (DOMPurify avec config bloquante : script/iframe/object/embed/on*/javascript:, strip ou placeholder pour `` distants) **Critère d'acceptation** : - `cd frontend && npx tsc --noEmit` OK - Test manuel d'un appel `mail.listFolders()` depuis devtools renvoie 401 si pas authentifié, 200 sinon **Dépendances** : Phase 3 (les endpoints doivent exister). --- ## Phase 5 — UI principale (page /mail) **Plan détaillé attendu** : `docs/superpowers/plans/2026-05-19-mail-phase5-ui-main.md` **Scope** : - Page `frontend/pages/mail.vue` — layout 3 colonnes (dossiers / liste / lecteur), responsive - Composants `frontend/components/mail/` : - `MailFolderTree.vue` — arbre récursif avec badges unread, sélection - `MailMessageList.vue` — liste paginée (infinite scroll), indicateurs lu/étoilé/PJ, formatage relatif des dates - `MailMessageViewer.vue` — header (de/à/cc/date) + body sanitizé via DOMPurify + liste PJ téléchargeables + actions (Créer tâche / Lier / Marquer lu/non-lu / Étoiler) - `MailRefreshButton.vue` — bouton sync manuel, désactivé pendant `syncing` - i18n clés `mail.*` dans `frontend/i18n/locales/fr.json` (et `en.json` si présent) : titres, vides, actions, erreurs - Mapping noms dossiers système (`INBOX`, `Sent`, `Drafts`, `Archive`, `Trash`, `Junk`) → labels traduits - Gestion query param `?messageId=X` pour deep-link vers un mail (selection auto à l'ouverture) - Refus visuel pour ROLE_CLIENT (le middleware backend bloque déjà, mais ajouter check côté router/middleware Nuxt) **Critère d'acceptation** : - Page accessible à `/mail` pour ROLE_USER/ROLE_ADMIN - ROLE_CLIENT redirigé vers `/portal` - Pas d'XSS via body mail (test manuel avec un mail contenant ``) - Pixels tracking distants remplacés par placeholder **Dépendances** : Phase 4. --- ## Phase 6 — Intégration Tâches **Plan détaillé attendu** : `docs/superpowers/plans/2026-05-19-mail-phase6-task-integration.md` **Scope** : - `frontend/components/mail/MailCreateTaskModal.vue` — wrapper du `TaskDrawer` existant pré-rempli : - Titre = subject - Description = body plain text - Picker projet + groupe + priorité - À la création : appelle `POST /api/mail/messages/{id}/create-task`, ferme modal, redirige ou affiche succès - `frontend/components/mail/MailLinkTaskModal.vue` — autocomplete sur tâches existantes (filter par projet, statut non-archivé) - Onglet **"Mails"** sur `TaskDrawer.vue` : - Nouvelle section affichée à côté Documents / Time tracking / etc. - Liste `MailMessage` liés à la tâche (via `GET /api/tasks/{id}/mails`) - Item cliquable → `router.push('/mail?messageId=' + id)` - Bouton "Lier un mail" → ouvre un picker mail (TBD selon ergonomie : modal recherche ou redirige vers /mail) - Tests manuels : créer tâche depuis mail, lier mail à tâche existante, voir mail depuis onglet tâche **Critère d'acceptation** : - Workflow complet : mail → "Créer tâche" → tâche créée et liée → visible dans onglet "Mails" du TaskDrawer - Workflow : tâche existante → "Lier mail" → mail apparaît dans onglet **Dépendances** : Phase 5. --- ## Phase 7 — Admin Config + Sidebar + Polish **Plan détaillé attendu** : `docs/superpowers/plans/2026-05-19-mail-phase7-admin-polish.md` **Scope** : - `frontend/components/admin/AdminMailTab.vue` (calqué sur `AdminZimbraTab.vue`) : - Form : protocol (imap pour MVP), imapHost/Port/Encryption, smtpHost/Port/Encryption, username, password (write-only, `hasPassword: true` côté GET), sentFolderPath, enabled toggle - Bouton "Tester la connexion" → `POST /api/mail/configuration/test` - Indicateur OVH defaults pré-remplis (`ssl0.ovh.net:993/465`) - Ajout onglet `AdminMailTab` dans la page admin (selon pattern existant) - Lien sidebar dans le layout default : - Icône `material-symbols:mail-outline` - Label traduit - Badge unread (count `useMailStore.globalUnreadCount`) - Visible uniquement pour `ROLE_USER && !ROLE_CLIENT` - Lifecycle polling 30s : start dans `app.vue` ou layout default, stop au logout - Documentation finale : - README ou `docs/` : section "Mail integration" (cron OS, variables config, sécurité) - Makefile : `make mail-sync` documentée - Vérification finale tracking pixels (relire `sanitizeMailHtml.ts` + tester) - QA passe : workflow end-to-end depuis vraie boîte OVH (si dispo) ou IMAP test (greenmail/dovecot local) **Critère d'acceptation** : - Admin peut configurer la boîte, tester, activer - Sidebar affiche badge unread temps réel (30s polling) - Doc d'install à jour - Aucun warning console front, aucun ERROR PHP dans `make logs-dev` **Dépendances** : Phase 5 (sidebar utilise le store), Phase 3 (admin API). --- ## Conventions communes à toutes les phases - **TDD** : test rouge → code → test vert → commit - **Strict types** PHP (`declare(strict_types=1)`) en tête de chaque fichier - **PHP CS Fixer** : `make php-cs-fixer-allow-risky` avant chaque commit - **Commits** : format `(mail) : ` (espace avant `:`) - **Branche** : `feat/mail-integration` (créée au début de Phase 1) - **Pas de jamais logger** : bodies, password, attachments - **Review humaine entre chaque phase** : le user valide avant lancement phase suivante --- ## Cycle d'exécution Pour chaque phase N : 1. **Spawn subagent rédacteur** (`feature-dev:code-architect`) - Input : ce master plan + spec + scope phase N - Output : `docs/superpowers/plans/2026-05-19-mail-phaseN-*.md` au format `writing-plans` (tasks bite-sized, fichiers exacts, code complet, commandes test) 2. **Spawn subagent codeur** (`ruflo-core:coder`) - Input : plan détaillé phase N - Output : code + tests + commits (TDD strict) 3. **Review humaine** : user valide ou demande corrections 4. **Phase suivante** uniquement si OK