Luồng thư
Đọc và sắp xếp thư.
Chạy bất kỳ lệnh nào trong 7 lệnh gọi trên trang này với không gian làm việc của bạn, bằng khóa của chính bạn.
Liệt kê
GET /threads?folder=inbox. Truyền query sẽ tìm trên cùng chỉ mục cục bộ đó. Các từ thường phải xuất hiện đủ, và mỗi từ khớp lỏng, bỏ qua hoa thường, dấu và ký tự phân cách, nên min tìm ra "Benjamin". Một cụm trong dấu nháy được khớp đúng như viết, chỉ trừ hoa thường và dấu, nên "ben jamin" không tìm ra "Ben-Jamin". Những từ đệm như the hay emails bị loại khỏi danh sách từ thường khi vẫn còn thứ khác để tìm. Các toán tử như from:, to:, subject:, label:, is:unread, has:pdf, after:2026/01/31 và newer_than:7d thu hẹp kết quả, còn OR, dấu ngoặc đơn và dấu - đứng đầu dùng để kết hợp chúng. Người nhận được lưu thành một danh sách duy nhất không phân vai trò và không bao giờ chứa Bcc, nên cc: đọc cùng trường với to: và bcc: không tự khớp với gì cả. from:me là thư bạn đã gửi, còn to:me là thư có một trong các địa chỉ của chính bạn, kể cả alias, trong danh sách người nhận hoặc làm địa chỉ nhận thư.
Các từ và các toán tử from:, to:, cc:, subject: và body: đọc thư mới nhất trên mỗi luồng: người gửi, người nhận, subject và 4.000 ký tự đầu của phần thân. filename: và has: đọc mọi tệp đính kèm trên toàn bộ cuộc hội thoại, còn label:, in: và is: đọc toàn bộ cuộc hội thoại. folder vẫn áp dụng trừ khi truy vấn nêu một thư mục bằng in:, hoặc bằng một is: là thư mục chẳng hạn is:sent, và in:anywhere tìm trong mọi thư mục, dù đứng một mình hay đi cùng các điều kiện khác. Danh sách bản nháp là ngoại lệ và vẫn giới hạn trong bản nháp bất kể truy vấn nêu gì.
Một giá trị mà tìm kiếm không dùng được sẽ bị bỏ qua thay vì thu hẹp kết quả, nên một lỗi gõ nhầm trong giá trị sẽ làm rộng kết quả chứ không làm nó rỗng: category:, larger:, smaller:, size:, messagesize:, list:, rfc822msgid:, received:, sent:, các từ chỉ danh mục như is:promotions, một từ has: không chỉ loại tệp đính kèm nào, một importance: khác high hoặc low, một ngày không đọc được và một khoảng thời gian có đơn vị không phải h, d, w, m hay y. Một tên toán tử không được nhận ra, chẳng hạn project:, được tìm như văn bản thường. Ngày tháng so với hoạt động mới nhất trên luồng, theo UTC, trong đó after: bao gồm chính ngày được nêu còn before: thì loại trừ; hãy viết ngày dạng YYYY/MM/DD, YYYY-MM-DD, YYYYMMDD, chỉ năm, hoặc epoch tính bằng giây hay mili giây.
nextPageToken là giá trị mờ. Hãy truyền lại đúng thứ bạn nhận được; đừng bao giờ tự dựng hay sửa nó. Cấu trúc của nó không thuộc hợp đồng.
Truy xuất
GET /threads/{id} trả về mọi thư trong luồng, không chỉ thư mới nhất, kèm nhãn của luồng và cho biết trong đó có thư nào chưa đọc hay không.
Thư đến ở dạng đã mã hóa
API này không mã hóa cũng không giải mã. Nó không thể mở một thư do người khác mã hóa, và không thể gửi một thư đã mã hóa. Một request mang dấu hiệu mã hóa bị từ chối với 422, vì chỉ những nơi giữ khóa mới được đặt dấu hiệu đó, và không API client nào giữ khóa. Việc nó làm là NHẬN RA một phong bì niêm phong khi thư đi vào, chỉ dựa trên Content-Type ở cấp cao nhất và không gì khác, rồi ghi nhận điều đó trên thư.
Bản thân OpenEmail nay đã giữ khóa, và cần nói chính xác là nửa nào và ở đâu. Chủ hộp thư tạo một danh tính OpenPGP trong trình duyệt của họ và công bố khóa CÔNG KHAI lên một danh bạ mà những người gửi OpenEmail đã đăng nhập khác có thể tra cứu. Nửa riêng tư được tạo trong trình duyệt đó, không bao giờ được gửi tới đây và không bao giờ khôi phục được, nên không thứ gì trong API này giải mã được bất cứ gì, và không yêu cầu hỗ trợ, trát tòa hay bản sao lưu nào của chúng tôi tạo ra được một khóa có thể làm việc đó. Ứng dụng web nay có thể MỞ một thư PGP/MIME hoặc inline-PGP khi khóa nằm trong trình duyệt của người đọc, nhưng việc giải mã diễn ra trong tab và bản rõ không bao giờ được ghi ngược lại: thư đã lưu vẫn là bản mã, và không phản hồi nào từ API này mang theo nội dung đã mở. Ứng dụng nay có thể niêm phong một thư mới trong trình duyệt và gửi nó: trình soạn thư mã hóa bằng các khóa công khai đã công bố của người nhận và thư đi ra dưới dạng PGP/MIME. API này vẫn không thể niêm phong gì, nên trường dưới đây mô tả cả thư do người khác mã hóa lẫn thư được niêm phong trong một tab OpenEmail.
Điều đó đáng có một trường riêng vì phương án thay thế là gì. Một thư đã niêm phong không lưu phần thân đọc được, nên decodedBody trả về "", giống hệt một thư thực sự không có nội dung. encryption là thứ giúp bạn phân biệt hai trường hợp trước khi xử lý, và nó là một nhận định về phong bì chứ không phải một lần xác minh: thấy thư đã niêm phong không có nghĩa là đã mở được nó.
{ "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'- Loại phong bì đã đến. Đọc từ `Content-Type` ở cấp cao nhất (tham số `protocol` với PGP, `smime-type` với S/MIME) hoặc, với `pgp-inline`, từ một phần thân mở đầu bằng header armor của PGP. Một phần `pkcs7-mime` hoàn toàn không có `smime-type` được đọc là `smime-encrypted`, đúng như RFC 8551 quy định mặc định.
detectedAtstring- ISO 8601, thời điểm bộ phát hiện chạy, tức là thời điểm thư được nạp vào đây. Nó không cho biết thư được mã hóa khi nào, hay bởi ai.
rawRetainedboolean- Các byte RFC822 gốc có được giữ lại để trả về nguyên vẹn thư hay không. Hiện là false trên mọi thư, vì chưa có gì ở đây lưu giữ thư thô. Nó có mặt trong phản hồi ngay từ bây giờ để ngày nó thay đổi không đồng thời là ngày mọi thư đã lưu phải được di chuyển lại lần nữa.
partsobject[]- Các phần phong bì mà định dạng này dùng. Có mặt bất cứ khi nào `encryption` có mặt, và rỗng khi không có phần nào để nêu: `pgp-inline` hoàn toàn không có phần riêng, vì armor của nó CHÍNH LÀ phần thân và đến trong `decodedBody`.
parts[].indexnumber- Đây là phần MIME thứ mấy của thư gốc, được đếm trên các phần đúng như khi chúng đến chứ không phải trên `attachments`. Hai danh sách này khác nhau, và đó là toàn bộ lý do giá trị này được ghi lại.
parts[].attachmentIdstring- Id mà phần này mang trong `attachments`, nếu nó có xuất hiện ở đó: id của thư nối thêm chỉ số của phần. Phần `ciphertext` được liệt kê và tải xuống như mọi tệp khác; `version` và `signature` bị giữ ngoài danh sách, nên id của chúng chỉ dùng để đối chiếu hai góc nhìn và không gì hơn. Endpoint tệp đính kèm sẽ không trả về chúng.
parts[].role'version' | 'ciphertext' | 'signature'- `version` là phần điều khiển của PGP/MIME, `ciphertext` là nội dung thư, `signature` là chữ ký tách rời. Chỉ `ciphertext` là đáng tải; hai phần kia là thành phần giao thức từng hiển thị thành tệp đính kèm rác và nay không còn nữa.
| format | Thứ đã đến | Thân thư |
|---|---|---|
| pgp-mime | Một phong bì PGP/MIME: multipart/encrypted với protocol=application/pgp-encrypted. | Niêm phong |
| pgp-inline | Armor nằm ngay trong phần thân. Chỉ được đọc từ văn bản của phần thân, nên một thư trả lời chỉ trích dẫn lại một khối armor không bị nhầm là thư niêm phong. | Niêm phong |
| smime-encrypted | Một phần S/MIME pkcs7-mime với smime-type=enveloped-data, hoặc một phần hoàn toàn không có smime-type. | Niêm phong |
| pgp-signed | Một chữ ký PGP tách rời đi kèm thư: multipart/signed với protocol=application/pgp-signature. | Đọc được |
| smime-signed | Một chữ ký S/MIME tách rời: protocol pkcs7-signature, hoặc smime-type=signed-data. | Đọc được |
Đã ký không có nghĩa là đã niêm phong, và rẽ nhánh theo sự có mặt của encryption thay vì theo format là hiểu ngược hoàn toàn. Chữ ký là một khẳng định về người đã viết thư, không phải lớp bọc quanh nó: phần thân của thư đã ký ở dạng rõ và đọc được như mọi thư khác. Hãy coi pgp-mime, pgp-inline và smime-encrypted là không đọc được, còn hai định dạng đã ký là thư thường.
Điều gì thay đổi trên một thư niêm phong
Chỉ ba định dạng niêm phong mới làm thay đổi điều gì đó, và thay đổi xảy ra lúc nạp thư chứ không phải trong phản hồi này. Mọi thứ lẽ ra sẽ đọc phần thân đều đứng ngoài, thay vì đọc bản mã rồi báo một kết quả mà nó không thể có được:
- Tìm kiếm trong phần thân. Thư được đánh chỉ mục với đoạn trích phần thân rỗng, nên vẫn tìm được theo người gửi, subject, địa chỉ và nhãn, nhưng không theo bất cứ thứ gì bên trong.
- Lượt quét phần thân của bộ chấm điểm lừa đảo. Kết luận vẫn có và nêu rõ điều nó không làm được:
risk.signalschứabody-encryptedvàrisk.aiCheckedlà false. - Bước kiểm tra thư do AI viết, vốn từ chối thay vì đoán:
aiWritten.levellàunknownvàaiWritten.skippedlàencrypted. - Các điều kiện về phần thân trong quy tắc. Điều kiện về phong bì và header vẫn chạy như trước; một quy tắc hỏi về phần thân được ghi nhận là chưa đánh giá chứ không tính là không khớp, vì "không khớp" và "không đọc được" là hai câu trả lời khác nhau.
- Nhập lời mời lịch. Lời mời nằm bên trong bản mã, và dựng một sự kiện từ phong bì sẽ đặt một mục sai lên một lịch thật.
- Tóm tắt luồng và embedding, cho toàn bộ luồng. Chỉ cần một thư trả lời niêm phong là đủ. Bản tóm tắt là cách mô hình đọc bản rõ rồi lưu lại dưới dạng metadata không mã hóa, và đó là chỗ duy nhất trong quy trình này mà phần thân có thể rò rỉ vào một kho mà không ai nghĩ là nơi chứa phần thân.
Mọi thứ không cần tới phần thân đều không bị ảnh hưởng:
- DMARC, DKIM và SPF. Chúng được đọc từ
Authentication-Results, thứ mà bản mã không che giấu, nên một thư đã mã hóa vẫn nhận được kết luận xác thực thật thay vì không có gì. - Gom luồng, lọc thư rác và danh sách chặn: tất cả đều làm việc trên phong bì và header.
- Tệp đính kèm. Phần ciphertext vẫn nằm trong
attachments, được đặt tênencrypted-message.asckhi nó đến mà không có tên, và tải xuống qua endpoint bên dưới. Đó chính là thứ mà trình đọc của ứng dụng web tải về rồi giải mã trong trình duyệt; với một API client, vốn không giữ khóa nào, lượt tải đó vẫn là cách duy nhất để đọc thư. Hãy mở nó trong một client có khóa. - Một thư đã ký không mất gì trong số này. Mọi bước kiểm tra ở trên vẫn chạy trên nó và không có gì bị giữ lại, đó là lý do danh sách niêm phong gồm ba định dạng chứ không phải năm.
Việc thiếu encryption không phải là khẳng định rằng thư ở dạng rõ. Nó nghĩa là chưa ai kiểm tra: thư có từ trước khi có bộ phát hiện, hoặc vào hộp thư theo một con đường không chạy bộ phát hiện. Không có gì bổ sung ngược trường này, nên một trường nói "chúng tôi chưa kiểm tra" không bao giờ được hiểu thành "chúng tôi đã kiểm tra và không thấy gì".
Đánh dấu và gắn nhãn
PATCH /threads/{id} nhận read, addLabelIds và removeLabelIds. Trạng thái đã đọc là một nhãn trên mọi backend mà sản phẩm này hỗ trợ, nên đặt read và chuyển nhãn trong cùng một lệnh gọi giữ cho thứ tự xử lý xác định.
{ "read": true, "addLabelIds": ["USER_INVOICES"] }TRASH và SNOOZED bị từ chối ở đây với label_not_directly_settable. Không trạng thái nào trong hai cái này được thể hiện chỉ bằng nhãn của nó (chuyển vào thùng rác còn xóa các nhãn thư mục, và tạm ẩn cần một thời điểm đánh thức lưu kèm), nên đặt chúng thủ công sẽ để một luồng ở trạng thái mà ứng dụng không bao giờ tạo ra và không thể khôi phục. Hãy dùng các endpoint bên dưới.
Thùng rác và tạm ẩn
| Endpoint | Tác dụng |
|---|---|
| POST /threads/{id}/trash | Chuyển vào Bin, đồng thời xóa INBOX, SPAM, SNOOZED và ARCHIVE. |
| POST /threads/{id}/snooze | Body { "wakeAt": "…" }. Ẩn luồng và hẹn giờ đưa nó trở lại. |
| POST /threads/{id}/unsnooze | Đưa nó trở lại ngay và hủy lần trở lại đã hẹn. |
Tạm ẩn ghi hai thứ: nhãn làm ẩn luồng, và mục hẹn đưa nó trở lại. Làm cái này mà không làm cái kia chính là lý do đây là các endpoint chứ không phải thao tác chỉnh nhãn.
Tệp đính kèm
GET /threads/{id}/messages/{messageId}/attachments trả về từng tệp đính kèm với filename, contentType, size và content dạng base64. content là chuỗi rỗng khi không tìm thấy byte đã lưu, nên hãy kiểm tra độ dài của nó trước khi giải mã.
Một phong bì đã mã hóa không có đầy đủ ở đây. Phần ciphertext thì có (nó chính là nội dung thư, và tải nó xuống là cách duy nhất để một API client đọc loại thư này), nhưng phần version của PGP/MIME và mọi chữ ký tách rời đều bị giữ ngoài danh sách, vì chúng hiển thị thành tệp đính kèm rác và caller không làm được gì với chúng. Cả hai vẫn giữ id trong encryption.parts, dùng để đối chiếu hai góc nhìn; endpoint này không trả về chúng.