رشتهها
`threads.list`، `listAll`، `iterate`، `get`، `update`، `trash`، `snooze`، `unsnooze` و `listAttachments`.
خواندن
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 رشتهها را با یک pageToken صفحهبندی میکند. کلاینت آن را بهصورت nextCursor به شما میدهد و بهصورت cursor پس میگیرد، مثل هر فهرست دیگری، و listAll و iterate آن را برایتان دنبال میکنند. این مقدار مات است: همان چیزی را که گرفتهاید بازبفرستید و هرگز خودتان یکی نسازید.
سازماندهی
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_…')وضعیت خواندهشدن اینجا روی هر backend یک برچسب است، پس همراه فهرستهای برچسب سفر میکند و وقتی هر دو را تنظیم کنید ترتیب قطعی است. دستکم یکی از آن سه فیلد باید حاضر باشد.
پیوستهای یک پیام
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 بهصورت base64 است، و وقتی بایتهای ذخیرهشده پیدا نشوند یک رشتهٔ خالی، پس پیش از decode کردن طولش را بررسی کنید. متن رمزِ یک پیام رمزنگاریشده در این فهرست هست و مثل هر فایل دیگری دانلود میشود؛ بخش نسخهٔ PGP/MIME و هر امضای جداگانه نیستند. آنها تنها شناسههایشان را در encryption.parts نگه میدارند و بس.
پیامی که رمزنگاریشده رسیده است
این SDK نه رمزنگاری میکند و نه رمزگشایی: نمیتواند پیامی را که کسی دیگر رمزنگاری کرده باز کند، و نمیتواند پیامی رمزنگاریشده بفرستد. اگر درخواست ارسال نشانگر رمزنگاری با خود داشته باشد رد میشود، چون کلاینتی که کلید ندارد حقی هم برای ادعای آن ندارد. کلیدهایی که در اپ OpenEmail ساخته میشوند در همان مرورگری که ساختهشان زندگی میکنند و به اینجا نمیرسند، و وقتی آن مرورگر پیامی مهرومومشده را باز میکند متن آشکار در همانجا میماند و پیام ذخیرهشدهای که این فراخوانی میخواند همچنان متن رمز است. آنچه threads.get به شما میدهد پاکت است، بازشناختهشده. پیامی که بهصورت پیچیده در PGP یا S/MIME رسیده باشد یک object با نام encryption دارد، تا یک decodedBody خالی دیگر تنها چیزی نباشد که به شما داده میشود؛ و encryption تنها فیلد روی MessageResource است که تایپی واقعی دارد، چون تنها فیلدی است که حدس زدن نبودش را نمیتوان تاب آورد.
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)}با isSealed شاخه بزنید، هرگز با حضور خودِ فیلد. دو تا از پنج قالب، pgp-signed و smime-signed، بدنهای را توصیف میکنند که بهصورت آشکار همراه یک امضای جداگانه رسیده است، پس شرط گذاشتن روی حضور، ایمیلی را پنهان میکند که هیچکس نیاز به پنهان کردنش نداشته، و کاربر نه میتواند ببیندش نه توضیحش دهد. isSealed دقیقاً به همین دلیل عرضه میشود: سرور مجموعهٔ مهرومومشده را یک بار بیان میکند، و نسخهٔ سومی که از روی union نوشته شود همان نسخهای است که واگرا میشود.
نبودن به معنای متن آشکار نیست. encryption روی هر پیامی که پیش از عرضهٔ تشخیص ذخیره شده باشد غایب است، و روی هر چیزی که از مسیری به صندوق پستی رسیده باشد که تشخیصدهنده هرگز روی آن اجرا نشده است. این فیلد ثبت میکند که کسی نگاه نکرده است، یعنی واقعیتی دربارهٔ پوشش ما نه دربارهٔ خودِ ایمیل، و هیچچیز آن را بهصورت عقبگرد پر نمیکند.
تفاوتهای این بخش با بقیه
- هر مدخل در
ThreadResource.messagesیکMessageResourceاست، یعنی یکRecord<string, unknown>که دقیقاً یک فیلد نامدار روی آن هست. تایپ کردن بقیه یعنی کلاینت نرمالسازیای را ادعا کند که هیچکس انجامش نمیدهد، وencryptionبه هر حال نام برده شده چون کلاینتی که نتواند روی آن شاخه بزند یک پیام مهرومومشده را پیامی خالی میخواند. - درخواستی که نتوان با وفاداری پاسخش داد یک 422 با
capability_unsupportedاست، نه پاسخی که درست به نظر برسد و بیصدا نادرست باشد.
پارامترها: threads.list (ThreadListOptions)
folderstring- کدام پوشه فهرست شود. سرور آن را به `inbox` پیشفرض میکند، پس حذفش فهرست را باریک میکند نه اینکه به همهچیز گستردهاش کند. روی جستوجوی `query` هم اعمال میشود، مگر آنکه خودِ کوئری با `in:` یا یک `is:` پوشهای مانند `is:sent` پوشهای را نام ببرد.
querystring- نحو جستوجوی صندوق پستی. همهٔ واژههای ساده باید حاضر باشند و هرکدام آزادانه تطبیق مییابد: بزرگی و کوچکی حروف، علائم و جداکنندهها نادیده گرفته میشوند و بخشی از واژهای بلندتر هم به حساب میآید، پس `min` و `ben jamin` هر دو «Benjamin» را پیدا میکنند. عبارت داخل گیومه جز از نظر بزرگی حروف و علائم دقیقاً همانطور که نوشته شده تطبیق مییابد، پس `"ben jamin"` عبارت «Ben-Jamin» را پیدا نمیکند، و واژههای پرکننده وقتی چیز دیگری برای جستوجو مانده باشد کنار گذاشته میشوند. با عملگرهایی مانند `from:ada`، `label:Invoices`، `is:unread`، `has:pdf`، `before:2026/01/31` و `older_than:1y` باریکش کنید و آنها را با `OR`، پرانتز و یک `-` در ابتدا ترکیب کنید؛ مقداری که جستوجو نتواند از آن استفاده کند نادیده گرفته میشود بهجای آنکه نتیجه را باریک کند. واژهها و عملگرهای `from:`، `to:`، `cc:`، `subject:` و `body:` فرستنده، گیرندگان، موضوع و 4,000 نویسهٔ نخستِ بدنهٔ آخرین پیام را با نشانهگذاری حذفشده میخوانند، در حالی که `filename:` و `has:` همهٔ پیوستهای کل گفتوگو را میخوانند و برچسبها و پوشهها کل گفتوگو را. همان ایندکسی را باریک میکند که فهرست بدون فیلتر میخواند. پیامهای مهرومومشده هیچ متن بدنهای ذخیره نمیکنند، پس تنها فرستنده، گیرندگان و موضوعشان میتواند تطبیق یابد.
labelIdsstring | string[]- فهرست را به رشتههایی محدود کنید که این برچسبها را دارند. اندپوینت یک رشتهٔ جداشده با کاما میگیرد و کلاینت یک آرایه را برایتان به آن تبدیل میکند؛ محدودیتی برای تعداد برچسبهایی که نام میبرید وجود ندارد.
limitnumber- چند رشته بازگردانده شود، از 1 تا 100. اگر داده نشود، هندلر از 25 استفاده میکند. پیشفرض بهجای اسکیما در خودِ هندلر است، پس نبودن مقدار و 25 صریح یکسان رفتار میکنند.
cursorstring- مقدار `nextCursor` صفحهٔ پیشین، که عیناً بازفرستاده میشود. همان `pageToken` در API است با نامی که هر فهرست دیگری به کار میبرد، و مات است، پس هرگز یکی نسازید یا ویرایشش نکنید.
پاسخ: Page<ThreadSummaryResource>
itemsThreadSummaryResource[]- برای هر رشته در این صفحه یک مدخل، بیرونکشیدهشده از پاکت `data` در API. هر مدخل تنها یک نشانگر object و یک شناسه است. این فهرست نه موضوع دارد، نه خلاصه، نه مشارکتکنندگان و نه برچسب، پس هر چیز بیشتری یعنی صدا زدن `threads.get` روی رشتههایی که میخواهید.
items[].idstring- شناسهٔ رشته، که بیتغییر به `threads.get`، `threads.update` و بقیه داده میشود. چه ردیف از یک فهرست فیلترشده آمده باشد چه از یک جستوجوی `query`، همان شناسه است.
hasMoreboolean- اینکه صفحهٔ دیگری هست یا نه، که هر جا API آن را بیان نکند از `nextCursor` مشتق میشود.
nextCursorstring | null- همان `nextPageToken` در API، که برای صفحهٔ بعدی بهصورت `cursor` بازفرستاده میشود، یا وقتی صفحهٔ دیگری نباشد null. توکن خالی به null نرمال میشود، پس بررسی falsy و بررسی null همداستاناند.