Цепочки
Чтение и упорядочивание почты.
Выполняет любой из 7 запросов на этой странице в вашем рабочем пространстве, с вашим собственным ключом.
Получение списка
GET /threads?folder=inbox. Переданный query ищет по тому же локальному индексу. Обычные слова должны встретиться все, и каждое совпадает нестрого, без учёта регистра, диакритики и разделителей, так что min находит «Benjamin». Фраза в кавычках сопоставляется как написана, если не считать регистра и диакритики, так что "ben jamin" не находит «Ben-Jamin». Служебные слова вроде the или emails отбрасываются из списка обычных слов, когда остаётся что искать помимо них. Операторы вроде from:, to:, subject:, label:, is:unread, has:pdf, after:2026/01/31 и newer_than:7d сужают поиск, а OR, скобки и ведущий - их комбинируют. Получатели хранятся одним списком без ролей и никогда не содержат Bcc, так что cc: читает то же поле, что и to:, а bcc: сам по себе ничего не находит. from:me — это отправленная вами почта, а to:me — почта, несущая один из ваших собственных адресов, включая псевдонимы, среди получателей или как адрес доставки.
Слова и операторы from:, to:, cc:, subject: и body: читают самое новое сообщение каждой цепочки: его отправителя, получателей, тему и первые 4 000 символов тела. filename: и has: читают все вложения всей переписки, а label:, in: и is: читают всю переписку. folder продолжает действовать, если только запрос не называет папку через in: или через is:, обозначающий папку, например is:sent, а in:anywhere ищет по всем папкам — и сам по себе, и рядом с другими условиями. Список черновиков — исключение: он остаётся в черновиках, что бы ни называл запрос.
Значение, которое поиск не может использовать, игнорируется, а не сужает выборку, так что опечатка в значении расширяет результат, а не опустошает его: category:, larger:, smaller:, size:, messagesize:, list:, rfc822msgid:, received:, sent:, слова-категории вроде is:promotions, слово при has:, не называющее вид вложения, importance: со значением, отличным от high или low, нечитаемая дата и длительность, единица которой не h, d, w, m или y. Имя оператора, которое поиску неизвестно, например project:, ищется как обычный текст. Даты читают последнюю активность в цепочке, в UTC, причём after: включает названный день, а before: исключает; записывайте дату как YYYY/MM/DD, YYYY-MM-DD, YYYYMMDD, просто год, либо секунды или миллисекунды эпохи.
nextPageToken непрозрачен. Возвращайте ровно то, что вам выдали; никогда не конструируйте и не правьте его. Его форма не часть контракта.
Получение
GET /threads/{id} возвращает все сообщения цепочки, а не только самое свежее, вместе с её метками и признаком того, есть ли в ней непрочитанное.
Сообщения, пришедшие зашифрованными
Этот API ничего не шифрует и не расшифровывает. Он не может открыть сообщение, зашифрованное кем-то другим, и не может отправить зашифрованное. Запрос, несущий маркер шифрования, отклоняется с 422, потому что устанавливать его вправе только те поверхности, у которых есть ключи, а ни у одного клиента API ключа нет. Что он делает — так это РАСПОЗНАЁТ запечатанный конверт на входе, по верхнеуровневому Content-Type и не более того, и затем сообщает об этом в сообщении.
OpenEmail теперь и сам держит ключи, и стоит точно сказать, какую половину и где. Владелец почтового ящика создаёт OpenPGP-личность в своём браузере и публикует ОТКРЫТЫЙ ключ в каталоге, который могут разрешить другие вошедшие отправители OpenEmail. Закрытая половина создаётся в том браузере, никогда не отправляется сюда и невосстановима, так что ничто в этом API не может ничего расшифровать, и ни обращение в поддержку, ни судебный запрос, ни наша резервная копия не дадут ключа, которым это можно было бы сделать. Веб-приложение теперь умеет ОТКРЫВАТЬ сообщение PGP/MIME или inline-PGP, когда ключ есть в браузере читателя, но расшифровка происходит во вкладке, и открытый текст никогда не записывается обратно: сохранённое сообщение остаётся шифротекстом, и ни один ответ этого API никогда не несёт открытый текст. Приложение теперь умеет запечатать новое сообщение в браузере и отправить его: редактор шифрует на опубликованные ключи получателей, и почта уходит как PGP/MIME. Этот API по-прежнему ничего запечатать не может, так что поле ниже описывает и почту, зашифрованную кем-то другим, и почту, запечатанную во вкладке OpenEmail.
Это заслуживает отдельного поля из-за того, какова была альтернатива. Запечатанное сообщение не хранит читаемого тела, так что decodedBody приходит как "" — те же байты, что и у сообщения, у которого содержимого действительно не было. encryption — это то, что позволяет различить их до того, как вы начнёте действовать, и это утверждение о конверте, а не проверка: видеть, что сообщение запечатано, — не то же самое, что открыть его.
{ "object": "thread", "id": "thread_2f9b…", "messages": [ { "id": "msg_7c41…", "subject": "Q3 numbers", "decodedBody": "", "encryption": { "format": "pgp-mime", "detectedAt": "2026-08-30T09:14:22.117Z", "rawRetained": false, "parts": [ { "index": 0, "attachmentId": "msg_7c41…-0", "role": "version" }, { "index": 1, "attachmentId": "msg_7c41…-1", "role": "ciphertext" } ] } } ] }encryption
format'pgp-mime' | 'pgp-signed' | 'pgp-inline' | 'smime-encrypted' | 'smime-signed'- Какой конверт пришёл. Считывается с верхнеуровневого `Content-Type` (его параметр `protocol` для PGP, его `smime-type` для S/MIME) либо, для `pgp-inline`, с тела, начинающегося с заголовка PGP-брони. Часть `pkcs7-mime`, вовсе не несущая `smime-type`, читается как `smime-encrypted`, каковой её по умолчанию и делает RFC 8551.
detectedAtstring- ISO 8601, когда отработал детектор, то есть когда сообщение было принято здесь. Это ничего не говорит о том, когда и кем сообщение было зашифровано.
rawRetainedboolean- Сохранены ли исходные байты RFC822, чтобы сообщение можно было вернуть целиком. Сегодня false у каждого сообщения, поскольку здесь пока ничто не хранит сырую почту. Оно есть в ответе уже сейчас, чтобы день, когда это изменится, не стал ещё и днём повторной миграции всех сохранённых сообщений.
partsobject[]- Части конверта, которые использует этот формат. Присутствует всегда, когда присутствует `encryption`, и пусто, когда называть нечего: у `pgp-inline` вообще нет отдельной части, поскольку его броня И ЕСТЬ тело и приходит в `decodedBody`.
parts[].indexnumber- Какой MIME-частью исходного сообщения это было, считая по частям в том виде, в каком они пришли, а не по `attachments`. Эти два списка различаются, и ровно поэтому это записывается.
parts[].attachmentIdstring- Идентификатор, который эта часть несёт в `attachments`, если она там вообще появляется: идентификатор сообщения с добавленным индексом части. Часть `ciphertext` перечисляется и скачивается как любой другой файл; `version` и `signature` из списка исключены, так что их идентификаторы лишь соотносят два представления, и не более того. Эндпоинт вложений их не вернёт.
parts[].role'version' | 'ciphertext' | 'signature'- `version` — управляющая часть PGP/MIME, `ciphertext` — само сообщение, `signature` — открепленная подпись. Забирать стоит только `ciphertext`; две другие — протокольная мебель, которая раньше отображалась как мусорные вложения, а теперь нет.
| format | Что пришло | Тело |
|---|---|---|
| pgp-mime | Конверт PGP/MIME: multipart/encrypted с protocol=application/pgp-encrypted. | Запечатано |
| pgp-inline | Броня в самом теле. Считывается только с текста тела, так что ответ, просто цитирующий бронированный блок, не будет принят за такое сообщение. | Запечатано |
| smime-encrypted | Часть S/MIME pkcs7-mime с smime-type=enveloped-data либо вовсе без smime-type. | Запечатано |
| pgp-signed | Открепленная PGP-подпись рядом с сообщением: multipart/signed с protocol=application/pgp-signature. | Читаемо |
| smime-signed | Открепленная подпись S/MIME: протокол pkcs7-signature либо smime-type=signed-data. | Читаемо |
Подписано — не значит запечатано, и ветвление по наличию encryption вместо format переворачивает это ровно наоборот. Подпись — это утверждение о том, кто написал сообщение, а не обёртка вокруг него: тело подписанного сообщения открыто и читается как любое другое. Считайте нечитаемыми pgp-mime, pgp-inline и smime-encrypted, а два подписанных формата — обычной почтой.
Что меняется у запечатанного сообщения
Что-либо меняют только три запечатанных формата, и меняется это при приёме, а не в этом ответе. Всё, что читало бы тело, отходит в сторону вместо того, чтобы прочитать шифротекст и сообщить результат, которого оно не могло получить:
- Поиск по телу. Сообщение индексируется с пустым фрагментом тела, так что его по-прежнему можно найти по отправителю, теме, адресу и метке — и нельзя по тому, что внутри.
- Проход фишинг-оценщика по телу. Вердикт всё равно приходит и говорит, чего он не смог:
risk.signalsнесётbody-encrypted, аrisk.aiCheckedравно false. - Проверка на авторство ИИ, которая отказывается отвечать, а не гадает:
aiWritten.levelравенunknown, аaiWritten.skipped—encrypted. - Условия по телу в правилах. Условия по конверту и заголовкам отрабатывают в точности как раньше; правило, спрашивавшее про тело, записывается как невычисленное, а не засчитывается как несовпадение, потому что «не совпало» и «не удалось прочитать» — разные ответы.
- Импорт приглашений календаря. Приглашение находится внутри шифротекста, и построение события из конверта поставило бы неверную запись в настоящий календарь.
- Сводки цепочки и эмбеддинги — для всей цепочки. Достаточно одного запечатанного ответа. Сводка — это прочтение моделью открытого текста, сохранённое как метаданные в открытом виде, и это единственное место в конвейере, где тело утекло бы в хранилище, которое никто телом не считает.
Всё, чему тело не нужно, остаётся нетронутым:
- DMARC, DKIM и SPF. Они считываются с
Authentication-Results, который шифротекст не скрывает, так что зашифрованное сообщение всё равно получает настоящий вердикт аутентификации, а не никакого. - Сшивание в цепочки, раскладка спама и список блокировок: всё это работа с конвертом и заголовками.
- Вложения. Часть с шифротекстом остаётся в
attachmentsпод именемencrypted-message.asc, если она пришла без имени, и скачивается через эндпоинт ниже. Это ровно то, что забирает и расшифровывает в браузере собственный читатель веб-приложения; для клиента API, у которого ключа нет, эта загрузка остаётся единственным способом прочитать письмо. Откройте его в клиенте, у которого ключ есть. - Подписанное сообщение ничего из этого не теряет. Все перечисленные выше проверки продолжают на нём работать, и ничего не утаивается, — поэтому список запечатанных содержит три формата, а не пять.
Отсутствие encryption — не утверждение о том, что текст открыт. Оно означает, что никто не смотрел: сообщение старше детектора или попало в почтовый ящик путём, на котором детектор не работает. Ничто это не дозаполняет, так что поле, говорящее «мы не проверяли», никогда нельзя читать как «мы проверили и ничего не нашли».
Пометки и метки
PATCH /threads/{id} принимает read, addLabelIds и removeLabelIds. Состояние прочтения — это метка на любом бэкенде, который поддерживает продукт, так что установка read и перемещение меток в одном вызове делают порядок детерминированным.
{ "read": true, "addLabelIds": ["USER_INVOICES"] }TRASH и SNOOZED здесь отклоняются с label_not_directly_settable. Ни одно из этих состояний не задаётся одной лишь меткой (удаление в корзину ещё и снимает метки папок, а отложенному нужно время пробуждения, хранимое рядом), так что ручная установка оставит цепочку в состоянии, которого приложение никогда не создаёт и из которого не может выйти. Используйте эндпоинты ниже.
Корзина и откладывание
| Эндпоинт | Действие |
|---|---|
| POST /threads/{id}/trash | Перемещает в Bin, снимая разом INBOX, SPAM, SNOOZED и ARCHIVE. |
| POST /threads/{id}/snooze | Тело { "wakeAt": "…" }. Прячет цепочку и планирует её возвращение. |
| POST /threads/{id}/unsnooze | Возвращает её сейчас же и отменяет запланированное возвращение. |
Откладывание записывает две вещи: метку, которая прячет цепочку, и запись, которая её возвращает. Сделать одно без другого — ровно та причина, по которой это эндпоинты, а не правки меток.
Вложения
GET /threads/{id}/messages/{messageId}/attachments возвращает каждое вложение с filename, contentType, size и content в base64. content — пустая строка там, где сохранённые байты не удалось найти, так что проверяйте её длину перед декодированием.
Зашифрованный конверт представлен здесь не целиком. Шифротекст есть (это и есть сообщение, и его загрузка — единственный способ для клиента API прочитать эту почту), но часть version у PGP/MIME и любая открепленная подпись из списка исключены, потому что они отображались как мусорные вложения и вызывающей стороне с ними делать нечего. Обе сохраняют свои идентификаторы в encryption.parts, что соотносит два представления; этот эндпоинт их не возвращает.