پرش به مستندات
PHP

رشته‌ها

`threads->list`، `listAll`، `iterate`، `get`، `update`، `trash`، `snooze`، `unsnooze` و `listAttachments`.

خواندن

read_threads.php
$page = $client->threads->list(    folder: 'inbox',    query: 'from:ada',    labelIds: ['INBOX', 'IMPORTANT'],    limit: 25,); if ($page->nextCursor !== null) {    $nextPage = $client->threads->list(folder: 'inbox', cursor: $page->nextCursor);    echo count($nextPage), PHP_EOL;} $thread = $client->threads->get('CAHk7pQ2x9LmZ4-mail.example.com');echo $thread['messageCount'], ' ', $thread['hasUnread'] ? 'unread' : 'read', ' ', $thread['totalReplies'], PHP_EOL;

API رشته‌ها را با یک pageToken صفحه‌بندی می‌کند. کلاینت آن را به‌صورت nextCursor به شما می‌دهد و به‌صورت cursor: پس می‌گیرد، مثل هر فهرست دیگری، و listAll و iterate آن را برایتان دنبال می‌کنند. این مقدار مبهم است: همان چیزی را که گرفته‌اید پس بدهید و هرگز خودتان یکی نسازید.

فیلترهای فهرست، آرگومان‌های نام‌دار هستند (labelIds:، dateFrom:)، در حالی که فیلدهای بدنهٔ درخواست کلیدهای آرایه با نام‌های API هستند (addLabelIds روی update). هر رشته به‌صورت یک آرایه با کلیدهای camelCase برمی‌گردد، پس $thread['messageCount'] تعداد را می‌خواند.

sort_threads.php
use OpenEmail\Constants\ThreadSorts; $lastWeek = $client->threads->listAll(    sort: ThreadSorts::OLDEST,    dateFrom: new \DateTimeImmutable('-7 days'),    dateTo: new \DateTimeImmutable(),    fromContacts: true,);echo count($lastWeek), PHP_EOL; foreach ($client->threads->iterate(sort: ThreadSorts::SENDER) as $thread) {    echo $thread['id'], PHP_EOL;}

sort:، dateFrom:، dateTo: و fromContacts: کنترل‌های خودِ فهرست رشته‌ها هستند. sort: یکی از newest، oldest، sender یا subject است، و OpenEmail\Constants\ThreadSorts آن‌ها را نام می‌برد. تاریخ‌ها یک DateTimeInterface می‌گیرند که به‌صورت لحظه‌ای با UTC فرستاده می‌شود، یا یک رشتهٔ ISO 8601 همراه با ساعت و offset، و هر دو سر شامل‌اند. رشتهٔ تاریخی بدون ساعت با یک 422 رد می‌شود. fromContacts: true نامه‌هایی را نگه می‌دارد که تازه‌ترین پیامشان از یک مخاطب ذخیره‌شده آمده است. هر ترتیب تا انتها صفحه‌بندی می‌شود بی‌آنکه رشته‌ای جا بیفتد یا تکرار شود.

listAll وقتی آخرین صفحه رسید یک آرایه برمی‌گرداند. iterate یک Generator برمی‌گرداند که هر رشته را yield می‌کند و صفحهٔ بعد را فقط وقتی حلقه به آن نیاز دارد می‌گیرد، پس به محض اینکه آنچه لازم دارید را داشته باشید یک break درخواست‌ها را متوقف می‌کند.

سازمان‌دهی

organise_threads.php
$threadId = 'CAHk7pQ2x9LmZ4-mail.example.com'; $client->threads->update($threadId, ['read' => true, 'addLabelIds' => ['USER_DONE'], 'removeLabelIds' => ['INBOX']]); $client->threads->trash($threadId);$client->threads->snooze($threadId, new \DateTimeImmutable('+1 day'));$client->threads->unsnooze($threadId);

وضعیت خوانده شدن اینجا روی هر backend یک برچسب است، پس همراه فهرست‌های برچسب می‌آید، و وقتی هر دو را تنظیم کنید ترتیب ثابت است: حذف‌ها پیش از افزودن‌ها اعمال می‌شوند، پس شناسه‌ای که در هر دو فهرست باشد در نهایت روی رشته می‌ماند. دست‌کم یکی از آن سه فیلد باید حاضر باشد.

addLabelIds شناسه‌هایی از labels->list و شناسه‌های سیستمی مانند ARCHIVE و STARRED را می‌گیرد. شناسه‌ای که به هیچ برچسبی اشاره نکند به‌جای ساخته شدن با یک 422 label_not_found رد می‌شود، پس ابتدا برچسب را با labels->create بسازید. $client->threads->list(folder: 'USER_DONE') هر رشته‌ای را که یک برچسب دارد فهرست می‌کند، در هر پوشه‌ای که باشد.

پیوست‌های یک پیام

attachments.php
$files = $client->threads->listAttachments('CAHk7pQ2x9LmZ4-mail.example.com', 'message_4c1b257a'); foreach ($files as $file) {    echo $file['filename'], ' ', $file['contentType'], ' ', $file['size'], PHP_EOL;     $bytes = base64_decode($file['content'], true);     if ($file['content'] !== '' && $bytes !== false) {        file_put_contents(basename($file['filename']), $bytes);    }}

listAttachments فهرستی از آرایه‌ها برمی‌گرداند. content به‌صورت base64 است، که base64_decode() آن را دوباره به بایت تبدیل می‌کند، و وقتی بایت‌های ذخیره‌شده پیدا نشوند یک رشتهٔ خالی است، پس پیش از کدگشایی آن را بررسی کنید. متن رمزِ یک پیام رمزگذاری‌شده در این فهرست هست و مثل هر فایل دیگری دانلود می‌شود. بخش نسخهٔ PGP/MIME و هر امضای جداگانه در آن نیستند. آن‌ها تنها شناسه‌هایشان را در encryption.parts نگه می‌دارند و بس.

پیامی که رمزنگاری‌شده رسیده است

این بسته نه رمزگذاری می‌کند و نه رمزگشایی. نمی‌تواند پیامی را که کسی دیگر رمزگذاری کرده باز کند، و نمی‌تواند پیامی رمزگذاری‌شده بفرستد. اگر درخواست ارسال نشانگر رمزگذاری با خود داشته باشد رد می‌شود، چون کلاینتی که کلید ندارد حقی هم برای ادعای آن ندارد. کلیدهایی که در برنامهٔ OpenEmail ساخته می‌شوند در همان مرورگری که ساخته‌شان می‌مانند و به اینجا نمی‌رسند. وقتی آن مرورگر پیامی مهروموم‌شده را باز می‌کند متن آشکار در همان‌جا می‌ماند، و پیام ذخیره‌شده‌ای که این فراخوانی می‌خواند همچنان متن رمز است. آنچه threads->get به شما می‌دهد پاکت است، بازشناخته‌شده. پیامی که پیچیده در PGP یا S/MIME رسیده باشد یک آرایه به نام encryption دارد، تا یک decodedBody خالی دیگر تنها چیزی نباشد که به شما داده می‌شود. encryption تنها فیلد پیام است که API به آن متعهد است، چون تنها فیلدی است که نمی‌توان با حدس زدن دربارهٔ نبودنش کنار آمد.

encrypted_mail.php
use OpenEmail\OpenEmail; $thread = $client->threads->get('CAHk7pQ2x9LmZ4-mail.example.com'); foreach ($thread['messages'] as $message) {    if (!isset($message['encryption']) || !OpenEmail::isSealed($message)) {        continue;    }     error_log('cannot read this one: ' . $message['encryption']['format']);}

با OpenEmail::isSealed() شاخه بزنید، هرگز بر اساس حضور خودِ فیلد. دو تا از پنج قالب، pgp-signed و smime-signed، بدنه‌ای را توصیف می‌کنند که به‌صورت آشکار همراه یک امضای جداگانه رسیده است، پس شرط گذاشتن روی حضور، ایمیلی را پنهان می‌کند که هیچ‌کس نیازی به پنهان کردنش نداشته، و کاربر نه می‌تواند ببیندش نه توضیحش دهد. OpenEmail::isSealed() دقیقاً به همین دلیل وجود دارد. سرور مجموعهٔ مهروموم‌شده را یک بار بیان می‌کند، نسخهٔ بسته از همان منبع تولید می‌شود، و نسخهٔ سومی که با دست نوشته شود همان نسخه‌ای است که واگرا می‌شود. OpenEmail\Constants\MessageEncryptionFormats هر پنج قالب را نام می‌برد.

نبودن به معنای متن آشکار نیست. encryption روی هر پیامی که پیش از عرضهٔ تشخیص ذخیره شده باشد غایب است، و روی هر چیزی که از مسیری به صندوق پستی رسیده باشد که تشخیص‌دهنده هرگز روی آن اجرا نشده است. این فیلد ثبت می‌کند که کسی نگاه نکرده است، یعنی واقعیتی دربارهٔ پوشش ما نه دربارهٔ خودِ ایمیل، و هیچ‌چیز آن را به‌صورت عقب‌گرد پر نمی‌کند.

تفاوت‌های این بخش با بقیه

  • هر مدخل در messages یک رشته همان آرایه‌ای است که صندوق پستی ذخیره کرده، بدون فهرست ثابتی از فیلدها، پس هر کلیدی جز encryption را با ?? null بخوانید. وعدهٔ بیشتر یعنی کلاینت نرمال‌سازی‌ای را ادعا کند که هیچ‌کس انجامش نمی‌دهد. encryption تنها فیلدی است که API به هر حال به آن متعهد است، چون کلاینتی که نتواند روی آن شاخه بزند یک پیام مهروموم‌شده را پیامی خالی می‌خواند.
  • درخواستی که نتوان با وفاداری پاسخش داد یک 422 capability_unsupported است که به‌صورت ValidationException پرتاب می‌شود، نه پاسخی که درست به نظر برسد و بی‌صدا نادرست باشد.

پارامترها: threads->list

folderstring
کدام پوشه فهرست شود. سرور آن را به `inbox` پیش‌فرض می‌کند، پس ننوشتنش فهرست را باریک می‌کند نه اینکه به همه‌چیز گسترده‌اش کند. روی جست‌وجوی `query:` هم اعمال می‌شود، مگر آنکه خودِ کوئری با `in:` یا یک `is:` پوشه‌ای مانند `is:sent` پوشه‌ای را نام ببرد.
querystring
نحو جست‌وجوی صندوق پستی. همهٔ واژه‌های ساده باید حاضر باشند و هرکدام آزادانه تطبیق می‌یابد: بزرگی و کوچکی حروف، اعراب و جداکننده‌ها نادیده گرفته می‌شوند و بخشی از واژه‌ای بلندتر هم به حساب می‌آید، پس `min` و `ben jamin` هر دو «Benjamin» را پیدا می‌کنند. عبارت داخل گیومه جز از نظر بزرگی حروف و اعراب دقیقاً همان‌طور که نوشته شده تطبیق می‌یابد، پس `"ben jamin"` عبارت «Ben-Jamin» را پیدا نمی‌کند، و واژه‌های پرکننده وقتی چیز دیگری برای جست‌وجو مانده باشد کنار گذاشته می‌شوند. وقتی هیچ چیز دقیقاً مطابقت نداشته باشد، به‌جای آن املاهای نزدیک برگردانده می‌شوند، پس `benjimin` واژهٔ «Benjamin» را پیدا می‌کند: یک واژهٔ ساده، یا مقدار `from:`، `to:`، `cc:`، `subject:`، `body:`، `filename:` یا `label:`، اگر چهار تا هفت حرف داشته باشد می‌تواند به اندازهٔ یک غلط تایپی (حرفی عوض‌شده، جاافتاده، اضافه یا جابه‌جا) با آغاز یک واژه تفاوت داشته باشد و اگر هشت حرف یا بیشتر داشته باشد به اندازهٔ دو غلط. عبارت داخل گیومه، واژهٔ دارای رقم، واژهٔ کوتاه‌تر و واژهٔ کنارگذاشته‌شده همچنان فقط دقیق مطابقت می‌یابند، و صفحه‌های بعدی نیز به همین شیوه تطبیق می‌دهند. با عملگرهایی مانند `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 or array
فهرست را به رشته‌هایی محدود کنید که این برچسب‌ها را دارند. اندپوینت یک رشتهٔ جداشده با کاما می‌گیرد، و کلاینت یک آرایه را برایتان به آن تبدیل می‌کند. محدودیتی برای تعداد برچسب‌هایی که نام می‌برید وجود ندارد.
limitint
چند رشته بازگردانده شود، از 1 تا 100. اگر داده نشود، هندلر از 25 استفاده می‌کند. پیش‌فرض به‌جای اسکیما در خودِ هندلر است، پس نبودن مقدار و 25 صریح یکسان رفتار می‌کنند.
cursorstring
مقدار `nextCursor` صفحهٔ پیشین، که عیناً پس داده می‌شود. همان `pageToken` در API است با نامی که هر فهرست دیگری به کار می‌برد، و مبهم است، پس هرگز یکی نسازید یا ویرایشش نکنید.

پاسخ: OpenEmail\Result\Page

itemsarray
برای هر رشته در این صفحه یک آرایه، بیرون‌کشیده از پاکت `data` در API. هرکدام تنها یک نشانگر `object` و یک `id` است. این فهرست نه موضوع دارد، نه خلاصه، نه مشارکت‌کنندگان و نه برچسب، پس هر چیز بیشتری یعنی فراخوانی `threads->get` روی رشته‌هایی که می‌خواهید.
items[].idstring
شناسهٔ رشته، که به شکل `$item['id']` خوانده می‌شود، تا بی‌تغییر به `threads->get`، `threads->update` و بقیه داده شود. چه ردیف از یک فهرست فیلترشده آمده باشد چه از یک جست‌وجوی `query:`، همان شناسه است.
hasMorebool
اینکه صفحهٔ دیگری هست یا نه، که هر جا API آن را بیان کند از همان گرفته می‌شود و هر جا نکند از `nextCursor` مشتق می‌شود.
nextCursorstring or null
همان `nextPageToken` در API، که برای صفحهٔ بعدی به‌صورت `cursor:` پس فرستاده می‌شود، یا وقتی صفحهٔ دیگری نباشد null. توکن خالی به null نرمال می‌شود، پس یک بررسی null تنها آزمونی است که لازم دارید.