Threads
`threads.list`, `listAll`, `iterate`, `get`, `update`, `trash`, `snooze`, `unsnooze` e `listAttachments`.
Leitura
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)A API pagina as threads com um pageToken. O cliente entrega-lho como nextCursor e recebe-o de volta como cursor, tal como em todas as outras listagens, e listAll e iterate seguem-no por si. É opaco: devolva o que lhe foi dado e nunca construa um.
Organização
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_…')O estado de leitura É uma label em todos os backends aqui, por isso viaja com as listas de labels e a ordem é determinista quando define ambas. Pelo menos um dos três campos tem de estar presente.
Anexos de uma mensagem
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 é base64, 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
Este SDK 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, e 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 objeto encryption, para que um decodedBody vazio deixe de ser a única coisa que lhe é entregue, e encryption é o único campo em MessageResource com um tipo a sério, porque é aquele cuja ausência não se sobrevive a adivinhar.
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)}Decida com isSealed, 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. isSealed existe exatamente por essa razão: o servidor declara o conjunto selado uma vez, e uma terceira cópia escrita a partir da união é a cópia que se desvia.
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 em
ThreadResource.messagesé umMessageResource, umRecord<string, unknown>com exatamente um campo nomeado. Tipar o resto seria o cliente a afirmar uma normalização que ninguém faz, eencryptioné nomeado ainda 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, e não uma resposta que parece certa e está silenciosamente errada.
Parâmetros: threads.list (ThreadListOptions)
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 por `query`, a não ser que a própria query 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. 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 labels 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.
labelIdsstring | string[]- Restringe a listagem às threads que levem estas labels. O endpoint recebe uma string separada por vírgulas e o cliente junta-lhe um array numa só; não há limite para quantas nomeia.
limitnumber- Quantas threads devolver, de 1 a 100. 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 `nextCursor` 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: Page<ThreadSummaryResource>
itemsThreadSummaryResource[]- Uma entrada por thread nesta página, extraída do envelope `data` da API. Cada entrada é apenas um marcador de objeto e um id. A listagem não leva assunto, excerto, participantes nem labels, por isso qualquer coisa mais implica chamar `threads.get` nas threads que quiser.
items[].idstring- O id da thread, 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 por `query`.
hasMoreboolean- Se existe mais uma página, derivado de `nextCursor` nos casos em que a API não o declara.
nextCursorstring | null- O `nextPageToken` da API, a devolver como `cursor` para a página seguinte, ou null quando não há mais páginas. Um token vazio é normalizado para null, por isso uma verificação de falsy e uma verificação de null concordam.