スレッド
メールを読み、整理します。
このページの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、括弧、先頭の - で組み合わせます。受信者は役割を持たない 1 つのリストとして保存され、Bcc は決して保持されないので、cc: は to: と同じフィールドを読み、bcc: は独自には何にも一致しません。from:me は自分が送ったメール、to:me はエイリアスを含む自分のアドレスのいずれかを受信者として、または配送先アドレスとして持つメールです。
語および from:、to:、cc:、subject:、body: 演算子は、各スレッドの最新メッセージ、つまりその送信者、受信者、件名、本文の先頭 4,000 文字を読みます。filename: と has: は会話全体のすべての添付ファイルを読み、label:、in:、is: は会話全体を読みます。folder は、クエリが in: でフォルダーを指定するか、is:sent のようにフォルダーを意味する is: を使わない限り適用され続けます。in:anywhere は単独でも他の条件と並べても、すべてのフォルダーを検索します。下書きの一覧だけは例外で、クエリが何を指定しても下書きにとどまります。
検索が使えない値は絞り込みに使われず無視されるので、値のタイプミスは結果を空にするのではなく広げます。対象は category:、larger:、smaller:、size:、messagesize:、list:、rfc822msgid:、received:、sent:、is:promotions のようなカテゴリー語、添付の種類を指さない has: の語、high でも low でもない importance:、読み取れない日付、単位が h、d、w、m、y のいずれでもない期間です。知らない演算子名、たとえば project: は、通常のテキストとして検索されます。日付はスレッドの最新の活動を UTC で読み、after: は指定した日を含み、before: は含みません。書式は YYYY/MM/DD、YYYY-MM-DD、YYYYMMDD、年のみ、エポック秒またはミリ秒です。
nextPageToken は不透明です。渡されたものをそのまま返してください。決して組み立てたり編集したりしないでください。その形は契約の一部ではありません。
単体の取得
GET /threads/{id} は、最新の 1 通だけでなくスレッド内のすべてのメッセージを、ラベルと未読が含まれているかどうかとともに返します。
暗号化された状態で届いたメッセージ
この API は暗号化も復号もしません。他人が暗号化したメッセージを開くことはできず、暗号化したメッセージを送ることもできません。暗号化マーカーを持つリクエストは 422 で拒否されます。それを設定してよいのは鍵を持つ面だけであり、API クライアントは鍵を持たないからです。この API が行うのは、受信時に最上位の Content-Type だけを見て封印されたエンベロープを認識し、それをメッセージ上で明示することです。
OpenEmail 自身も今は鍵を持っており、どちらの半分をどこに持つのかは正確に述べる価値があります。メールボックスの所有者はブラウザー内で OpenPGP の識別情報を生成し、公開鍵のほうを、サインイン済みの他の OpenEmail 送信者が参照できるディレクトリーに公開します。秘密鍵のほうはそのブラウザー内で作られ、ここへ送られることはなく、復元もできません。したがってこの API の中に何かを復号できるものはなく、サポート依頼も、召喚状も、当社のバックアップも、鍵を生み出すことはありません。Web アプリは、読み手のブラウザーに鍵があれば PGP/MIME やインライン PGP のメッセージを開けるようになりましたが、その復号はタブの中で起こり、平文が書き戻されることはありません。保存されたメッセージは暗号文のままで、この API のレスポンスが開かれたテキストを運ぶことは決してありません。アプリは新しいメッセージをブラウザー内で封印して送ることもできるようになりました。コンポーザーが受信者の公開鍵に対して暗号化し、メールは PGP/MIME として出ていきます。この API は依然として何も封印できないので、以下のフィールドは、他人が暗号化したメールと OpenEmail のタブ内で封印されたメールの両方を表します。
これがフィールドに値するのは、代わりの結末を考えれば分かります。封印されたメッセージは読める本文を保存しないので、decodedBody は "" として返り、これは本当に中身がなかったメッセージとまったく同じバイト列です。encryption は、行動を起こす前にその 2 つを見分けるためのものであり、検証ではなくエンベロープについての記述です。メッセージが封印されていると分かることは、それを開いたことと同じではありません。
{ "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`(PGP ならその `protocol` パラメーター、S/MIME ならその `smime-type`)から、`pgp-inline` の場合は PGP の armor ヘッダーで始まる本文から読み取ります。`smime-type` をまったく持たない `pkcs7-mime` パートは `smime-encrypted` として読まれます。RFC 8551 が既定でそう定めているからです。
detectedAtstring- ISO 8601 で、検出器が動いた時刻、つまりメッセージがここに取り込まれた時刻です。メッセージがいつ、誰によって暗号化されたかについては何も語りません。
rawRetainedboolean- 元の RFC822 バイト列が保持されたかどうか。保持されていればメッセージをそのまま返すことができます。現状ではすべてのメッセージで false です。ここでは生のメールをまだ保持していないからです。今この形でレスポンスに入れてあるのは、これが変わる日が、保存済みの全メッセージを再び移行しなければならない日と重ならないようにするためです。
partsobject[]- この形式が使うエンベロープのパート。`encryption` があるときは常に存在し、挙げるものがないときは空です。`pgp-inline` には独立したパートがまったくありません。その armor 自体が本文であり、`decodedBody` に届くからです。
parts[].indexnumber- 元のメッセージのどの MIME パートだったか。`attachments` ではなく、届いたときのパートの並びで数えます。2 つのリストは一致せず、それこそがこれを記録している理由です。
parts[].attachmentIdstring- このパートが `attachments` の中で持つ id(そこに現れる場合)。メッセージ id にパートのインデックスを付けたものです。`ciphertext` のパートは一覧に載り、他のファイルと同様にダウンロードできます。`version` と `signature` は一覧から外されるので、その id は 2 つのビューを対応づける以上の意味を持ちません。添付エンドポイントはそれらを返しません。
parts[].role'version' | 'ciphertext' | 'signature'- `version` は PGP/MIME の制御パート、`ciphertext` はメッセージ本体、`signature` は分離署名です。取得する価値があるのは `ciphertext` だけで、残りの 2 つはかつてゴミの添付ファイルとして表示され、今はされなくなったプロトコル上の付属物です。
| format | 届いたもの | 本文 |
|---|---|---|
| pgp-mime | PGP/MIME のエンベロープ。protocol=application/pgp-encrypted を伴う multipart/encrypted です。 | 封印済み |
| pgp-inline | 本文そのものの中にある armor。必ず本文テキストからのみ読み取るので、armor ブロックを引用しただけの返信が誤認されることはありません。 | 封印済み |
| smime-encrypted | smime-type=enveloped-data を伴う S/MIME の pkcs7-mime パート、または smime-type をまったく持たないもの。 | 封印済み |
| pgp-signed | メッセージの横にある分離された PGP 署名。protocol=application/pgp-signature を伴う multipart/signed です。 | 読み取り可能 |
| smime-signed | 分離された S/MIME 署名。pkcs7-signature プロトコル、または smime-type=signed-data です。 | 読み取り可能 |
署名済みは封印済みではありません。format ではなく encryption の有無で分岐すると、これを正反対に取り違えます。署名は誰がメッセージを書いたかについての主張であって、メッセージを包むものではありません。署名済みメッセージの本文は平文であり、他のメールと同じように読めます。pgp-mime、pgp-inline、smime-encrypted を読み取り不能として扱い、2 つの署名形式は通常のメールとして扱ってください。
封印されたメッセージで変わること
変化が生じるのは封印された 3 つの形式だけで、しかもその変化はこのレスポンスではなく取り込み時に起こります。本文を読むはずだったものはすべて手を引きます。暗号文を読んで、得られるはずのない結果を報告するのではなく、次のようにします。
- 本文の検索。メッセージは本文スニペットが空の状態でインデックスされるので、送信者、件名、アドレス、ラベルでは見つかりますが、中身では見つかりません。
- フィッシング判定の本文パス。判定自体は行われ、何ができなかったかを述べます。
risk.signalsにbody-encryptedが入り、risk.aiCheckedは false になります。 - AI による執筆判定は、推測せずに棄権します。
aiWritten.levelはunknown、aiWritten.skippedはencryptedになります。 - ルールの本文条件。エンベロープとヘッダーの条件はこれまでどおり動きます。本文を問うルールは、不一致として数えられるのではなく未評価として記録されます。「一致しなかった」と「読めなかった」は別の答えだからです。
- カレンダー招待の取り込み。招待は暗号文の中にあり、エンベロープから予定を組み立てれば、実在のカレンダーに誤った項目を入れることになります。
- スレッドの要約と埋め込みは、スレッド全体で停止します。封印された返信が 1 通あれば十分です。要約とは、モデルが平文を読んだ結果を平文のメタデータとして保存したものであり、このパイプラインの中で、本文が本文とは思われていない保存先へ漏れ出しうる唯一の場所です。
本文を必要としないものは何も変わりません。
- DMARC、DKIM、SPF。これらは
Authentication-Resultsから読み取られ、暗号文はそれを隠しません。したがって暗号化されたメッセージも、判定なしではなく本物の認証判定を得ます。 - スレッド化、迷惑メールの振り分け、ブロックリスト。いずれもエンベロープとヘッダーの処理です。
- 添付ファイル。暗号文のパートは
attachmentsに残り、名前がない場合はencrypted-message.ascと名付けられ、下記のエンドポイントからダウンロードできます。これはまさに Web アプリ自身のリーダーが取得してブラウザー内で復号するものであり、鍵を持たない API クライアントにとっては、このダウンロードがそのメールを読む唯一の道です。鍵を持つクライアントで開いてください。 - 署名済みのメッセージは、これらを何も失いません。上記のチェックはすべて実行され続け、何も差し控えられません。だからこそ封印されるのは 5 つではなく 3 つの形式なのです。
encryption がないことは、平文であるという主張ではありません。それは誰も見ていないという意味です。メッセージが検出機能より前のものであるか、検出器が動かない経路でメールボックスに届いたかです。後から埋めるものは何もないので、「確認していない」と言っているフィールドを「確認して暗号化はなかった」と読んではいけません。
既読とラベル付け
PATCH /threads/{id} は read、addLabelIds、removeLabelIds を受け取ります。この製品が対応するすべてのバックエンドで既読状態はラベルなので、read の設定とラベルの移動を 1 回の呼び出しで行うことで順序が決定的になります。
{ "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 | 今すぐ戻し、予約された復帰を取り消します。 |
スヌーズは 2 つのものを書き込みます。スレッドを隠すラベルと、スレッドを戻すエントリーです。片方だけを行ってしまうことこそ、これらがラベル編集ではなくエンドポイントになっている理由です。
添付ファイル
GET /threads/{id}/messages/{messageId}/attachments は、各添付ファイルを filename、contentType、size、そして base64 の content とともに返します。保存されたバイト列が見つからなかった場合 content は空文字列になるので、デコードする前に長さを確認してください。
暗号化されたエンベロープのすべてがここにあるわけではありません。暗号文はあります(それがメッセージ本体であり、API クライアントがこのメールを読む唯一の方法です)が、PGP/MIME の version パートと分離署名は一覧から外されます。ゴミの添付ファイルとして表示されていたうえ、呼び出し側にできることが何もないからです。どちらも encryption.parts の中に id を保持しており、それが 2 つのビューを対応づけます。このエンドポイントはそれらを返しません。