Conversations
`threads.list`, `listAll`, `iterate`, `get`, `update`, `trash`, `snooze`, `unsnooze` et `listAttachments`.
Lecture
const page = await openemail.threads.list({ folder: 'inbox', query: 'from:ada', labelIds: ['INBOX', 'IMPORTANT'], limit: 25,}) const next = page.nextCursor ? await openemail.threads.list({ folder: 'inbox', cursor: page.nextCursor }) : null const thread = await openemail.threads.get('thread_…')console.log(thread.messageCount, thread.hasUnread, thread.totalReplies)L'API pagine les conversations avec un pageToken. Le client vous le remet sous le nom nextCursor et le reprend sous le nom cursor, comme pour toutes les autres listes, et listAll et iterate le suivent à votre place. Il est opaque : renvoyez ce qu'on vous a donné et n'en fabriquez jamais un.
Classement
await openemail.threads.update('thread_…', { read: true, addLabelIds: ['Done'], removeLabelIds: ['INBOX'],}) await openemail.threads.trash('thread_…')await openemail.threads.snooze('thread_…', new Date(Date.now() + 86_400_000))await openemail.threads.unsnooze('thread_…')L'état lu/non lu EST un libellé sur tous les backends ici : il voyage donc avec les listes de libellés et l'ordre est déterministe quand vous définissez les deux. Au moins un des trois champs doit être présent.
Pièces jointes d'un message
const files = await openemail.threads.listAttachments('thread_…', 'message_…') for (const file of files) { console.log(file.filename, file.contentType, file.size) if (file.content) await save(file.filename, Buffer.from(file.content, 'base64'))}content est en base64, et vaut une chaîne vide quand les octets stockés sont introuvables : vérifiez donc sa longueur avant de décoder. Le chiffré d'un message chiffré EST bien dans cette liste et se télécharge comme n'importe quel autre fichier ; la partie version PGP/MIME et toute signature détachée, non. Celles-ci gardent leurs ids dans encryption.parts, et rien de plus.
Un message arrivé chiffré
Ce SDK ne chiffre ni ne déchiffre : il ne peut pas ouvrir un message chiffré par quelqu'un d'autre, ni en envoyer un chiffré. La requête d'envoi est refusée si elle porte un marqueur de chiffrement, car un client sans clé n'a pas à en affirmer un. Les clés générées dans l'application OpenEmail vivent dans le navigateur qui les a créées et n'atteignent rien ici ; quand ce navigateur ouvre un message scellé, le texte en clair y reste, et le message stocké que cet appel lit demeure du chiffré. Ce que threads.get vous donne, c'est l'enveloppe, reconnue. Un message arrivé enveloppé en PGP ou S/MIME porte un objet encryption, si bien qu'un decodedBody vide cesse d'être la seule chose qu'on vous remet ; et encryption est le seul champ de MessageResource doté d'un vrai type, parce que c'est celui dont l'absence ne se devine pas impunément.
import { isSealed, openemail } from '@openemail/sdk' const thread = await openemail.threads.get('thread_…') for (const message of thread.messages) { if (!message.encryption) continue if (!isSealed(message)) continue console.warn('cannot read this one:', message.encryption.format)}Branchez avec isSealed, jamais sur la présence du champ. Deux des cinq formats, pgp-signed et smime-signed, décrivent un corps arrivé EN CLAIR à côté d'une signature détachée : conditionner sur la présence masque donc du courrier que personne n'avait besoin de masquer, et l'utilisateur ne peut ni le voir ni l'expliquer. isSealed est fourni exactement pour cette raison : le serveur énonce une fois l'ensemble des formats scellés, et une troisième copie écrite à la main à partir de l'union est celle qui dérive.
L'absence ne signifie pas texte en clair. encryption manque sur tous les messages stockés avant la mise en service de la détection, et sur tout ce qui a rejoint la boîte par un chemin où le détecteur n'a jamais tourné. Cela consigne que personne n'a regardé — un fait sur notre couverture, pas sur le courrier — et rien ne vient le rétro-remplir.
En quoi ces méthodes diffèrent des autres
- Chaque entrée de
ThreadResource.messagesest uneMessageResource, unRecord<string, unknown>doté d'un seul champ nommé. Typer le reste reviendrait, pour le client, à affirmer une normalisation que personne n'effectue ; etencryptionest tout de même nommé, parce qu'un client incapable de s'y brancher lit un message scellé comme un message vide. - Une requête qui ne peut pas être servie fidèlement donne un 422
capability_unsupported, et non une réponse qui a l'air correcte et qui est discrètement fausse.
Paramètres : threads.list (ThreadListOptions)
folderstring- Le dossier à lister. Le serveur le fixe par défaut à `inbox` : l'omettre restreint donc le listing au lieu de l'élargir à tout. Cela vaut aussi pour une recherche `query`, sauf si la requête nomme elle-même un dossier avec `in:` ou un `is:` de dossier tel que `is:sent`.
querystring- La syntaxe de recherche de la boîte aux lettres. Les mots simples doivent tous apparaître, et chacun correspond de façon souple : la casse, les accents et les séparateurs sont ignorés, et une portion d'un mot plus long compte, si bien que `min` comme `ben jamin` trouvent « Benjamin ». Une expression entre guillemets est recherchée telle qu'elle est écrite, à la casse et aux accents près : `"ben jamin"` ne trouve donc pas « Ben-Jamin », et les mots outils sont écartés dès qu'il reste autre chose à chercher. Affinez avec des opérateurs comme `from:ada`, `label:Invoices`, `is:unread`, `has:pdf`, `before:2026/01/31` et `older_than:1y`, et combinez-les avec `OR`, des parenthèses et un `-` en préfixe ; une valeur que la recherche ne peut pas exploiter est ignorée plutôt que restrictive. Les mots ainsi que les opérateurs `from:`, `to:`, `cc:`, `subject:` et `body:` lisent l'expéditeur, les destinataires et l'objet du message le plus récent ainsi que les 4 000 premiers caractères de son corps, balisage retiré, tandis que `filename:` et `has:` lisent toutes les pièces jointes de la conversation entière, et que les libellés et les dossiers portent sur la conversation entière. La recherche restreint le même index que lit le listing sans filtre. Les messages scellés ne stockent aucun texte de corps : seuls leur expéditeur, leurs destinataires et leur objet peuvent correspondre.
labelIdsstring | string[]- Restreint le listing aux conversations portant ces libellés. L'endpoint prend une chaîne séparée par des virgules et le client assemble un tableau en une seule chaîne pour vous ; il n'y a pas de limite au nombre de libellés que vous nommez.
limitnumber- Combien de conversations renvoyer, de 1 à 100. En l'absence de valeur, le handler utilise 25. La valeur par défaut vit dans le handler plutôt que dans le schéma : une valeur absente et un 25 explicite se comportent donc de la même façon.
cursorstring- Le `nextCursor` de la page précédente, renvoyé tel quel. C'est le `pageToken` de l'API sous le nom qu'utilisent toutes les autres listes, et il est opaque : n'en construisez ni n'en modifiez jamais un.
Réponse : Page<ThreadSummaryResource>
itemsThreadSummaryResource[]- Une entrée par conversation de cette page, extraite de l'enveloppe `data` de l'API. Chaque entrée n'est qu'un marqueur d'objet et un id. Le listing ne porte ni objet, ni extrait, ni participants, ni libellés : pour tout le reste, il faut appeler `threads.get` sur les conversations voulues.
items[].idstring- L'id de la conversation, à passer tel quel à `threads.get`, `threads.update` et aux autres. C'est le même id, que la ligne vienne d'un listing filtré ou d'une recherche `query`.
hasMoreboolean- Indique s'il existe une page suivante, déduit de `nextCursor` là où l'API ne le précise pas.
nextCursorstring | null- Le `nextPageToken` de l'API, à renvoyer comme `cursor` pour la page suivante, ou null lorsqu'il n'y a plus de page. Un jeton vide est normalisé en null : un test de valeur falsy et un test de nullité s'accordent donc.