Wątki
`threads.list`, `listAll`, `iterate`, `get`, `update`, `trash`, `snooze`, `unsnooze` i `listAttachments`.
Odczyt
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 stronicuje wątki za pomocą pageToken. Klient podaje ci go jako nextCursor i przyjmuje z powrotem jako cursor, tak jak przy każdej innej liście, a listAll i iterate podążają za nim za ciebie. Jest nieprzejrzysty: odsyłaj to, co dostałeś, i nigdy go nie konstruuj.
Porządkowanie
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_…')Stan przeczytania JEST tutaj etykietą w każdym backendzie, więc podróżuje razem z listami etykiet, a kolejność jest deterministyczna, gdy ustawisz jedno i drugie. Musi być obecne co najmniej jedno z trzech pól.
Załączniki wiadomości
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 jest w base64, a gdy zapisanych bajtów nie udało się odnaleźć — pustym ciągiem, więc sprawdź jego długość przed dekodowaniem. Szyfrogram zaszyfrowanej wiadomości JEST na tej liście i pobiera się jak każdy inny plik; część z wersją PGP/MIME i ewentualny odłączony podpis już nie. Ich id pozostają w encryption.parts i nic ponadto.
Wiadomość, która przyszła zaszyfrowana
Ten SDK ani nie szyfruje, ani nie odszyfrowuje: nie otworzy wiadomości, którą zaszyfrował ktoś inny, i nie wyśle zaszyfrowanej. Żądanie wysyłki jest odrzucane, jeśli niesie znacznik szyfrowania, bo klient bez klucza nie ma prawa go deklarować. Klucze wygenerowane w aplikacji OpenEmail żyją w przeglądarce, która je stworzyła, i nie docierają tutaj nigdzie, a gdy ta przeglądarka otwiera zapieczętowaną wiadomość, tekst jawny zostaje w niej, a zapisana wiadomość, którą czyta to wywołanie, nadal jest szyfrogramem. To, co daje ci threads.get, to rozpoznana koperta. Wiadomość, która przyszła opakowana w PGP lub S/MIME, niesie obiekt encryption, więc puste decodedBody przestaje być jedyną rzeczą, jaką dostajesz — a encryption to jedyne pole MessageResource z prawdziwym typem, bo jako jedynego nie przeżyjesz, zgadując jego brak.
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)}Rozgałęziaj kod na isSealed, nigdy na obecności pola. Dwa z pięciu formatów, pgp-signed i smime-signed, opisują treść, która przyszła OTWARTYM tekstem obok odłączonego podpisu, więc bramkowanie na obecności ukrywa pocztę, której nikt nie musiał ukrywać, a użytkownik nie może jej zobaczyć ani sobie tego wytłumaczyć. isSealed istnieje dokładnie z tego powodu: serwer podaje zbiór formatów zapieczętowanych raz, a trzecia kopia wypisana z unii jest tą, która się rozjedzie.
Brak pola nie oznacza tekstu jawnego. encryption nie ma na żadnej wiadomości zapisanej przed wdrożeniem wykrywania ani na niczym, co trafiło do skrzynki drogą, na której detektor nigdy się nie uruchomił. Pole zapisuje fakt, że nikt nie sprawdzał — fakt o naszym pokryciu, a nie o poczcie — i nic nie uzupełnia go wstecznie.
Czym różnią się od reszty
- Każdy wpis w
ThreadResource.messagesjest typuMessageResource— toRecord<string, unknown>z dokładnie jednym nazwanym polem. Otypowanie reszty byłoby deklarowaniem przez klienta normalizacji, której nikt nie przeprowadza, aencryptioni tak jest nazwane, bo klient, który nie może się na nim rozgałęzić, czyta zapieczętowaną wiadomość jako pustą. - Żądanie, którego nie da się obsłużyć wiernie, kończy się 422
capability_unsupported, a nie odpowiedzią, która wygląda poprawnie i po cichu jest błędna.
Parametry: threads.list (ThreadListOptions)
folderstring- Który folder wylistować. Serwer domyślnie ustawia `inbox`, więc pominięcie tego pola zawęża listę, a nie rozszerza ją na wszystko. Dotyczy to również wyszukiwania przez `query`, chyba że samo zapytanie wskaże folder za pomocą `in:` albo folderowego `is:`, takiego jak `is:sent`.
querystring- Składnia wyszukiwania w skrzynce. Wszystkie zwykłe słowa muszą wystąpić, a każde dopasowuje się luźno: wielkość liter, znaki diakrytyczne i separatory są ignorowane, a fragment dłuższego słowa też się liczy, więc zarówno `min`, jak i `ben jamin` znajdą „Benjamin”. Fraza w cudzysłowie dopasowuje się tak, jak ją zapisano, z pominięciem wielkości liter i znaków diakrytycznych, więc `"ben jamin"` nie znajdzie „Ben-Jamin”, a słowa wypełniające są odrzucane, o ile zostaje coś innego do wyszukania. Zawężaj operatorami takimi jak `from:ada`, `label:Invoices`, `is:unread`, `has:pdf`, `before:2026/01/31` i `older_than:1y` oraz łącz je przez `OR`, nawiasy i wiodący `-`; wartość, której wyszukiwarka nie potrafi użyć, jest ignorowana, zamiast zawężać wynik. Słowa oraz operatory `from:`, `to:`, `cc:`, `subject:` i `body:` czytają nadawcę, odbiorców, temat i pierwsze 4000 znaków treści najnowszej wiadomości z usuniętymi znacznikami, podczas gdy `filename:` i `has:` czytają każdy załącznik całej konwersacji, a etykiety i foldery czytają całą konwersację. Zawęża to ten sam indeks, który czyta lista bez filtrów. Zapieczętowane wiadomości nie przechowują tekstu treści, więc dopasować mogą się tylko ich nadawca, odbiorcy i temat.
labelIdsstring | string[]- Ogranicz listę do wątków niosących te etykiety. Endpoint przyjmuje ciąg rozdzielony przecinkami, a klient sam skleja dla ciebie tablicę w taki ciąg; nie ma limitu, ile ich wymienisz.
limitnumber- Ile wątków zwrócić, od 1 do 100. Przy pominięciu handler używa 25. Wartość domyślna mieszka w handlerze, a nie w schemacie, więc brak wartości i jawne 25 zachowują się tak samo.
cursorstring- `nextCursor` z poprzedniej strony, odesłany dosłownie. To `pageToken` API pod nazwą, której używa każda inna lista, i jest nieprzejrzysty, więc nigdy go nie konstruuj ani nie edytuj.
Odpowiedź: Page<ThreadSummaryResource>
itemsThreadSummaryResource[]- Jeden wpis na wątek w tej stronie, wyjęty z koperty `data` API. Każdy wpis to wyłącznie znacznik obiektu i id. Lista nie niesie tematu, fragmentu treści, uczestników ani etykiet, więc cokolwiek więcej oznacza wywołanie `threads.get` na wątkach, które cię interesują.
items[].idstring- Id wątku, przekazywane bez zmian do `threads.get`, `threads.update` i reszty. To to samo id niezależnie od tego, czy wiersz pochodzi z listy z filtrem, czy z wyszukiwania przez `query`.
hasMoreboolean- Czy istnieje kolejna strona — wyprowadzane z `nextCursor` tam, gdzie API tego nie podaje.
nextCursorstring | null- `nextPageToken` z API, odsyłany jako `cursor` po kolejną stronę, albo null, gdy dalszej strony nie ma. Pusty token jest normalizowany do null, więc sprawdzenie „falsy” i sprawdzenie null dają ten sam wynik.