Ir a la documentación
Ruby

Conversaciones

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

Lectura

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]

La API pagina los hilos con un pageToken. El cliente te lo entrega como next_cursor y lo recibe de vuelta como cursor:, igual que cualquier otra lista, y list_all e iterate lo siguen por ti. Es opaco: devuelve lo que te dieron y nunca construyas uno.

Los filtros de la lista son argumentos nombrados de Ruby en snake_case (label_ids:, date_from:), mientras que los campos de un cuerpo de solicitud conservan los nombres en camelCase de la API (addLabelIds: en update). Un hilo vuelve como un Hash con claves Symbol, así que thread[:messageCount] lee el recuento.

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: y from_contacts: son los controles propios de la lista de hilos. sort: es newest, oldest, sender o subject, y OpenEmail::THREAD_SORTS los nombra. Las fechas aceptan un Time, un DateTime o una cadena ISO 8601 con hora y desfase horario, y ambos extremos están incluidos. Una Date de Ruby se envía como fecha sin hora, que estos campos rechazan con un 422. from_contacts: true conserva el correo cuyo mensaje más reciente vino de un contacto guardado. Cada orden se pagina hasta el final sin saltarse ni repetir un hilo.

list_all devuelve un único Array una vez que llega la última página. iterate pasa cada hilo a un bloque y solo obtiene la página siguiente cuando el bucle la necesita. Sin bloque devuelve un Enumerator, así que first(10) o lazy se detienen en cuanto tienen lo que necesitan.

Organización

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)

El estado de lectura es una etiqueta en todos los backends de aquí, así que viaja con las listas de etiquetas, y el orden es fijo cuando indicas ambas: las eliminaciones se aplican antes que las adiciones, así que un id presente en las dos listas acaba en el hilo. Al menos uno de los tres campos debe estar presente.

addLabelIds acepta ids de labels.list y los ids de sistema como ARCHIVE y STARRED. Un id que no nombra ninguna etiqueta se rechaza con un 422 label_not_found en lugar de crearse, así que crea antes la etiqueta con labels.create. client.threads.list(folder: "USER_DONE") lista todos los hilos que llevan una etiqueta, estén en la carpeta que estén.

Adjuntos de un mensaje

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 devuelve un Array de Hashes. content es base64, que unpack1("m") convierte en una String binaria, y es una cadena vacía cuando no se han podido encontrar los bytes almacenados, así que comprueba su longitud antes de decodificar. El texto cifrado de un mensaje cifrado está en esta lista y se descarga como cualquier otro archivo. La parte de versión PGP/MIME y cualquier firma separada no lo están. Conservan sus ids en encryption.parts y nada más.

Un mensaje que llegó cifrado

Esta gema no cifra ni descifra. No puede abrir un mensaje que haya cifrado otra persona ni puede enviar uno cifrado. La solicitud de envío se rechaza si lleva un marcador de cifrado, porque un cliente sin clave no tiene por qué afirmar que la tiene. Las claves generadas en la aplicación de OpenEmail viven en el navegador que las creó y no llegan a nada de aquí. Cuando ese navegador abre un mensaje sellado, el texto plano se queda en él, y el mensaje almacenado que lee esta llamada sigue siendo texto cifrado. Lo que te da threads.get es el sobre, reconocido. Un mensaje que llegó envuelto en PGP o S/MIME lleva un Hash encryption, de modo que un decodedBody vacío deja de ser lo único que recibes. encryption es el único campo de un mensaje con el que se compromete la API, porque es aquel cuya ausencia no puedes sobrevivir adivinando.

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

Ramifica con OpenEmail.sealed?, nunca según la presencia del campo. Dos de los cinco formatos, pgp-signed y smime-signed, describen un cuerpo que llegó en claro junto a una firma separada, así que condicionar por la presencia oculta correo que nadie necesitaba ocultar, y el usuario no puede verlo ni explicárselo. OpenEmail.sealed? existe exactamente por eso. El servidor declara una sola vez el conjunto sellado, la copia de la gema se genera a partir de esa misma fuente, y una tercera copia escrita a mano es la copia que se desvía. OpenEmail::MESSAGE_ENCRYPTION_FORMATS nombra los cinco formatos.

La ausencia no significa texto plano. encryption falta en todos los mensajes almacenados antes de que se publicara la detección, y en todo lo que llegó al buzón por una ruta donde el detector nunca se ejecutó. Registra que nadie miró, un hecho sobre nuestra cobertura y no sobre el correo, y nada lo rellena a posteriori.

En qué se diferencian del resto

  • Cada entrada de los messages de un hilo es el Hash que almacenó el buzón, sin una lista fija de campos. Prometer más sería que el cliente afirmara una normalización que nadie hace. encryption es el único campo con el que la API se compromete de todos modos, porque un cliente que no puede ramificar según él lee un mensaje sellado como uno vacío.
  • Una solicitud que no puede atenderse fielmente es un 422 capability_unsupported, lanzado como OpenEmail::ValidationError, y no una respuesta que parece correcta y está silenciosamente equivocada.

Parámetros: threads.list

folderString
Qué carpeta listar. El servidor usa `inbox` por defecto, así que omitirlo restringe el listado en lugar de ampliarlo a todo. También se aplica a una búsqueda con `query:`, salvo que la propia consulta nombre una carpeta con `in:` o con un `is:` de carpeta como `is:sent`.
queryString
La sintaxis de búsqueda del buzón. Todas las palabras sueltas deben aparecer, y cada una coincide de forma laxa: se ignoran mayúsculas, acentos y separadores, y una parte de una palabra más larga cuenta, así que tanto `min` como `ben jamin` encuentran «Benjamin». Una frase entre comillas coincide tal como está escrita, salvo por mayúsculas y acentos, de modo que `"ben jamin"` no encuentra «Ben-Jamin», y las palabras vacías se descartan cuando queda algo más que buscar. Cuando nada coincide exactamente, se devuelven en su lugar grafías parecidas, así que `benjimin` encuentra «Benjamin»: una palabra suelta, o el valor de `from:`, `to:`, `cc:`, `subject:`, `body:`, `filename:` o `label:`, puede diferir del comienzo de una palabra en una errata (una letra cambiada, omitida, sobrante o intercambiada) si tiene de cuatro a siete letras y en dos si tiene ocho o más. Una frase entre comillas, una palabra con un dígito, una palabra más corta y una palabra excluida siguen coincidiendo solo de forma exacta, y las páginas siguientes buscan de la misma manera. Restringe con operadores como `from:ada`, `label:Invoices`, `is:unread`, `has:pdf`, `before:2026/01/31` y `older_than:1y`, y combínalos con `OR`, paréntesis y un `-` inicial. Un valor que la búsqueda no pueda usar se ignora en lugar de restringir. Las palabras y los operadores `from:`, `to:`, `cc:`, `subject:` y `body:` leen el remitente, los destinatarios y el asunto del mensaje más reciente, y los primeros 4.000 caracteres de su cuerpo con el marcado eliminado, mientras que `filename:` y `has:` leen todos los adjuntos de la conversación completa, y las etiquetas y las carpetas leen la conversación completa. Restringe el mismo índice que lee el listado sin filtrar. Los mensajes sellados no almacenan texto del cuerpo, así que solo pueden coincidir su remitente, sus destinatarios y su asunto. Una palabra suelta también coincide con el nombre de cualquier adjunto de la conversación, sea cual sea el mensaje que lo trajo.
label_idsString or Array<String>
Restringe el listado a los hilos que llevan estas etiquetas. El endpoint recibe una cadena separada por comas, y el cliente une un Array o un Set en una sola por ti. No hay límite de cuántas nombres.
limitInteger
Cuántos hilos devolver, de 1 a 100. Si se omite, el handler usa 25. El valor por defecto vive en el handler y no en el esquema, así que un valor ausente y un 25 explícito se comportan igual.
cursorString
El `next_cursor` de la página anterior, devuelto literalmente. Es el `pageToken` de la API bajo el nombre que usa cualquier otra lista, y es opaco, así que nunca construyas ni edites uno.

Respuesta: OpenEmail::Page

itemsArray<Hash>
Un Hash por hilo en esta página, extraído del sobre `data` de la API. Cada uno es solo un marcador `object` y un `id`. El listado no lleva asunto, fragmento, participantes ni etiquetas, así que cualquier otra cosa implica llamar a `threads.get` sobre los hilos que te interesen.
items[].idString
El id del hilo, que se lee con `item[:id]`, para pasárselo sin cambios a `threads.get`, `threads.update` y los demás. Es el mismo id tanto si la fila vino de un listado filtrado como de una búsqueda con `query:`.
has_more?Boolean
Si hay otra página más, tomado de la API cuando lo indica y derivado de `next_cursor` cuando no.
next_cursorString or nil
El `nextPageToken` de la API, para reenviarlo como `cursor:` en la página siguiente, o nil cuando no hay más páginas. Un token vacío se normaliza a nil, así que `if page.next_cursor` y una comprobación de nil coinciden.