Aller à la documentation
Ruby

Fils

`threads.list`, `list_all`, `iterate`, `get`, `update`, `trash`, `snooze`, `unsnooze` et `list_attachments`.

Lecture

read_threads.rb
page = client.threads.list(  folder: "inbox",  query: "from:ada",  label_ids: ["INBOX", "IMPORTANT"],  limit: 25) if page.next_cursor  next_page = client.threads.list(folder: "inbox", cursor: page.next_cursor)  puts next_page.items.sizeend thread = client.threads.get("CAHk7pQ2x9LmZ4-mail.example.com")puts thread[:messageCount], thread[:hasUnread], thread[:totalReplies]

L'API pagine les fils avec un pageToken. Le client vous le remet sous le nom next_cursor et le reprend sous le nom cursor:, comme pour toutes les autres listes, et list_all et iterate le suivent à votre place. Il est opaque : renvoyez ce qu'on vous a donné et n'en fabriquez jamais un.

Les filtres de liste sont des mots-clés Ruby en snake_case (label_ids:, date_from:), alors que les champs d'un corps de requête gardent les noms en camelCase de l'API (addLabelIds: sur update). Un fil revient sous forme de Hash à clés Symbol : thread[:messageCount] lit donc le nombre de messages.

sort_threads.rb
last_week = client.threads.list_all(  sort: "oldest",  date_from: Time.now - (7 * 86_400),  date_to: Time.now,  from_contacts: true)puts last_week.size client.threads.iterate(sort: "sender") do |thread|  puts thread[:id]end

sort:, date_from:, date_to: et from_contacts: sont les commandes propres à la liste des fils. sort: vaut newest, oldest, sender ou subject, et OpenEmail::THREAD_SORTS les nomme. Les dates prennent un Time, un DateTime ou une chaîne ISO 8601 avec une heure et un décalage, et les deux bornes sont incluses. Une Date Ruby est envoyée comme une date nue, que ces champs refusent avec un 422. from_contacts: true garde le courrier dont le message le plus récent vient d'un contact enregistré. Chaque ordre se pagine jusqu'au bout sans sauter ni répéter un fil.

list_all renvoie un seul Array une fois la dernière page reçue. iterate passe chaque fil à un bloc et ne récupère la page suivante que lorsque la boucle en a besoin. Sans bloc, il renvoie un Enumerator : first(10) ou lazy s'arrêtent donc dès qu'ils ont ce qu'il leur faut.

Organisation

organise_threads.rb
thread_id = "CAHk7pQ2x9LmZ4-mail.example.com" client.threads.update(thread_id, read: true, addLabelIds: ["USER_DONE"], removeLabelIds: ["INBOX"]) client.threads.trash(thread_id)client.threads.snooze(thread_id, Time.now + 86_400)client.threads.unsnooze(thread_id)

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 fixe quand vous définissez les deux. Les retraits sont appliqués avant les ajouts : un id présent dans les deux listes finit donc sur le fil. Au moins un des trois champs doit être présent.

addLabelIds prend des ids issus de labels.list et les ids système comme ARCHIVE et STARRED. Un id qui ne désigne aucun libellé est refusé avec un 422 label_not_found au lieu d'être créé : créez donc d'abord le libellé avec labels.create. client.threads.list(folder: "USER_DONE") liste tous les fils qui portent un libellé, quel que soit leur dossier.

Pièces jointes d'un message

attachments.rb
files = client.threads.list_attachments("CAHk7pQ2x9LmZ4-mail.example.com", "message_4c1b257a") files.each do |file|  puts "#{file[:filename]} #{file[:contentType]} #{file[:size]}"  File.binwrite(file[:filename], file[:content].unpack1("m")) unless file[:content].to_s.empty?end

list_attachments renvoie un Array de Hashes. content est en base64, que unpack1("m") transforme en String binaire, 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é

Cette gem ne chiffre ni ne déchiffre. Elle 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 lit cet appel est toujours chiffré. Ce que threads.get vous donne, c'est l'enveloppe, reconnue comme telle. Un message arrivé enveloppé en PGP ou S/MIME porte un Hash encryption : un decodedBody vide cesse ainsi d'être la seule chose qu'on vous remet. encryption est le seul champ d'un message sur lequel l'API s'engage, car c'est le seul dont vous ne pouvez pas vous permettre de deviner l'absence.

encrypted_mail.rb
thread = client.threads.get("CAHk7pQ2x9LmZ4-mail.example.com") thread[:messages].each do |message|  next unless message[:encryption]  next unless OpenEmail.sealed?(message)   warn "cannot read this one: #{message[:encryption][:format]}"end

Branchez-vous sur OpenEmail.sealed?, 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. OpenEmail.sealed? existe exactement pour cette raison. Le serveur énonce une fois l'ensemble des formats scellés, la copie de la gem est générée à partir de cette même source, et une troisième copie écrite à la main est celle qui dérive. OpenEmail::MESSAGE_ENCRYPTION_FORMATS nomme les cinq formats.

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 des messages d'un fil est le Hash que la boîte a stocké, sans liste de champs fixe. Promettre davantage reviendrait pour le client à affirmer une normalisation que personne n'effectue. encryption est malgré tout le seul champ sur lequel l'API s'engage, car un client qui ne peut pas se brancher dessus lit un message scellé comme un message vide.
  • Une requête qui ne peut pas être servie fidèlement donne un 422 capability_unsupported, levé sous forme d'OpenEmail::ValidationError, et non une réponse qui a l'air correcte et qui est discrètement fausse.

Paramètres : threads.list

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. Quand rien ne correspond exactement, des graphies proches sont renvoyées à la place, si bien que `benjimin` trouve « Benjamin » : un mot simple, ou la valeur de `from:`, `to:`, `cc:`, `subject:`, `body:`, `filename:` ou `label:`, peut s'écarter du début d'un mot d'une faute de frappe (une lettre changée, manquante, en trop ou inversée) s'il compte de quatre à sept lettres, et de deux s'il en compte huit ou plus. Une expression entre guillemets, un mot contenant un chiffre, un mot plus court et un mot exclu doivent toujours correspondre exactement, et les pages suivantes cherchent de la même façon. 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. Un mot simple correspond aussi au nom de n'importe quelle pièce jointe de la conversation, quel que soit le message qui la portait.
label_idsString or Array<String>
Restreint le listing aux fils portant ces libellés. L'endpoint prend une chaîne séparée par des virgules, et le client y assemble pour vous un Array ou un Set. Il n'y a pas de limite au nombre de libellés que vous nommez.
limitInteger
Combien de fils 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 `next_cursor` 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 : OpenEmail::Page

itemsArray<Hash>
Un Hash par fil de cette page, extrait de l'enveloppe `data` de l'API. Chacun n'est qu'un marqueur `object` 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 fils voulus.
items[].idString
L'id du fil, lu avec `item[:id]`, à 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:`.
has_more?Boolean
Indique s'il y a une page suivante, repris de l'API quand elle l'indique et dérivé de `next_cursor` sinon.
next_cursorString or nil
Le `nextPageToken` de l'API, à renvoyer comme `cursor:` pour la page suivante, ou nil lorsqu'il n'y a plus de page. Un jeton vide est normalisé en nil : `if page.next_cursor` et un test de nil s'accordent donc.