@equinoxes/module-messagerie (0.2.1)
Installation
@equinoxes:registry=npm install @equinoxes/module-messagerie@0.2.1"@equinoxes/module-messagerie": "0.2.1"About this package
@equinoxes/module-messagerie
Webmail du site, sur https://<site>/mail : les boîtes du client (IMAP/SMTP de son hébergeur) lues et écrites dans une
interface moderne, aux couleurs du site, sur ordinateur, tablette et téléphone. Les boîtes autorisées sont déclarées par
le Super Admin ; les personnes se connectent avec l'adresse et le mot de passe de leur boîte, sans compte du
back-office. Le module tourne dans le processus de l'API du site : pas de service à part. Une seule fonction travaille sans
personne devant l'écran, l'importation d'une autre boîte (voir plus bas) ; elle ne dépend pas de ce processus.
Ce qu'il fait
- Webmail (
/equi/mail, adresse courte/mail) : dossiers (spéciaux reconnus, création, renommage), liste paginée avec aperçus, recherche (de:,objet:, texte), filtres (non lus, suivis, pièces jointes), sélection multiple et glisser-déposer vers un dossier, lecture dans un cadre isolé, conversation, pièces jointes (aperçu, ouverture, téléchargement), images distantes masquées puis relayées sur demande, désabonnement en un clic, impression, traduction (DeepL, si configuré dans Paramètres), source.eml. - Rédaction : destinataires avec suggestions (correspondants appris des envois), éditeur riche, images collées, pièces jointes (bouton, glisser-déposer, collage), réponses types, priorité, brouillon enregistré automatiquement, annulation de l'envoi (0 à 30 s), alerte « pièce jointe annoncée mais absente », raccourcis clavier.
- Réponse automatique d'absence (filtre Sieve rangé sur le serveur : elle répond même webmail fermé, pour tous les logiciels), bandeau de rappel tant qu'elle est active.
- Préférences par boîte : nom d'expéditeur, signature (par défaut : celle du site, avec son modèle réglable par le Super Admin), volet de lecture à droite ou dessous, densité, images, délai d'annulation, notifications.
- Plusieurs boîtes dans un même navigateur (sélecteur de compte), application installable (manifeste, icône).
- Tutos sur la page de connexion et dans les préférences : iPhone/iPad, Android, Outlook, Mac, autres, avec les
réglages à copier (jamais de mot de passe) ; profil de configuration Apple (
.mobileconfig, un geste) et autoconfiguration Thunderbird (/.well-known/autoconfig/mail/config-v1.1.xml). - Super Admin (
/equi/messagerie) : interrupteur, serveurs IMAP/SMTP/ManageSieve, serveurs affichés dans les tutos, taille des pièces jointes, boîtes autorisées (ajout, nom, activation, fermeture des sessions, retrait), essai réel d'une boîte (capacités du serveur, dossiers, espace, envoi), courriel de bienvenue (lien et réglages, sans mot de passe), journal des connexions et des envois (90 jours), modèle de signature. - Importation d'une autre boîte (0.2.0, menu du compte → « Importer une autre boîte ») : copie les messages d'une ancienne messagerie (Gmail, Orange, Mailo, OVH, n'importe quel serveur IMAP) dans la boîte, en tâche de fond, avec suivi, pause, reprise et message de bilan à la fin. Détail dans « Importation » plus bas.
- Lien d'écriture, pour les autres écrans du site (« Répondre » depuis un formulaire, une commande…) :
/equi/mail/?a=adresse&sujet=…&corps=…ouvre une rédaction (après connexion à la boîte) ; le navigateur peut aussi confier au webmail les liensmailto:de tous les sites (Préférences → Général, https obligatoire). - Sonde :
/api/healthreçoitmessagerie(ouverte et configurée),messagerieBoites,messagerieConnexions,messagerieImportsetmessagerieImportsBloques(importations ouvertes, dont bloquées : à surveiller).
Montage dans un site (core ≥ 0.8.21)
package.jsondu site :"@equinoxes/module-messagerie": "^0.2.0"(dépendances).site.config.ts: ajouter'@equinoxes/module-messagerie'àmodules(migrations copiées, règles.htaccessde/mailet de l'autoconfiguration ajoutées au build).api/src/app.ts:moduleMessagerie({ jetons: site.jetons, initiale: site.admin?.initiale })dansmodules.admin/src/main.tsx:moduleMessagerieAdmin(export par défaut de@equinoxes/module-messagerie/admin) dansmodules.
Rien à installer sur le serveur : ImapFlow (JavaScript pur) est inclus dans api/dist/app.js par le build (il
n'est pas dans api/package.json, qui ne liste que ce que le serveur fournit). Les autres dépendances (express,
multer, nodemailer, sanitize-html, express-rate-limit) sont déjà celles du core. Le webmail est un morceau séparé du
bundle du back-office (environ 26 Ko compressés), chargé seulement à l'ouverture de /equi/mail.
Puis, dans le back-office (Super Admin) → Messagerie : serveurs, boîtes, essai d'une boîte, ouverture.
Pour que les importations se poursuivent navigateur fermé : poser MESSAGERIE_CRON_TOKEN dans api/.env (24 caractères
au moins, aléatoires) et créer, chez l'hébergeur, une tâche planifiée chaque minute :
curl -fsS -m 70 -X POST -H "Authorization: Bearer $MESSAGERIE_CRON_TOKEN" https://<site>/api/messagerie/import/tick.
Sans elle, l'importation avance tant que le webmail est ouvert ou que l'application est éveillée (la page le dit à la
personne) ; le Super Admin (onglet Importations) montre si le réveil frappe.
Sécurité
- Mot de passe des boîtes : jamais enregistré en clair ni durablement. Une session garde le mot de passe chiffré
(AES-256-GCM) par une clé dérivée de
SETTINGS_KEYet d'une clé aléatoire qui n'existe que dans le cookie (HttpOnly,SameSite=Strict, chemin/api/messagerie) : la base seule ne suffit pas à le relire. Mot de passe changé chez l'hébergeur : la session tombe au premier refus du serveur. - Connexion : adresse non déclarée refusée comme un mauvais mot de passe (même message, délai aléatoire) ; trois freins avant le serveur IMAP (par adresse IP, 5 échecs par boîte en 15 min, 30 échecs pour tout le site en 5 min) pour que le pare-feu de l'hébergeur ne bloque jamais l'adresse IP du site lui-même.
- Écritures : en-tête
X-Messagerie: 1exigé (aucun formulaire d'un autre site ne peut le poser) en plus du cookieSameSite=Strict. - HTML des messages : assaini (sanitize-html, liste blanche ; ni script, ni évènement, ni formulaire, ni cadre, ni
SVG, ni
javascript:), puis affiché dans uniframesandboxsansallow-scripts, avec une CSP propre au cadre. Images distantes masquées par défaut ; affichées, elles passent par un relais (adresses signées par la session, aucune adresse interne — IPv4, IPv6 et leurs formes mêlées, DNS vérifié et figé —, 3 redirections, 5 Mo, 10 s, images matricielles seulement). CSS normalisé avant contrôle (commentaires et échappements retirés,image-set()neutralisé) : ni l'adresse IP du lecteur ni l'heure de lecture ne partent chez l'expéditeur. La citation d'une réponse, insérée dans l'éditeur du document principal, n'a ni bloc<style>, ni ressource distante en CSS, ni élément positionné ; ses images relayées retrouvent leur adresse d'origine à l'envoi. - Pièces jointes : servies en flux ; ne s'ouvrent dans le navigateur que les images matricielles, le PDF et le texte
(les autres sont toujours téléchargées, isolées par
Content-Security-Policy: sandbox,nosniff). - Envoi : plafonds par boîte (50 destinataires par message, 60 messages par heure, 300 par jour) : une session volée ne peut pas faire envoyer de spam depuis le serveur, ce qui mettrait son adresse IP sur liste noire. Chaque envoi et chaque refus est journalisé (sans contenu).
- Réponse automatique : le webmail n'écrit que son propre script Sieve (
webmail) et ne remplace jamais un autre filtre actif de la boîte.
Importation d'une autre boîte
Parcours (inspiré du « transfert de comptes externes » de Mailo, avec ce qui s'y plaint : messages visibles qu'à la fin, aucun avancement chiffré, erreurs peu explicites) : assistant en trois étapes — ancienne adresse (le fournisseur et son serveur IMAP sont reconnus : Gmail, Outlook, Yahoo, iCloud, Orange, Free, SFR, La Poste, Mailo, AOL, GMX ; OVH, IONOS, Infomaniak, Gandi à choisir ; sinon base d'autoconfiguration de Thunderbird, interrogée pour le seul domaine ; sinon saisie du serveur), connexion (aide propre au fournisseur : mot de passe d'application, accès à autoriser…), dossiers à copier (tous cochés sauf spam et corbeille, avec le nombre de messages, le volume et l'espace libre de la boîte) — puis un suivi : barre d'avancement chiffrée, temps restant, détail par dossier, pause, reprise, annulation, bilan (copiés, déjà présents, non copiés avec leur dossier, leur date, leur taille et le motif). Les messages apparaissent dans la boîte au fur et à mesure.
Ce qui est copié : tous les dossiers cochés, chaque message octet pour octet (aucune réécriture), avec sa date d'origine
et ses drapeaux (lu, suivi, répondu, transféré, brouillon). Les dossiers spéciaux (Envoyés, Brouillons, Corbeille, Spam,
Archives) vont dans ceux de la boîte, les autres gardent leur nom et leur arborescence. Pas copiés : un message marqué « à
supprimer » chez l'ancien fournisseur ; les vues regroupées de Gmail (« Tous les messages », « Important », « Suivis »), qui
répètent d'autres dossiers (un message archivé sans libellé n'est donc visible que dans « Tous les messages » et n'est pas
copié) ; un message de plus de 40 Mo (MESSAGERIE_IMPORT_TAILLE_MAX_MO, 1 à 200) : il est lu en entier en mémoire.
Contacts et agenda : hors périmètre. L'ancienne boîte n'est jamais modifiée (lecture seule : EXAMINE, BODY.PEEK).
Robustesse (importation.ts, essais contre un vrai serveur dans tests/messagerie-import-imap.test.ts) :
- État en base, reprise au curseur : un curseur par dossier (dernier message traité, validité des numéros du dossier). Une coupure, un redémarrage, une pause, un autre processus : la reprise ne recopie rien. Message en cours de copie au moment d'une coupure brutale : retrouvé par son Message-ID à la reprise, jamais doublé. Ancien serveur qui renumérote un dossier : on repart du début, tout ce qui est déjà dans la boîte est reconnu. Seconde importation de la même boîte : tout est reconnu (dédoublonnage par Message-ID, à défaut taille + date + objet).
- Un seul travailleur par importation : verrou en base renouvelé toutes les 20 s (90 s de validité) ; un processus mort le laisse expirer et un autre reprend, sans intervention (même principe que les actualisations du module SEO). Un seul tour à la fois par processus, un tour de 40 s au plus pour la tâche planifiée.
- Pannes passagères (réseau, limite de débit de l'ancien fournisseur) : attentes de 30 s, 1, 2, 5 puis 10 min, jusqu'à 12 tours de suite sans progrès (environ 1 h 30), puis blocage signalé. Actions de la personne (mot de passe de l'ancienne boîte refusé, boîte d'arrivée pleine, mot de passe de la boîte changé) : blocage immédiat avec le message qui convient et le bouton pour reprendre (un nouveau mot de passe d'origine est vérifié tout de suite). Un message qui ne passe pas (trop gros, refusé) est consigné et n'arrête pas les autres ; 25 refus de suite arrêtent l'importation (c'est un serveur qui pose problème, pas un message).
- Prévenir à la fin : message de bilan déposé directement dans la boîte (sans serveur d'envoi : il arrive même si l'envoi
du site est en panne), bandeau et notification du navigateur dans le webmail, courriel facultatif à une autre adresse, ligne
dans le journal (
import_termine,import_bloque) et sonde de santé. Blocage : un seul message par blocage. - Passes de rattrapage : les messages arrivés chez l'ancien fournisseur pendant la copie sont repris avant la fin (trois passes au plus).
Sécurité : le serveur d'origine est choisi par la personne, donc traité comme une adresse venue d'Internet : ports 993 et
143 seulement, nom résolu par le module et toutes les adresses refusées si l'une est interne (IPv4, IPv6, formes mêlées),
connexion faite sur l'adresse vérifiée (pas de nouvelle résolution), certificat vérifié sur le nom saisi, erreurs traduites sans
détail technique. Deux analyses à la fois au plus pour le site, 10 essais par quart d'heure et par boîte ouverte, une
importation ouverte à la fois par boîte, trois par jour. Les deux mots de passe doivent survivre à la session : ils sont
gardés chiffrés (AES-256-GCM) par la seule clé du serveur (SETTINGS_KEY, hors de la base), et effacés à la fin, à
l'annulation, au retrait de la boîte par le Super Admin, et après 7 jours de pause ou de blocage. C'est la seule exception à
« aucun mot de passe durable » de ce module. Jamais de mot de passe dans le journal ni dans les messages d'erreur.
Limites connues : Gmail n'accepte plus le mot de passe habituel (mot de passe d'application avec validation en deux étapes, ou OAuth) ; Microsoft 365 / Outlook a retiré l'accès par mot de passe (OAuth) : non pris en charge, la page le dit et laisse essayer. Proton Mail (messages chiffrés) : refusé avec explication. Le serveur de l'ancien fournisseur peut limiter le débit (Gmail : environ 2,5 Go par jour en IMAP) : l'importation ralentit et reprend seule.
Hébergement mutualisé (Passenger, N0C)
Aucune connexion permanente ni IDLE : une connexion IMAP par session, ouverte à la demande et fermée après 2 minutes
sans usage (40 au plus par processus), un délai maximal par opération, toute erreur réseau interceptée (un évènement
error non écouté arrêterait l'API du site). Le navigateur interroge /nouveautes (compteurs seulement) toutes les 30 s
(60 s onglet caché). Sessions en base : elles survivent à la mise en veille de l'application. Le serveur IMAP reste la
seule source des messages (rien à synchroniser, rien à migrer). Importation : l'application peut être endormie à tout
moment, donc rien ne repose sur le processus : état et verrou en base, reprise au curseur, tours bornés lancés par la page
ouverte, par un minuteur du processus (filet) et surtout par la tâche planifiée (/import/tick, voir Montage).
Non vérifié
- Le serveur de messagerie de N0C (capacités IMAP, ManageSieve, quotas, limites de connexions et d'envoi) : essayé sur un Dovecot + Postfix de test. Le Super Admin dispose d'un essai réel (onglet Serveurs) qui affiche ces capacités.
- Importation : le débit réel entre N0C et l'ancien fournisseur, la limite de connexions simultanées par utilisateur de
N0C, la présence des tâches planifiées chaque minute chez l'hébergeur, le serveur IMAP de Mailo (
mail.mailo.compuisimap.mailo.comsont essayés : les sources divergent, à confirmer avec une vraie boîte), Gmail et Outlook réels (mot de passe d'application, refus d'OAuth) : essayée contre un Dovecot de test, pas contre un vrai fournisseur. - La limite de taille des requêtes de LiteSpeed (envoi de pièces jointes lourdes) et le passage par
.htaccessde/mailet de l'autoconfiguration (règles contrôlées comme texte). - Navigateurs : essayé dans Chrome (ordinateur, et émulation d'iPhone) ; pas encore dans Safari ni Firefox réels.
Tests
tests/messagerie.test.ts (sans serveur de courrier : MIME, assainissement, citation, relais, sessions, tutos, routes du
Super Admin, freins) et tests/messagerie-imap.test.ts (parcours complet contre un vrai serveur ; sauté sans
TEST_IMAP=hôte:port_imap:port_smtp:adresse:adresse_destinataire, TEST_IMAP_MDP et, pour un certificat de test,
NODE_EXTRA_CA_CERTS).
Importation : tests/messagerie-import.test.ts (sans serveur de courrier, jouée aussi sur PostgreSQL : fournisseurs,
garde-fous réseau, erreurs, chiffrement, dédoublonnage, états, ménage, Super Admin, réveil par jeton) et
tests/messagerie-import-imap.test.ts (vrai serveur : copie vérifiée octet par octet par empreinte, dates, drapeaux, dossiers
à accents, origine intacte, pause, coupure brutale, renumérotation, annulation, deux tours simultanés, processus mort, mot de
passe refusé puis repris, boîte pleine, serveur injoignable, certificat auto-signé, STARTTLS, réveil par jeton) ; sauté sans
TEST_IMPORT=hôte:port_imap:port_smtp:origine:arrivée:arrivée_à_quota_réduit[:port_starttls] et TEST_IMAP_MDP. Les trois
boîtes sont vidées par le test (la dernière doit avoir un quota de 3 Mo). MESSAGERIE_IMPORT_RESEAU_INTERNE=1 (essais
seulement, jamais en production) autorise le serveur d'origine sur le réseau local.
Dependencies
Dependencies
| ID | Version |
|---|---|
| imapflow | 2.7.0 |
Peer dependencies
| ID | Version |
|---|---|
| @equinoxes/core | ^0.8.21 |
| express | ^4.19.2 |
| express-rate-limit | ^8.7.0 |
| multer | ^2.3.0 |
| nodemailer | ^10.0.1 |
| react | ^18.3.1 |
| react-router-dom | ^6.26.2 |
| sanitize-html | 2.13.1 |