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
| Crate | Rôle |
|---|---|
oximail-bin | L’exécutable unique : la CLI, le démarrage, le câblage du registre des méthodes et la colle qui ouvre les backends de stockage et lance le serveur. |
oximail-server | Le serveur HTTP axum : les routes JMAP, l’API REST d’administration, ACME / TLS et l’intégration du rate-limit et de fail2ban. |
oximail-core | JMAP Core (RFC 8620) : session, analyse des requêtes, répartition des méthodes, gestion des blobs, erreurs, capacités, push et l’EventBus. |
Surface courrier
| Crate | Rôle |
|---|---|
oximail-mail | JMAP Mail (RFC 8621) : Mailbox, Email, EmailSubmission, Identity, VacationResponse et le chemin d’ingestion. |
oximail-thread | Le type Thread de première classe, avec fusion et suivi des changements. |
oximail-label | Le modèle d’étiquettes à plat : des labels partageables plutôt que des dossiers imbriqués. |
oximail-conversation | Une chronologie de conversation indépendante du canal, qui agrège les courriels et les messages de chat dans une seule vue ordonnée et propre au compte. |
oximail-sieve | Le compilateur et le runtime Sieve internes, ainsi que la traduction des règles. |
oximail-spam | Le score de contenu : vérifications heuristiques (raccourcisseurs d’URL, en-têtes falsifiés), vérifications réseau (DNSBL, rDNS) et greylisting, réunis dans un pipeline de score. |
oximail-blob | Le chemin de lecture canonique des blobs : recherche des métadonnées, déchiffrement et vérification du schéma. |
Travail collaboratif
| Crate | Rôle |
|---|---|
oximail-calendar | Les calendriers JMAP et les événements JSCalendar (RFC 9553). |
oximail-contact | Les contacts JMAP (RFC 9610). |
oximail-task | Les tâches JMAP : tâches et listes de tâches avec partage. |
oximail-file | Le stockage de fichiers JMAP : nœuds de fichiers avec partage et liens de partage. |
oximail-sharing | Le modèle Principal de la RFC 9670, les notifications de partage et les ACL. |
oximail-chat | Le chat sur JMAP : salons, messages et accusés de lecture, transmis via WebSocket. |
oximail-scheduler | Un ordonnanceur de tâches générique en mémoire pour le travail différé (envois programmés, réveils de messages mis en pause, rappels). |
oximail-link-unfurl | Un récupérateur d’URL protégé contre les SSRF et un analyseur Open Graph pour les aperçus de liens dans le chat et le courrier. |
Stockage et chiffrement
| Crate | Rôle |
|---|---|
oximail-store | Le cœur du stockage : le trait SqlBackend, le backend SQLite avec un pool de connexions r2d2, l’index Tantivy, le stockage de blobs sur le système de fichiers et les migrations versionnées. |
oximail-keyring | L’authentification et la gestion des clés : mots de passe Argon2, mots de passe d’application, passkeys (WebAuthn) et le service de clés qui arbitre le déchiffrement. |
oximail-crypto | Les primitives cryptographiques : chiffrement par enveloppe X25519, opérations symétriques AES-256-GCM et encapsulation des clés. |
Transport et interopérabilité
| Crate | Rôle |
|---|---|
oximail-smtp | SMTP entrant et soumission, DKIM/SPF/DMARC, l’orchestration du pipeline anti-spam, MTA-STS, SRS et le chemin de MX de secours. |
oximail-imap | IMAP4rev2 (RFC 9051), traduit vers JMAP Mail. Activé par la feature legacy. |
oximail-caldav | CalDAV (RFC 4791), traduit vers JMAP Calendar. Activé par la feature legacy. |
oximail-carddav | CardDAV (RFC 6352), traduit vers JMAP Contact. Activé par la feature legacy. |
oximail-managesieve | ManageSieve (RFC 5804), en lecture seule : il expose les règles du compte sous forme de texte Sieve mais refuse les modifications de script, car les clients gèrent les règles via JMAP. Activé par la feature legacy. |
oximail-rate-limit | Un limiteur de débit générique par clé (token bucket), utilisé par les couches HTTP et SMTP. |
oximail-distribution-groups | Les groupes de distribution à l’échelle de l’organisation : des alias qui se déploient vers de nombreux destinataires, avec un budget de diffusion par organisation. |
Transversal
| Crate | Rôle |
|---|---|
oximail-admin | La surface d’administration : gestion des organisations, administration des principals, stockage des comptes et journal d’audit. |
oximail-migrate | Un outil d’import en une passe pour migrer depuis d’autres serveurs. Il ne fait pas partie du binaire de production en fonctionnement. |
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 :| Crate | Rôle |
|---|---|
oximail-parser | L’analyse et la construction RFC 5322 unifiées, la génération de DSN et un client SMTP asynchrone. Un seul type de message circule de l’analyse à la construction puis à l’envoi. |
oximail-auth | L’authentification du courrier : DKIM (signature et vérification), SPF, DMARC, ARC, MTA-STS, DANE et SRS. |
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.