Passer au contenu principal
Cette page définit les quelques termes nécessaires avant de lire le reste du manuel. Chaque concept comporte une définition courte et un renvoi vers la décision d’architecture (Architecture Decision Record, ou ADR) qui l’a tranchée, dans le dépôt du serveur OxiMail, où se trouve le raisonnement. C’est un glossaire, pas un guide pratique.

Organisation

Une organisation est un client isolé d’une instance OxiMail : une entreprise, un organisme public, un hôpital, une université, bref toute entité qui dispose de sa propre part cloisonnée sur un serveur partagé. Au sens du stockage, une organisation est un tenant, identifié par un tenant_id. Chaque objet du système, chaque boîte aux lettres, e-mail, calendrier, contact et blob, appartient à une seule organisation. L’isolation est appliquée dans la couche de stockage, pas seulement dans les contrôles d’accès : tenant_id est un paramètre obligatoire sur chaque méthode du stockage, et chaque requête SQL porte une clause WHERE tenant_id = ?. Un bug dans un gestionnaire de requête ne peut physiquement pas atteindre les lignes d’une autre organisation. C’est une défense en profondeur, distincte du contrôle d’accès par rôle et située en dessous de lui. Tranché dans ADR-015 (isolation des tenants dans la couche de stockage). L’ADR documente aussi deux exceptions étroites et volontaires où une requête s’exécute sans le filtre tenant_id : l’agrégation inter-tenants des retours anti-spam, qui ne contient aucune donnée personnelle, et la recherche de credential WebAuthn dite « discoverable », qui n’a pas encore de contexte de tenant puisque le but de cette requête est précisément de le résoudre.

Compte et Principal

OxiMail distingue clairement trois notions que d’autres serveurs ont tendance à confondre, en suivant la RFC 9670 :
  • Un utilisateur (User) est une personne qui s’authentifie. Un utilisateur possède des identifiants et appartient à une organisation.
  • Un compte (Account) est un espace de données : un ensemble de boîtes aux lettres, calendriers et contacts. Chaque utilisateur a un compte principal et peut recevoir l’accès à d’autres comptes par le partage.
  • Un principal (Principal) est une identité partageable. Un utilisateur est un principal ; un groupe aussi, de même qu’une ressource comme une salle de réunion. On partage un compte, ou des objets qu’il contient, avec un principal.
Garder ces notions séparées rend le partage et la multi-location sans ambiguïté : une organisation contient des utilisateurs et des comptes, et un compte se partage avec un principal. Tranché dans ADR-012 (modèle Compte/Principal explicite).

Identifiants UUIDv7

Chaque identifiant d’objet dans OxiMail est un UUIDv7, écrit en hexadécimal minuscule avec des tirets (par exemple 550e8400-e29b-71d4-a716-446655440000). Il n’y a aucun encodage maison. UUIDv7 est ordonné dans le temps : les identifiants récents se trient après les anciens, ce qui donne gratuitement un ordre chronologique et la pagination. La validation est standard : une chaîne est un UUID valide ou elle ne l’est pas. Un identifiant mal formé n’est jamais ignoré en silence, il renvoie immédiatement notFound. Tranché dans ADR-008 (UUIDv7 pour tous les identifiants).

Blob

Un blob est un contenu opaque, le plus souvent les octets bruts d’un e-mail ou d’une pièce jointe. Les blobs sont adressés par leur contenu : l’identifiant d’un blob est le hachage SHA-256 de son contenu. Deux pièces jointes identiques pointent donc vers le même blob et ne sont stockées qu’une fois. La déduplication n’a lieu que dans cette couche de stockage et uniquement par hachage de contenu, jamais par Message-Id : deux e-mails qui partagent un Message-Id restent toujours deux objets Email distincts, même s’ils pointent vers le même blob sous-jacent. C’est ce qui fait que s’envoyer un e-mail à soi-même fonctionne correctement. À la réception, un e-mail est analysé une seule fois. Les octets bruts RFC 5322 sont stockés comme un blob, et un instantané structuré et compact (en-têtes analysés, drapeaux, références de fil, champs dérivés comme hasAttachment) est stocké dans la base de données. Les lectures ne ré-analysent jamais le message brut : les requêtes utilisent l’instantané structuré, et le blob brut est conservé pour un export fidèle, le téléchargement MIME et la re-vérification des signatures. Tranché dans ADR-014 (déduplication des blobs par hachage de contenu) et ADR-009 (stocker à la fois le brut et le structuré).

Capabilities

Une capability (capacité) est un ensemble de fonctionnalités que le serveur annonce dans la session JMAP, identifié par un URI tel que urn:ietf:params:jmap:mail. Un client choisit les capabilities qu’il compte utiliser en les listant dans le tableau using d’une requête ; appeler une méthode dont la capability n’a pas été demandée est une erreur, pas une réussite silencieuse. OxiMail annonce deux générations de capabilities côte à côte. Les capabilities v1 sont l’ensemble standard des RFC 8620 et RFC 8621. À côté, le serveur annonce un ensemble v2 modernisé sous l’espace de noms urn:oximail:params:jmap:v2:*. Les deux sont visibles dans la session, et une requête peut utiliser l’une, l’autre, ou les deux en même temps. Chaque gestionnaire déclare son URN v2 comme capability supplémentaire, donc une requête qui liste à la fois une capability v1 et son équivalent v2 reçoit l’union de leurs propriétés. Le répartiteur conserve un mécanisme d’exclusion mutuelle, mais sa liste de paires est actuellement vide, donc aucune combinaison n’est rejetée aujourd’hui (le mécanisme est gardé pour une éventuelle génération future). Les capabilities qu’un compte voit réellement sont ensuite filtrées par le plan de son organisation et par son rôle de compte ; ces niveaux ne peuvent que restreindre l’ensemble du serveur, jamais l’élargir. Voir ADR-028 (accountCapabilities dynamiques) pour le modèle de filtrage par compte. Pour le détail de la session et du tableau using au niveau du protocole, voir JMAP Core.

Fail-loud

Le fail-loud (échouer bruyamment) est la première règle du projet : OxiMail ne perd jamais de données en silence et n’avale jamais une erreur. Un identifiant invalide est signalé dans notFound, pas écarté. Un échec de sérialisation renvoie serverFail, pas un objet vide. Un e-mail accepté avec un 250 OK est toujours livré quelque part, même si un traitement ultérieur échoue. Les valeurs par défaut ne servent jamais à masquer une erreur, et un Result Rust n’est jamais ignoré sans bruit. Ce principe est appliqué dans le code par les règles clippy::unwrap_used et clippy::expect_used en mode « deny » : un unwrap() égaré dans le code de production fait échouer la compilation. Tranché dans ADR-027 (audit fail-silent), qui consigne le passage initial ayant corrigé tous les motifs fail-silent alors connus et mis en place la barrière de lint.

Pour aller plus loin