@equinoxes/module-messagerie (0.2.0)

Published 2026-10-27 12:29:28 +00:00 by Equinoxes

Installation

@equinoxes:registry=
npm install @equinoxes/module-messagerie@0.2.0
"@equinoxes/module-messagerie": "0.2.0"

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 liens mailto: de tous les sites (Préférences → Général, https obligatoire).
  • Sonde : /api/health reçoit messagerie (ouverte et configurée), messagerieBoites, messagerieConnexions, messagerieImports et messagerieImportsBloques (importations ouvertes, dont bloquées : à surveiller).

Montage dans un site (core ≥ 0.8.21)

  1. package.json du site : "@equinoxes/module-messagerie": "^0.2.0" (dépendances).
  2. site.config.ts : ajouter '@equinoxes/module-messagerie' à modules (migrations copiées, règles .htaccess de /mail et de l'autoconfiguration ajoutées au build).
  3. api/src/app.ts : moduleMessagerie({ jetons: site.jetons, initiale: site.admin?.initiale }) dans modules.
  4. admin/src/main.tsx : moduleMessagerieAdmin (export par défaut de @equinoxes/module-messagerie/admin) dans modules.

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_KEY et 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: 1 exigé (aucun formulaire d'un autre site ne peut le poser) en plus du cookie SameSite=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 un iframe sandbox sans allow-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.com puis imap.mailo.com sont 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 .htaccess de /mail et 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
Details
npm
2026-10-27 12:29:28 +00:00
8
146 KiB
Assets (1)
Versions (2) View all
0.2.1 2026-10-27
0.2.0 2026-10-27