Saltar para a documentação
Python

Conversas

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

Leitura

read_threads.py
from openemail import openemail page = openemail.threads.list(    folder='inbox',    query='from:ada',    label_ids=['INBOX', 'IMPORTANT'],    limit=25,) next_page = (    openemail.threads.list(folder='inbox', cursor=page['nextCursor'])    if page['nextCursor']    else None) thread = openemail.threads.get('thread_…')print(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 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 em snake_case (label_ids=, date_from=), enquanto as chaves do corpo de um pedido mantêm os nomes em camelCase da API (addLabelIds em update). Uma página e uma conversa voltam como dicionários, por isso leem-se com page['nextCursor'] e thread['messageCount'].

sort_threads.py
from datetime import datetime, timedelta, timezone from openemail import openemail now = datetime.now(timezone.utc) last_week = openemail.threads.list_all(    sort='oldest',    date_from=now - timedelta(days=7),    date_to=now,    from_contacts=True,) for thread in openemail.threads.iterate(sort='sender'):    print(thread['id'])

sort, date_from, date_to e from_contacts são os controlos próprios da lista de conversas. sort é newest, oldest, sender ou subject, as datas aceitam um datetime ou uma string ISO 8601 e ambos os extremos estão incluídos, e from_contacts 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. Um datetime sem tzinfo é lido como hora local.

Organização

organise_threads.py
from datetime import datetime, timedelta, timezone from openemail import openemail openemail.threads.update('thread_…', {    'read': True,    'addLabelIds': ['USER_DONE'],    'removeLabelIds': ['INBOX'],}) openemail.threads.trash('thread_…')openemail.threads.snooze('thread_…', datetime.now(timezone.utc) + timedelta(days=1))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.

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. threads.list(folder='USER_DONE') lista todas as conversas com uma etiqueta, em qualquer pasta.

Anexos de uma mensagem

attachments.py
import base64from pathlib import Path from openemail import openemail files = openemail.threads.list_attachments('thread_…', 'message_…') for file in files:    print(file['filename'], file['contentType'], file['size'])     if file['content']:        name = Path(file['filename']).name        Path(name).write_bytes(base64.b64decode(file['content']))

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 navegador que as criou e não chegam aqui, e quando esse navegador 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 traz um dicionário encryption, para que um decodedBody vazio deixe de ser a única coisa que lhe é entregue. É a única chave do dicionário cuja ausência não se pode dar ao luxo de adivinhar, e MessageEncryption em openemail.types descreve-a.

encrypted_mail.py
import sys from openemail import is_sealed, openemail thread = openemail.threads.get('thread_…') for message in thread['messages']:    if not message.get('encryption'):        continue    if not is_sealed(message):        continue     print('cannot read this one:', message['encryption']['format'], file=sys.stderr)

Decida com is_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. is_sealed 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 é um MessageResource, um dict[str, Any] simples cujo tipo não nomeia nenhum campo, nem sequer encryption. Tipar os campos seria o cliente a afirmar uma normalização que ninguém faz. Leia encryption com message.get('encryption') e decida com is_sealed, 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

folderstr
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`.
querystr
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, enquanto 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 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. Uma palavra simples também corresponde ao nome de qualquer anexo da conversa, seja qual for a mensagem que o trouxe.
label_idsstr | Sequence[str]
Restringe a listagem às conversas que tenham estas etiquetas. O endpoint recebe uma string separada por vírgulas, e o cliente junta por si uma lista ou um tuplo numa só. Não há limite para quantas indica.
limitint
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.
cursorstr
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]

itemslist[ThreadSummaryResource]
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[].idstr
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`.
hasMorebool
Se existe mais uma página, derivado de `nextCursor` nos casos em que a API não o declara.
nextCursorstr | None
O `nextPageToken` da API, a devolver como `cursor` para a página seguinte, ou `None` quando não há mais páginas. Um token vazio é normalizado para `None`, por isso uma verificação de falsy e uma verificação de `None` concordam.

Referência