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 queEmail/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.
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
headersexplicitement et vous les obtenez. partIdetsubPartssurvivent à une projection explicite dans l’arbre. Un arbre sans ses branches n’est plus un arbre : omettresubPartsdebodyPropertiesse lit comme une erreur et non comme un souhait d’aplatir, et le serveur les conserve.
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
notFoundn’est jamais vide à tort. Chaque identifiant inconnu ou inanalysable d’un/getatterrit explicitement dansnotFound.- Les valeurs de
updatedsontnull. Conformément à la RFC 8620 §5.3, testez l’appartenance (id in updated), pas la véracité de la valeur. hasAttachmentveut 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
attachmentsest tirée de la structure de corps de ce message (RFC 8621 §4.1.7) : une partiemessage/rfc822est 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êmecontract.pdfliste le.eml, pascontract.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.emlavecEmail/parseet 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/getetThread/changesne 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:mailen primaire etv2:threadsen additionnelle, et un client qui ne déclare quecore+mailest 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 avecproperties.- La pagination par ancre est exacte.
anchor+anchorOffset+positiondansEmail/querysuivent la RFC 8620 §5.5 à la lettre, positions négatives comprises. - Les brouillons se réécrivent. Une mise à jour du corps d’un message
$draftfonctionne par le cycle standard destroy+create ; le serveur ne rejette pas l’édition des brouillons. - S’écrire à soi-même fonctionne.
onSuccessUpdateEmails’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
EmailSubmissionavec 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é.
accountIdinconnu donneaccountNotFound; une capacité absente deusingdonneunknownCapability; une mauvaise référence arrière donneinvalidResultReference. 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 lefrom 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 avecurn: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 patchonSuccessUpdateEmaildoit poserkeywords/$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$mdnsentne réarme pas l’envoi.