Saltar para a documentação
PHP

Conversas

`threads->list`, `listAll`, `iterate`, `get`, `update`, `trash`, `snooze`, `unsnooze` e `listAttachments`.

Leitura

read_threads.php
$page = $client->threads->list(    folder: 'inbox',    query: 'from:ada',    labelIds: ['INBOX', 'IMPORTANT'],    limit: 25,); if ($page->nextCursor !== null) {    $nextPage = $client->threads->list(folder: 'inbox', cursor: $page->nextCursor);    echo count($nextPage), PHP_EOL;} $thread = $client->threads->get('CAHk7pQ2x9LmZ4-mail.example.com');echo $thread['messageCount'], ' ', $thread['hasUnread'] ? 'unread' : 'read', ' ', $thread['totalReplies'], PHP_EOL;

A API pagina as conversas 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.

Os filtros da lista são argumentos nomeados (labelIds:, dateFrom:), enquanto os campos de um corpo de pedido são chaves de array com os nomes da API (addLabelIds em update). Uma conversa volta como um array com chaves em camelCase, por isso $thread['messageCount'] lê a contagem.

sort_threads.php
use OpenEmail\Constants\ThreadSorts; $lastWeek = $client->threads->listAll(    sort: ThreadSorts::OLDEST,    dateFrom: new \DateTimeImmutable('-7 days'),    dateTo: new \DateTimeImmutable(),    fromContacts: true,);echo count($lastWeek), PHP_EOL; foreach ($client->threads->iterate(sort: ThreadSorts::SENDER) as $thread) {    echo $thread['id'], PHP_EOL;}

sort:, dateFrom:, dateTo: e fromContacts: são os controlos próprios da lista de conversas. sort: é newest, oldest, sender ou subject, e OpenEmail\Constants\ThreadSorts nomeia-os. As datas aceitam um DateTimeInterface, enviado como instante em UTC, ou uma string ISO 8601 com hora e fuso, e ambos os extremos estão incluídos. Uma string de data sem hora é recusada com um 422. fromContacts: 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.

listAll devolve um único array assim que chega a última página. iterate devolve um Generator que entrega cada conversa e só obtém a página seguinte quando o ciclo precisa dela, por isso um break interrompe os pedidos assim que tiver o que precisa.

Organização

organise_threads.php
$threadId = 'CAHk7pQ2x9LmZ4-mail.example.com'; $client->threads->update($threadId, ['read' => true, 'addLabelIds' => ['USER_DONE'], 'removeLabelIds' => ['INBOX']]); $client->threads->trash($threadId);$client->threads->snooze($threadId, new \DateTimeImmutable('+1 day'));$client->threads->unsnooze($threadId);

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

attachments.php
$files = $client->threads->listAttachments('CAHk7pQ2x9LmZ4-mail.example.com', 'message_4c1b257a'); foreach ($files as $file) {    echo $file['filename'], ' ', $file['contentType'], ' ', $file['size'], PHP_EOL;     $bytes = base64_decode($file['content'], true);     if ($file['content'] !== '' && $bytes !== false) {        file_put_contents(basename($file['filename']), $bytes);    }}

listAttachments devolve uma lista de arrays. content é base64, que base64_decode() volta a converter em bytes, e é uma string vazia quando não foi possível encontrar os bytes armazenados, por isso verifique-o 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 pacote 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 array 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.

encrypted_mail.php
use OpenEmail\OpenEmail; $thread = $client->threads->get('CAHk7pQ2x9LmZ4-mail.example.com'); foreach ($thread['messages'] as $message) {    if (!isset($message['encryption']) || !OpenEmail::isSealed($message)) {        continue;    }     error_log('cannot read this one: ' . $message['encryption']['format']);}

Decida com OpenEmail::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. OpenEmail::isSealed() existe exatamente por essa razão. O servidor declara o conjunto selado uma vez, a cópia do pacote é gerada a partir dessa mesma fonte, e uma terceira cópia escrita à mão é a cópia que se desvia. OpenEmail\Constants\MessageEncryptionFormats 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 messages de uma conversa é o array que a caixa de correio guardou, sem uma lista fixa de campos, por isso leia qualquer chave que não seja encryption com ?? null. 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 como ValidationException, 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.
labelIdsstring or array
Restringe a listagem às conversas que levem estas etiquetas. O endpoint recebe uma string separada por vírgulas, e o cliente junta um array numa só por si. Não há limite para quantas nomeia.
limitint
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 `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: OpenEmail\Result\Page

itemsarray
Um array 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:`.
hasMorebool
Se existe mais uma página, retirado da API quando o declara e derivado de `nextCursor` quando não o declara.
nextCursorstring or 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 null é tudo o que precisa.