Skip to main content
Le courrier est la surface de la RFC 8621, demandée avec urn:ietf:params:jmap:mail (plus submission et vacationresponse pour l’envoi et la réponse automatique). Cette page liste ce qu’OxiMail sert et où il va au-delà de la RFC. La mécanique commune à toutes les méthodes (regroupement, références arrière, /changes, push) est sur JMAP Core.

Objets et méthodes

Extensions de méthode propres à OxiMail sur la surface courrier : Email/send (l’envoi en un appel), Email/cancel (annuler un envoi différé ou en fenêtre d’annulation) et Email/wake (l’expiration d’une mise en veille). La réindexation est une affaire d’administration, pas de client.

Arbre du corps et valeurs de corps

Tout ce que Email/get dit du corps d’un message vient d’un seul parcours en profondeur du véritable arbre MIME. C’est ce qui fait que partId désigne la même partie dans toutes les propriétés d’une même réponse : l’identifiant lu dans bodyStructure est celui que porte la pièce jointe, et c’est la clé à chercher dans bodyValues.
bodyValues est vide si vous ne le demandez pas. La RFC 8621 §4.2 conditionne le contenu à fetchTextBodyValues, fetchHTMLBodyValues ou fetchAllBodyValues, et OxiMail en tient désormais la lettre : les valeurs étaient auparavant remplies que les arguments soient présents ou non. Un client qui n’a jamais posé d’argument fetch* et lisait quand même bodyValues y lira une table vide. Posez l’argument. Email/get et Email/parse passent par la même sélection : la règle est identique des deux côtés.
Les en-têtes par partie sont le chemin vers les paramètres de Content-Type. La propriété type est normativement nue, text/calendar et rien de plus, si bien que les paramètres qui portent le sens ne s’atteignent qu’en demandant les en-têtes de la partie : bodyProperties: ["partId", "type", "headers"]. C’est la façon prévue par la RFC de distinguer une véritable invitation (method=REQUEST) d’un simple .ics joint à la main, sans télécharger le blob. Deux écarts sont délibérés et méritent d’être connus :
  • La projection par défaut retire les en-têtes de partie. La forme par défaut reste un sous-ensemble de la liste normative du §4.2 ; demandez headers explicitement et vous les obtenez.
  • partId et subParts survivent à une projection explicite dans l’arbre. Un arbre sans ses branches n’est plus un arbre : omettre subParts de bodyProperties se lit comme une erreur et non comme un souhait d’aplatir, et le serveur les conserve.
Deux formes ne portent aucun bodyStructure, par construction : un message que l’analyseur a refusé et qu’il a relu à plat, car il n’annonce jamais une structure qu’il n’a pas analysée, et un brouillon natif, dont la forme canonique v2 est body/html.

Comportements à connaître

  • notFound n’est jamais vide à tort. Chaque identifiant inconnu ou inanalysable d’un /get atterrit explicitement dans notFound.
  • Les valeurs de updated sont null. Conformément à la RFC 8620 §5.3, testez l’appartenance (id in updated), pas la véracité de la valeur.
  • hasAttachment veut dire pièces jointes. Le drapeau est calculé depuis la structure MIME à l’ingestion, pas depuis l’extraction de texte.
  • Un message joint est une pièce jointe, pas son contenu. La liste attachments est tirée de la structure de corps de ce message (RFC 8621 §4.1.7) : une partie message/rfc822 est la pièce jointe, et ses propres parties ne sont pas annoncées à côté d’elle. Transférer un courrier qui portait lui-même contract.pdf liste le .eml, pas contract.pdf : on ne dit jamais au lecteur qu’il a reçu des fichiers que l’expéditeur n’a pas joints. Les parties encapsulées restent atteignables : analysez le blob .eml avec Email/parse et leurs blobs se téléchargent normalement, car toutes les parties sont stockées, imbriquées comprises. Ce que la liste annonce et ce qui est téléchargeable sont deux questions distinctes.
  • Thread/get et Thread/changes ne demandent que la capacité courrier. La RFC 8621 §3 rend cette paire obligatoire : ces deux méthodes portent donc les deux capacités, urn:ietf:params:jmap:mail en primaire et v2:threads en additionnelle, et un client qui ne déclare que core + mail est servi. Les verbes de fil plus riches (query, queryChanges, set, archive, merge, split) restent des extensions v2. La forme {id, emailIds} de la RFC est un sous-ensemble strict de ce que sert l’objet : sélectionnez-la avec properties.
  • La pagination par ancre est exacte. anchor + anchorOffset + position dans Email/query suivent la RFC 8620 §5.5 à la lettre, positions négatives comprises.
  • Les brouillons se réécrivent. Une mise à jour du corps d’un message $draft fonctionne par le cycle standard destroy+create ; le serveur ne rejette pas l’édition des brouillons.
  • S’écrire à soi-même fonctionne. onSuccessUpdateEmail s’exécute après la remise, et la déduplication n’opère jamais au niveau du message.
  • L’envoi différé est de premier rang. Une EmailSubmission avec une heure d’envoi future est mise en file et annulable (Email/cancel) jusqu’à son départ ; les envois différés d’un compte désactivé sont annulés à la source.
  • La rigueur est une fonctionnalité. accountId inconnu donne accountNotFound ; une capacité absente de using donne unknownCapability ; une mauvaise référence arrière donne invalidResultReference. Le serveur est l’implémentation de référence et n’a pas de mode tolérant.

Filtrage et recherche

Email/query prend en charge l’arbre de filtre de la RFC 8621 (inMailbox, from/to/subject/text, hasKeyword, hasAttachment, dates, tailles) avec les combinateurs AND/OR/NOT, et les conditions plein texte interrogent l’index Tantivy : un message est cherchable dès que SMTP a répondu 250 OK (accepté veut dire cherchable). SearchSnippet/get renvoie les fragments surlignés pour les listes de résultats.

Mots-clés et drapeaux

Les mots-clés JMAP ($seen, $flagged, $draft, $answered, $junk, personnalisés) sont le même état que les drapeaux IMAP lisent et écrivent : un seul stockage, deux protocoles. Le pipeline anti-spam classe avec $junk ; les étiquettes utilisateur sont un objet v2 distinct (JMAP v2).

L’envoi

Le pipeline de soumission valide le from contre les identités du compte (enveloppe et en-tête : falsifier l’adresse d’un collègue échoue avec forbiddenFrom), stocke le message chiffré et le remet à la file de remise. onSuccessUpdateEmail/onSuccessDestroyEmail s’exécutent après l’acceptation de la remise, dans la réponse de la même requête.

Les accusés de lecture (MDN)

Les notifications de disposition de message sont la surface de la RFC 9007, demandée avec urn:ietf:params:jmap:mdn. Les deux sens sont servis. Lire les accusés qu’on vous a envoyés. Les MDN entrants sont rattachés, à l’ingestion SMTP, au message envoyé auquel ils répondent, appariés sur Original-Message-ID et exposés en Email.mdnBlobIds. MDN/parse transforme un tel blob en objet MDN structuré, avec disposition, originalMessageId, finalRecipient, reportingUA, originalRecipient, mdnGateway, error et extensionFields. Un client affiche donc « votre message a été affiché le … » sans réimplémenter lui-même la RFC 8098. Les verdicts sont rendus par blob, jamais effondrés au niveau de la méthode : chaque identifiant atterrit dans parsed, dans notParsable (le blob s’analyse comme un courrier mais n’est pas un MDN, ou ne s’analyse pas du tout, la RFC 9007 ne trace pas de ligne plus fine) ou dans notFound. Les groupes vides sont servis comme null, la forme de la RFC elle-même. En envoyer un. MDN/send compose un accusé RFC 8098 pour un message reçu et le remet à la file de sortie, avec disposition et, en option, subject, textBody, reportingUA, finalRecipient (qui doit être une adresse que ce compte a le droit d’utiliser, sinon forbiddenFrom), includeOriginalMessage et extensionFields. Deux propriétés de cette méthode méritent d’être connues avant de bâtir dessus :
  • Rien n’envoie de MDN automatiquement. L’émission est un geste explicite de l’utilisateur, et cette méthode est ce geste. L’accusé part avec une enveloppe d’expéditeur nulle (RFC 8098 §3) et ne génère pas de trafic de suivi s’il échoue.
  • Le « déjà envoyé » n’est pas effaçable par le client. La RFC 9007 ancre le contrôle anti-doublon sur le mot-clé $mdnsent, et OxiMail tient la lettre de la RFC : le patch onSuccessUpdateEmail doit poser keywords/$mdnsent: true, refusé nommément sinon, pour qu’un client tiers littéral fonctionne sans modification. Mais le serveur vérifie en plus un horodatage d’audit côté serveur, écrit atomiquement avec le patch. Effacer $mdnsent ne réarme pas l’envoi.
Pour la vue protocoles classiques des mêmes données, voir protocoles historiques ; pour les extensions courrier v2 (étiquettes, mise en veille, messages enregistrés), voir JMAP v2.