Conversas
`threads.list`, `list_all`, `iterate`, `get`, `update`, `trash`, `snooze`, `unsnooze` e `list_attachments`.
Leitura
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]A API pagina as conversas com um pageToken. O cliente entrega-lho como next_cursor e recebe-o de volta como cursor:, tal como em todas as outras listagens, e list_all e iterate seguem-no por si. É opaco: devolva o que lhe foi dado e nunca construa um.
Os filtros da lista são argumentos nomeados de Ruby em snake_case (label_ids:, date_from:), enquanto os campos de um corpo de pedido mantêm os nomes em camelCase da API (addLabelIds: em update). Uma conversa volta como um Hash com chaves Symbol, por isso thread[:messageCount] lê a contagem.
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]endsort:, date_from:, date_to: e from_contacts: são os controlos próprios da lista de conversas. sort: é newest, oldest, sender ou subject, e OpenEmail::THREAD_SORTS nomeia-os. As datas aceitam um Time, um DateTime ou uma string ISO 8601 com hora e fuso, e ambos os extremos estão incluídos. Uma Date do Ruby é enviada como data simples, que estes campos recusam com um 422. from_contacts: true mantém o correio cuja mensagem mais recente veio de um contacto guardado. Cada ordem pagina até ao fim sem saltar nem repetir uma conversa.
list_all devolve um único Array assim que chega a última página. iterate passa cada conversa a um bloco e só obtém a página seguinte quando o ciclo precisa dela. Sem bloco, devolve um Enumerator, por isso first(10) ou lazy param assim que têm o que precisam.
Organização
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)O estado de leitura é uma etiqueta em todos os backends aqui, por isso viaja com as listas de etiquetas, e a ordem é fixa quando define ambas: as remoções são aplicadas antes das adições, por isso um id presente nas duas listas acaba na conversa. Pelo menos um dos três campos tem de estar presente.
addLabelIds aceita ids de labels.list e os ids de sistema como ARCHIVE e STARRED. Um id que não indica nenhuma etiqueta é recusado com um 422 label_not_found em vez de ser criado, por isso crie primeiro a etiqueta com labels.create. client.threads.list(folder: "USER_DONE") lista todas as conversas com uma etiqueta, em qualquer pasta.
Anexos de uma mensagem
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?endlist_attachments devolve um Array de Hashes. content é base64, que unpack1("m") converte numa String binária, e é uma string vazia quando não foi possível encontrar os bytes armazenados, por isso verifique o seu comprimento antes de descodificar. O texto cifrado de uma mensagem encriptada está nesta lista e descarrega-se como qualquer outro ficheiro. A parte de versão PGP/MIME e qualquer assinatura destacada não estão. Guardam os seus ids em encryption.parts e mais nada.
Uma mensagem que chegou encriptada
Esta gem não encripta nem desencripta. Não consegue abrir uma mensagem que outra pessoa encriptou, e não consegue enviar uma encriptada. O pedido de envio é recusado se levar um marcador de encriptação, porque um cliente sem chave não tem nada que afirmar uma. As chaves geradas na aplicação OpenEmail vivem no browser que as criou e não chegam aqui. Quando esse browser abre uma mensagem selada, o texto simples fica nele, e a mensagem armazenada que esta chamada lê continua a ser texto cifrado. O que threads.get lhe dá é o envelope, reconhecido. Uma mensagem que chegou embrulhada em PGP ou S/MIME leva um Hash encryption, para que um decodedBody vazio deixe de ser a única coisa que lhe é entregue. encryption é o único campo de uma mensagem com que a API se compromete, porque é aquele cuja ausência não se sobrevive a adivinhar.
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]}"endDecida com OpenEmail.sealed?, nunca pela presença do campo. Dois dos cinco formatos, pgp-signed e smime-signed, descrevem um corpo que chegou em claro ao lado de uma assinatura destacada, por isso condicionar pela presença esconde correio que ninguém precisava de esconder, e o utilizador não o consegue ver nem explicar. OpenEmail.sealed? existe exatamente por essa razão. O servidor declara o conjunto selado uma vez, a cópia da gem é gerada a partir dessa mesma fonte, e uma terceira cópia escrita à mão é a cópia que se desvia. OpenEmail::MESSAGE_ENCRYPTION_FORMATS nomeia os cinco formatos.
A ausência não é texto simples. encryption falta em todas as mensagens armazenadas antes de a deteção existir, e em tudo o que chegou à caixa de correio por um caminho onde o detetor nunca correu. Regista que ninguém olhou, um facto sobre a nossa cobertura e não sobre o correio, e nada o preenche retroativamente.
Em que é que estes diferem dos restantes
- Cada entrada dos
messagesde uma conversa é o Hash que a caixa de correio guardou, sem uma lista fixa de campos. Prometer mais seria o cliente a afirmar uma normalização que ninguém faz.encryptioné o único campo com que a API se compromete mesmo assim, porque um cliente que não consegue decidir com base nele lê uma mensagem selada como uma mensagem vazia. - Um pedido que não possa ser servido fielmente é um 422
capability_unsupported, lançado comoOpenEmail::ValidationError, e não uma resposta que parece certa e está silenciosamente errada.
Parâmetros: threads.list
folderString- Que pasta listar. O servidor assume `inbox` por omissão, por isso omiti-la restringe a listagem em vez de a alargar a tudo. Aplica-se também a uma pesquisa com `query:`, a não ser que a própria pesquisa nomeie uma pasta com `in:` ou com um `is:` de pasta, como `is:sent`.
queryString- A sintaxe de pesquisa da caixa de correio. As palavras soltas têm de aparecer todas, e cada uma corresponde de forma solta: maiúsculas, acentos e separadores são ignorados e parte de uma palavra maior conta, por isso tanto `min` como `ben jamin` encontram «Benjamin». Uma frase entre aspas é procurada tal como foi escrita, salvo maiúsculas e acentos, por isso `"ben jamin"` não encontra «Ben-Jamin», e as palavras de enchimento são descartadas quando sobra outra coisa para pesquisar. Quando nada corresponde exatamente, são devolvidas em vez disso grafias próximas, pelo que `benjimin` encontra «Benjamin»: uma palavra simples, ou o valor de `from:`, `to:`, `cc:`, `subject:`, `body:`, `filename:` ou `label:`, pode diferir do início de uma palavra por um erro de escrita (uma letra trocada, em falta, a mais ou invertida) quando tem de quatro a sete letras, e por dois quando tem oito ou mais. Uma frase entre aspas, uma palavra com um algarismo, uma palavra mais curta e uma palavra excluída continuam a corresponder só de forma exata, e as páginas seguintes pesquisam da mesma forma. Restrinja com operadores como `from:ada`, `label:Invoices`, `is:unread`, `has:pdf`, `before:2026/01/31` e `older_than:1y`, e combine-os com `OR`, parênteses e um `-` à frente. Um valor que a pesquisa não consiga usar é ignorado em vez de restringir. As palavras e os operadores `from:`, `to:`, `cc:`, `subject:` e `body:` leem o remetente, os destinatários, o assunto e os primeiros 4000 caracteres do corpo da mensagem mais recente com a marcação removida, enquanto `filename:` e `has:` leem todos os anexos de toda a conversa, e as etiquetas e as pastas leem toda a conversa. Restringe o mesmo índice que a listagem sem filtros lê. As mensagens seladas não armazenam texto do corpo, por isso só o seu remetente, destinatários e assunto podem corresponder. Uma palavra simples também corresponde ao nome de qualquer anexo da conversa, seja qual for a mensagem que o trouxe.
label_idsString or Array<String>- Restringe a listagem às conversas que levem estas etiquetas. O endpoint recebe uma string separada por vírgulas, e o cliente junta um Array ou um Set numa só por si. Não há limite para quantas nomeia.
limitInteger- Quantas conversas devolver, de 1 a 100. Se for omitido, o handler usa 25. O valor por omissão vive no handler e não no schema, por isso um valor ausente e um 25 explícito comportam-se da mesma maneira.
cursorString- O `next_cursor` da página anterior, devolvido tal e qual. É o `pageToken` da API com o nome que todas as outras listagens usam, e é opaco, por isso nunca construa nem edite um.
Resposta: OpenEmail::Page
itemsArray<Hash>- Um Hash por conversa nesta página, extraído do envelope `data` da API. Cada um é apenas um marcador `object` e um `id`. A listagem não leva assunto, excerto, participantes nem etiquetas, por isso qualquer coisa mais implica chamar `threads.get` nas conversas que quiser.
items[].idString- O id da conversa, lido com `item[:id]`, para entregar a `threads.get`, `threads.update` e aos restantes sem alterações. É o mesmo id quer a linha tenha vindo de uma listagem filtrada quer de uma pesquisa com `query:`.
has_more?Boolean- Se existe mais uma página, retirado da API quando o declara e derivado de `next_cursor` quando não o declara.
next_cursorString or nil- O `nextPageToken` da API, a devolver como `cursor:` para a página seguinte, ou nil quando não há mais páginas. Um token vazio é normalizado para nil, por isso `if page.next_cursor` e uma verificação de nil concordam.