Le modèle du binaire unique
Quand vous lancez OxiMail, vous obtenez un seul processus qui héberge :- Un serveur HTTP axum qui sert l’API JMAP, l’API d’administration, les points d’accès de push et la mécanique ACME / TLS.
- Un composant SMTP avec deux écouteurs distincts : le port 25 pour le courrier entrant et le port 587 pour la soumission authentifiée. Ce sont volontairement deux chemins de code séparés, car la soumission vérifie l’expéditeur par rapport aux identités de l’utilisateur authentifié, contrairement à l’entrant.
- Une pile de stockage : une base SQLite par organisation (un tenant au sens du stockage), un index plein texte Tantivy et un stockage de blobs sur le système de fichiers, adressé par le contenu.
Les crates, par couche
OxiMail est un espace de travail (workspace) Cargo de 30 crates sous licence AGPL-3.0-or-later, plus deux crates internes en dépendance de chemin qui vivent à côté. Les crates sont regroupées par la couche d’architecture qu’elles prennent en charge, et non par ordre alphabétique.Point d’entrée et répartition HTTP
Surface courrier
Travail collaboratif
Stockage et chiffrement
Transport et interopérabilité
Transversal
Crates de protocole courrier internes
Deux crates se trouvent à côté de l’espace de travail, en dépendance de chemin. Elles remplacent les bibliothèques de courrier tierces dont OxiMail dépendait autrefois, de sorte que tout le pipeline de courrier est interne :Les chemins des requêtes
OxiMail expose un seul modèle de stockage derrière plusieurs portes d’entrée.- JMAP sur HTTPS. Un client envoie une requête groupée (batch) à
/jmap. Le serveur authentifie le jeton bearer, analyse la requête, valide les capacités annoncées, répartit chaque appel de méthode dans le registre des méthodes et renvoie une seule réponse groupée (RFC 8620). - Les protocoles historiques se traduisent vers le même stockage. IMAP, CalDAV, CardDAV et ManageSieve sont des couches de traduction, pas des implémentations parallèles. Un
FETCHIMAP finit par appeler les mêmes gestionnaires internes que la méthode JMAP équivalente, puis ré-encode le résultat au format historique. La featurelegacydécide s’ils sont compilés. - SMTP entrant et soumission. Le courrier entrant sur le port 25 passe la vérification DKIM/SPF/DMARC et le pipeline anti-spam, puis il est livré par le même chemin d’ingestion qui écrit à la fois la forme brute et la forme structurée d’un message. La soumission authentifiée sur le port 587 est un chemin distinct qui vérifie l’expéditeur.
- Push. Les clients reçoivent les changements d’état via Server-Sent Events (EventSource) ou WebSocket, de sorte qu’un client sait ce qui a changé sans interrogation répétée.
Le modèle de stockage
Quelques invariants gouvernent chaque lecture et chaque écriture.- Un seul trait de backend, des connexions mises en pool. Chaque store est bâti sur
Arc<dyn SqlBackend>avec un pool de connexions r2d2. Il n’y a aucunMutex<Connection>nulle part. Un store n’embarque jamais de connexion ; il en emprunte une au pool le temps d’une requête. - L’isolation des organisations dans la couche de stockage. Le
tenant_idest un paramètre obligatoire de chaque méthode de store, et chaque requête porte une clauseWHERE tenant_id = ?. Un bug dans un gestionnaire ne peut pas atteindre physiquement les lignes d’une autre organisation. C’est de la défense en profondeur, distincte du contrôle d’accès. - Le courriel brut et structuré, stocké une seule fois. À l’ingestion, un message est analysé une seule fois. Les octets bruts RFC 5322 vont au stockage de blobs et la structure analysée va à la base de données. Les propriétés dérivées comme
hasAttachment,previewetthreadIdsont calculées à l’ingestion et stockées, jamais recalculées à la lecture. - La déduplication des blobs par hachage du contenu. Un blob est adressé par le SHA-256 de son contenu, donc deux pièces jointes identiques partagent un seul blob. Deux courriels qui partagent par hasard un Message-ID restent toujours deux objets distincts qui pointent vers le même blob ; la déduplication n’a jamais lieu au niveau du message.
- Le
/settransactionnel. Toutes les écritures d’un gestionnaire/setsont liées à la même transactionBEGIN IMMEDIATE: les créations, les mises à jour, les suppressions et les enregistrements de changements aboutissent tous ensemble, ou aucun.
L’EventBus
Les crates de domaine ne s’importent pas les unes les autres. La crate courrier ne va jamais chercher dans la crate calendrier, et ainsi de suite. Elles communiquent plutôt par un EventBus : une crate émet un événement de domaine après une écriture réussie, et toute crate intéressée s’y abonne. Par exemple, quand un message entrant porte une invitation de calendrier, la partie courrier émet un événement ; la partie calendrier, abonnée à cet événement, crée ou met à jour l’objet de calendrier. Cela garde les crates découplées et rend le comportement inter-domaines explicite et testable.Pour aller plus loin
- La Référence d’architecture pour le détail crate par crate.
- Les concepts fondamentaux pour le modèle de données et le vocabulaire.