Zur Dokumentation springen
SDK

Threads

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

Lesen

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)

Die API paginiert Threads mit einem pageToken. Der Client reicht ihn als nextCursor heraus und nimmt ihn als cursor wieder entgegen, wie bei jeder anderen Liste, und listAll sowie iterate folgen ihm automatisch. Er ist opak: Zurückgegeben wird genau der erhaltene Wert; ein Token darf nie selbst gebaut werden.

Organisieren

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_…')

Der Gelesen-Status IST hier auf jedem Backend ein Label, reist also mit den Label-Listen mit, und die Reihenfolge ist deterministisch, wenn beides gesetzt wird. Mindestens eines der drei Felder muss vorhanden sein.

Anhänge an einer Nachricht

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 ist base64 und ein leerer String, wenn die gespeicherten Bytes nicht gefunden werden konnten; vor dem Dekodieren sollte daher die Länge geprüft werden. Der Ciphertext einer verschlüsselten Nachricht STEHT in dieser Liste und wird wie jede andere Datei heruntergeladen; der Versions-Part von PGP/MIME und eine etwaige abgetrennte Signatur dagegen nicht. Sie behalten ihre ids in encryption.parts und sonst nichts.

Eine verschlüsselt eingegangene Nachricht

Dieses SDK verschlüsselt und entschlüsselt nicht: Es kann keine Nachricht öffnen, die jemand anderes verschlüsselt hat, und keine verschlüsselte senden. Die Sendeanfrage wird abgelehnt, wenn sie einen Verschlüsselungsmarker mitführt, denn ein Client ohne Schlüssel hat nichts zu behaupten. Schlüssel, die in der OpenEmail-App erzeugt wurden, leben in dem Browser, der sie erzeugt hat, und erreichen hier nichts; öffnet dieser Browser eine versiegelte Nachricht, bleibt der Klartext in ihm, und die gespeicherte Nachricht, die dieser Aufruf liest, ist weiterhin Ciphertext. Was threads.get liefert, ist der Umschlag – erkannt. Eine Nachricht, die PGP- oder S/MIME-verpackt eingegangen ist, führt ein encryption-Objekt mit; ein leerer decodedBody ist damit nicht mehr das Einzige, was man in die Hand bekommt. encryption ist das einzige Feld auf MessageResource mit einem echten Typ, weil es das eine ist, dessen Fehlen man durch Raten nicht übersteht.

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)}

Verzweigt wird über isSealed, nie über das Vorhandensein des Feldes. Zwei der fünf Formate, pgp-signed und smime-signed, beschreiben einen Body, der IM KLARTEXT neben einer abgetrennten Signatur eingegangen ist; eine Prüfung auf bloßes Vorhandensein verbirgt also Mail, die niemand verbergen musste, und die Nutzerin oder der Nutzer kann sie weder sehen noch erklären. Genau dafür gibt es isSealed: Der Server nennt die versiegelte Menge einmal, und eine dritte, aus der Union abgeschriebene Kopie ist die, die auseinanderdriftet.

Fehlen bedeutet nicht Klartext. encryption fehlt bei jeder Nachricht, die vor dem Ausliefern der Erkennung gespeichert wurde, und bei allem, was das Postfach über einen Weg erreicht hat, auf dem der Detektor nie lief. Es hält fest, dass niemand nachgesehen hat – eine Tatsache über unsere Abdeckung und nicht über die Mail –, und nichts trägt es nachträglich nach.

Worin sich diese vom Rest unterscheiden

  • Jeder Eintrag in ThreadResource.messages ist ein MessageResource, ein Record<string, unknown> mit genau einem benannten Feld darauf. Den Rest zu typisieren hieße, dass der Client eine Normalisierung behauptet, die niemand durchführt; encryption ist dennoch benannt, weil ein Client, der nicht darüber verzweigen kann, eine versiegelte Nachricht als leere liest.
  • Eine Anfrage, die nicht getreu bedient werden kann, ergibt ein 422 capability_unsupported und keine Antwort, die richtig aussieht und stillschweigend falsch ist.

Parameter: threads.list (ThreadListOptions)

folderstring
Welcher Ordner aufgelistet wird. Der Server setzt standardmäßig `inbox`; ein Weglassen schränkt die Auflistung also ein, statt sie auf alles auszuweiten. Der Wert gilt auch für eine `query`-Suche, sofern die Query nicht selbst einen Ordner mit `in:` oder ein Ordner-`is:` wie `is:sent` benennt.
querystring
Die Suchsyntax des Postfachs. Einfache Wörter müssen alle vorkommen und passen jeweils unscharf: Groß-/Kleinschreibung, Akzente und Trennzeichen werden ignoriert, und ein Teil eines längeren Wortes zählt mit, sodass `min` und `ben jamin` beide "Benjamin" finden. Eine Phrase in Anführungszeichen wird bis auf Groß-/Kleinschreibung und Akzente wörtlich gesucht, `"ben jamin"` findet also "Ben-Jamin" nicht, und Füllwörter werden verworfen, wenn sonst noch etwas zum Suchen bleibt. Eingegrenzt wird mit Operatoren wie `from:ada`, `label:Invoices`, `is:unread`, `has:pdf`, `before:2026/01/31` und `older_than:1y`, kombinierbar mit `OR`, Klammern und einem vorangestellten `-`; ein Wert, den die Suche nicht verwenden kann, wird ignoriert und schränkt nicht ein. Wörter sowie die Operatoren `from:`, `to:`, `cc:`, `subject:` und `body:` lesen Absender, Empfänger und Betreff der neuesten Nachricht sowie die ersten 4.000 Zeichen ihres Bodys ohne Markup, während `filename:` und `has:` jeden Anhang der gesamten Konversation lesen und Labels und Ordner die gesamte Konversation. Eingegrenzt wird derselbe Index, den die ungefilterte Auflistung liest. Versiegelte Nachrichten speichern keinen Body-Text, daher können nur ihr Absender, ihre Empfänger und ihr Betreff treffen.
labelIdsstring | string[]
Schränkt die Auflistung auf Threads mit diesen Labels ein. Der Endpunkt nimmt einen kommagetrennten String entgegen, und der Client fügt ein Array automatisch zu einem solchen zusammen; die Anzahl der genannten Labels ist unbegrenzt.
limitnumber
Wie viele Threads zurückgegeben werden, von 1 bis 100. Ohne Angabe verwendet der Handler 25. Der Standardwert liegt im Handler und nicht im Schema, daher verhalten sich ein fehlender Wert und eine ausdrückliche 25 gleich.
cursorstring
Der `nextCursor` der vorherigen Seite, unverändert zurückgegeben. Es ist das `pageToken` der API unter dem Namen, den jede andere Liste verwendet, und es ist opak; ein Token darf daher nie konstruiert oder bearbeitet werden.

Antwort: Page<ThreadSummaryResource>

itemsThreadSummaryResource[]
Ein Eintrag pro Thread auf dieser Seite, aus dem `data`-Umschlag der API herausgehoben. Jeder Eintrag besteht nur aus einem Objektmarker und einer id. Die Auflistung führt weder Betreff noch Snippet, Teilnehmer oder Labels mit; für mehr ist `threads.get` auf den gewünschten Threads aufzurufen.
items[].idstring
Die id des Threads, unverändert an `threads.get`, `threads.update` und die übrigen Aufrufe weiterzureichen. Es ist dieselbe id, ob die Zeile aus einer gefilterten Auflistung oder aus einer `query`-Suche stammt.
hasMoreboolean
Ob es eine weitere Seite gibt, abgeleitet aus `nextCursor`, wo die API es nicht angibt.
nextCursorstring | null
Das `nextPageToken` der API, als `cursor` für die folgende Seite zurückzusenden, oder null, wenn es keine weitere Seite gibt. Ein leeres Token wird zu null normalisiert, sodass eine Falsy-Prüfung und eine Null-Prüfung übereinstimmen.