Перейти к документации
SDK

Переписки

`threads.list`, `listAll`, `iterate`, `get`, `update`, `trash`, `snooze`, `unsnooze` и `listAttachments`.

Чтение

read-threads.ts
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)

API разбивает переписки на страницы через pageToken. Клиент отдаёт его вам как nextCursor и принимает обратно как cursor, как и в любом другом списке, а listAll и iterate проходят по страницам за вас. Он непрозрачен: возвращайте то, что вам дали, и никогда не составляйте его сами.

Упорядочивание

organise-threads.ts
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_…')

Состояние прочтения здесь ЯВЛЯЕТСЯ ярлыком на любом бэкенде, поэтому оно передаётся вместе со списками ярлыков, и порядок детерминирован, когда вы задаёте и то, и другое. Должно присутствовать хотя бы одно из трёх полей.

Вложения письма

attachments.ts
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, а при невозможности найти сохранённые байты — пустая строка, поэтому перед декодированием проверяйте её длину. Шифротекст зашифрованного письма В этом списке ЕСТЬ и скачивается, как любой другой файл; часть с версией PGP/MIME и отделённая подпись — нет. За ними остаются только их идентификаторы в encryption.parts, и ничего больше.

Письмо, пришедшее зашифрованным

Этот SDK ничего не шифрует и не расшифровывает: он не может открыть письмо, зашифрованное кем-то другим, и не может отправить зашифрованное. Запрос на отправку отклоняется, если он несёт маркер шифрования, потому что клиенту без ключа незачем что-либо о нём утверждать. Ключи, созданные в приложении OpenEmail, живут в том браузере, который их создал, и сюда не попадают, а когда этот браузер открывает запечатанное письмо, открытый текст остаётся в нём, и сохранённое письмо, которое читает этот вызов, по-прежнему остаётся шифротекстом. То, что даёт вам threads.get, — это распознанный конверт. Письмо, пришедшее в обёртке PGP или S/MIME, несёт объект encryption, поэтому пустой decodedBody перестаёт быть единственным, что вам вручили; encryption — единственное поле MessageResource с настоящим типом, потому что это единственное поле, чьё отсутствие нельзя пережить, угадывая.

encrypted-mail.ts
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)}

Ветвитесь по isSealed, а не по наличию поля. Два из пяти форматов, pgp-signed и smime-signed, описывают тело, пришедшее ОТКРЫТЫМ рядом с отделённой подписью, поэтому проверка на наличие поля прячет почту, которую прятать было не нужно, а пользователь не может её ни увидеть, ни объяснить. isSealed поставляется именно поэтому: сервер задаёт набор запечатанных форматов один раз, а третья копия, выписанная из объединения типов, — это та самая копия, которая разъедется.

Отсутствие — это не открытый текст. encryption отсутствует у всех писем, сохранённых до выпуска определителя, и у всего, что попало в ящик путём, где определитель не отрабатывал. Это фиксирует, что никто не смотрел, — факт о нашем покрытии, а не о почте, и задним числом поле ничем не заполняется.

Чем это отличается от остального

  • Каждая запись в ThreadResource.messages — это MessageResource, то есть Record<string, unknown> ровно с одним именованным полем. Типизировать остальное значило бы, что клиент утверждает нормализацию, которой никто не выполняет, а encryption всё же названо, потому что клиент, который не может по нему ветвиться, читает запечатанное письмо как пустое.
  • Запрос, который нельзя обслужить добросовестно, даёт 422 capability_unsupported, а не ответ, который выглядит правильным и тихо неверен.

Параметры: threads.list (ThreadListOptions)

folderstring
Какую папку выводить. Сервер по умолчанию подставляет `inbox`, поэтому пропуск параметра сужает выдачу, а не расширяет её до всего. Он применяется и к поиску через `query`, если только сам запрос не называет папку через `in:` или через папочный `is:`, например `is:sent`.
querystring
Синтаксис поиска по почтовому ящику. Все простые слова должны встретиться, и каждое сопоставляется нестрого: регистр, диакритика и разделители игнорируются, а часть более длинного слова засчитывается, поэтому и `min`, и `ben jamin` находят «Benjamin». Фраза в кавычках сопоставляется как написана, с точностью до регистра и диакритики, поэтому `"ben jamin"` не находит «Ben-Jamin», а служебные слова отбрасываются, если остаётся что-то ещё, по чему искать. Сужайте выдачу операторами вроде `from:ada`, `label:Invoices`, `is:unread`, `has:pdf`, `before:2026/01/31` и `older_than:1y` и комбинируйте их через `OR`, скобки и ведущий `-`; значение, которое поиск не может использовать, игнорируется, а не сужает выдачу. Слова и операторы `from:`, `to:`, `cc:`, `subject:` и `body:` читают отправителя, получателей, тему последнего письма и первые 4 000 символов его тела с вырезанной разметкой, тогда как `filename:` и `has:` читают все вложения всей переписки, а ярлыки и папки читают переписку целиком. Поиск сужает тот же индекс, который читает выдача без фильтров. Запечатанные письма не хранят текста тела, поэтому в них могут совпасть только отправитель, получатели и тема.
labelIdsstring | string[]
Ограничить выдачу переписками, несущими эти ярлыки. Эндпоинт принимает строку с разделителями-запятыми, а клиент склеивает массив в неё за вас; ограничения на количество названных ярлыков нет.
limitnumber
Сколько переписок вернуть, от 1 до 100. Если не указано, обработчик использует 25. Значение по умолчанию задано в обработчике, а не в схеме, поэтому отсутствие значения и явная 25 ведут себя одинаково.
cursorstring
`nextCursor` предыдущей страницы, переданный обратно дословно. Это `pageToken` из API под тем именем, которое использует любой другой список; он непрозрачен, поэтому никогда не составляйте и не правьте его.

Ответ: Page<ThreadSummaryResource>

itemsThreadSummaryResource[]
По одной записи на каждую переписку на этой странице, вынутые из конверта `data` в API. Каждая запись — это только маркер объекта и идентификатор. В выдаче нет ни темы, ни фрагмента, ни участников, ни ярлыков, поэтому за чем-то большим придётся вызвать `threads.get` для нужных переписок.
items[].idstring
Идентификатор переписки, который без изменений передаётся в `threads.get`, `threads.update` и остальные методы. Он один и тот же, пришла ли строка из отфильтрованной выдачи или из поиска через `query`.
hasMoreboolean
Есть ли следующая страница; выводится из `nextCursor` там, где API этого не сообщает.
nextCursorstring | null
`nextPageToken` из API, который отправляется обратно как `cursor` за следующей страницей, или null, когда следующей страницы нет. Пустой токен нормализуется в null, поэтому проверка на ложность и проверка на null дают один результат.