Luồng thư
`threads.list`, `listAll`, `iterate`, `get`, `update`, `trash`, `snooze`, `unsnooze` và `listAttachments`.
Đọc
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 phân trang các luồng thư bằng pageToken. Client trao nó cho bạn dưới dạng nextCursor và nhận lại dưới dạng cursor, giống như mọi danh sách khác, còn listAll và iterate tự động lần theo nó cho bạn. Giá trị này là mờ (opaque): hãy gửi lại đúng những gì bạn nhận được và đừng bao giờ tự tạo.
Sắp xếp
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_…')Trạng thái đã đọc CHÍNH LÀ một nhãn trên mọi backend ở đây, nên nó đi cùng các danh sách nhãn và thứ tự áp dụng là xác định khi bạn đặt cả hai. Phải có ít nhất một trong ba trường.
Tệp đính kèm của thư
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 là base64, và là chuỗi rỗng khi không tìm thấy các byte đã lưu, vì vậy hãy kiểm tra độ dài trước khi giải mã. Bản mã (ciphertext) của một thư được mã hóa CÓ nằm trong danh sách này và được tải xuống như mọi tệp khác; phần phiên bản PGP/MIME và mọi chữ ký tách rời thì không. Chúng chỉ giữ id của mình trong encryption.parts và không có gì hơn.
Thư đến ở dạng mã hóa
SDK 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ư được mã hóa. Yêu cầu gửi sẽ bị từ chối nếu mang dấu hiệu mã hóa, vì một client không có khóa thì không có lý do gì để khẳng định điều đó. Các khóa được tạo trong ứng dụng OpenEmail nằm trong trình duyệt đã tạo ra chúng và không đến được bất kỳ đâu ở đây; khi trình duyệt đó mở một thư đã niêm phong, bản rõ vẫn ở lại trong trình duyệt, và thư được lưu trữ mà lệnh gọi này đọc vẫn là bản mã. Những gì threads.get trả về cho bạn là phong bì, đã được nhận diện. Một thư đến dưới dạng bọc PGP hoặc S/MIME sẽ mang một đối tượng encryption, nên một decodedBody rỗng không còn là thứ duy nhất bạn nhận được, và encryption là trường duy nhất trên MessageResource có kiểu thực sự, vì đó là trường mà bạn không thể đoán mò về sự vắng mặt của nó mà vẫn an toàn.
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)}Hãy rẽ nhánh bằng isSealed, đừng bao giờ dựa vào việc trường có tồn tại hay không. Hai trong năm định dạng, pgp-signed và smime-signed, mô tả một nội dung đến ở dạng RÕ kèm theo chữ ký tách rời, nên việc kiểm tra theo sự tồn tại sẽ ẩn đi những thư không cần phải ẩn, và người dùng không thể nhìn thấy cũng như giải thích được. isSealed được cung cấp chính vì lý do đó: máy chủ khai báo tập hợp các định dạng niêm phong một lần, và một bản sao thứ ba được viết lại từ union chính là bản sao sẽ bị lệch.
Vắng mặt không có nghĩa là bản rõ. encryption không có trên mọi thư được lưu trước khi tính năng phát hiện ra mắt, và trên bất cứ thứ gì đến hộp thư qua một đường đi mà bộ phát hiện chưa từng chạy. Nó ghi nhận rằng chưa ai kiểm tra, một sự thật về phạm vi bao phủ của chúng tôi chứ không phải về thư, và không có gì điền bù (backfill) cho nó.
Những điểm khác biệt so với phần còn lại
- Mỗi mục trong
ThreadResource.messageslà mộtMessageResource, tức mộtRecord<string, unknown>với đúng một trường được đặt tên. Gán kiểu cho phần còn lại sẽ là việc client khẳng định một sự chuẩn hóa mà không ai thực hiện, cònencryptionvẫn được đặt tên vì một client không thể rẽ nhánh theo nó sẽ đọc một thư đã niêm phong như một thư rỗng. - Một yêu cầu không thể được phục vụ chính xác sẽ trả về 422
capability_unsupported, chứ không phải một phản hồi trông có vẻ đúng nhưng âm thầm sai.
Tham số: threads.list (ThreadListOptions)
folderstring- Thư mục cần liệt kê. Máy chủ mặc định là `inbox`, nên việc bỏ qua tham số này sẽ thu hẹp danh sách chứ không mở rộng ra mọi thứ. Nó cũng áp dụng cho tìm kiếm bằng `query`, trừ khi truy vấn tự chỉ định thư mục bằng `in:` hoặc một `is:` chỉ thư mục như `is:sent`.
querystring- Cú pháp tìm kiếm hộp thư. Mọi từ thông thường đều phải xuất hiện, và mỗi từ được khớp một cách linh hoạt: chữ hoa/thường, dấu và ký tự phân cách được bỏ qua, và một phần của từ dài hơn cũng được tính, nên cả `min` và `ben jamin` đều tìm thấy "Benjamin". Một cụm từ trong dấu ngoặc kép được khớp đúng như đã viết, ngoại trừ chữ hoa/thường và dấu, nên `"ben jamin"` không tìm thấy "Ben-Jamin", và các từ đệm sẽ bị loại bỏ khi vẫn còn nội dung khác để tìm. Thu hẹp kết quả bằng các toán tử như `from:ada`, `label:Invoices`, `is:unread`, `has:pdf`, `before:2026/01/31` và `older_than:1y`, rồi kết hợp chúng bằng `OR`, dấu ngoặc đơn và dấu `-` ở đầu; 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ả. Các từ và các toán tử `from:`, `to:`, `cc:`, `subject:` và `body:` đọc người gửi, người nhận, tiêu đề và 4.000 ký tự đầu tiên của nội dung (đã loại bỏ markup) của thư mới nhất, trong khi `filename:` và `has:` đọc mọi tệp đính kèm trên toàn bộ cuộc hội thoại, còn nhãn và thư mục đọc toàn bộ cuộc hội thoại. Nó thu hẹp trên cùng chỉ mục mà danh sách không lọc đọc. Thư đã niêm phong không lưu văn bản nội dung, nên chỉ người gửi, người nhận và tiêu đề của chúng có thể khớp.
labelIdsstring | string[]- Giới hạn danh sách ở các luồng thư mang những nhãn này. Endpoint nhận một chuỗi phân tách bằng dấu phẩy và client sẽ nối mảng thành chuỗi cho bạn; không giới hạn số lượng nhãn bạn chỉ định.
limitnumber- Số luồng thư cần trả về, từ 1 đến 100. Nếu bỏ qua, handler dùng 25. Giá trị mặc định nằm trong handler chứ không phải trong schema, nên việc không truyền giá trị và truyền rõ 25 có hành vi như nhau.
cursorstring- `nextCursor` của trang trước, được gửi lại nguyên văn. Đây là `pageToken` của API dưới cái tên mà mọi danh sách khác sử dụng, và nó là giá trị mờ (opaque), nên đừng bao giờ tự tạo hay chỉnh sửa.
Phản hồi: Page<ThreadSummaryResource>
itemsThreadSummaryResource[]- Mỗi mục ứng với một luồng thư trong trang này, được lấy ra từ phong bì `data` của API. Mỗi mục chỉ gồm một dấu đánh dấu đối tượng và một id. Danh sách không mang tiêu đề, đoạn trích, người tham gia hay nhãn, nên muốn thêm thông tin thì phải gọi `threads.get` cho các luồng thư bạn cần.
items[].idstring- Id của luồng thư, để truyền nguyên vẹn cho `threads.get`, `threads.update` và các phương thức còn lại. Id này giống nhau dù hàng đến từ danh sách có lọc hay từ tìm kiếm `query`.
hasMoreboolean- Có trang tiếp theo hay không, được suy ra từ `nextCursor` khi API không nêu rõ.
nextCursorstring | null- `nextPageToken` của API, để gửi lại dưới dạng `cursor` cho trang tiếp theo, hoặc null khi không còn trang nào. Token rỗng được chuẩn hóa thành null, nên kiểm tra falsy và kiểm tra null cho cùng kết quả.