दस्तावेज़ पर जाएँ
PHP

Threads

`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 आपके लिए इसका पीछा करते हैं। यह अपारदर्शी है: जो मिला वही वापस भेजें और ख़ुद कभी न बनाएँ।

सूची के फ़िल्टर named आर्ग्युमेंट हैं (labelIds:, dateFrom:), जबकि रिक्वेस्ट बॉडी के फ़ील्ड API के नामों वाली array कुंजियाँ हैं (update पर addLabelIds)। thread camelCase कुंजियों वाले array के रूप में लौटता है, इसलिए $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: thread सूची के अपने नियंत्रण हैं। sort: newest, oldest, sender या subject होता है, और OpenEmail\Constants\ThreadSorts इनके नाम देता है। तारीख़ें UTC में क्षण के रूप में भेजा जाने वाला DateTimeInterface, या समय और offset वाली ISO 8601 स्ट्रिंग लेती हैं, और दोनों सिरे शामिल होते हैं। बिना समय वाली तारीख़ की स्ट्रिंग 422 के साथ अस्वीकार की जाती है। fromContacts: true वह मेल रखता है जिसका सबसे नया संदेश किसी सहेजे गए संपर्क से आया हो। हर क्रम किसी thread को छोड़े या दोहराए बिना अंत तक पेज करता है।

listAll आख़िरी पेज आ जाने पर एक array लौटाता है। iterate एक Generator लौटाता है जो हर thread को 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 पर एक लेबल है, इसलिए यह लेबल सूचियों के साथ चलती है, और दोनों सेट करने पर क्रम तय है: हटाना जोड़ने से पहले लागू होता है, इसलिए दोनों सूचियों में मौजूद id अंत में थ्रेड पर रहती है। तीनों फ़ील्ड में से कम से कम एक मौजूद होना चाहिए।

addLabelIds labels->list से ids और ARCHIVE तथा STARRED जैसी सिस्टम ids लेता है। जो id किसी लेबल का नाम नहीं लेती उसे बनाया नहीं जाता, बल्कि 422 label_not_found के साथ अस्वीकार किया जाता है, इसलिए पहले labels->create से लेबल बनाएँ। $client->threads->list(folder: 'USER_DONE') लेबल वाला हर थ्रेड सूचीबद्ध करता है, चाहे वह किसी भी फ़ोल्डर में हो।

किसी संदेश पर attachment

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 arrays की एक सूची लौटाता है। content base64 है, जिसे base64_decode() वापस बाइट्स में बदलता है, और जब सहेजे गए बाइट्स नहीं मिलते तो यह ख़ाली स्ट्रिंग होता है, इसलिए डिकोड करने से पहले इसे जाँचें। encrypted संदेश का ciphertext इस सूची में होता है और किसी भी दूसरी फ़ाइल की तरह डाउनलोड होता है। PGP/MIME version वाला हिस्सा और कोई भी detached signature इसमें नहीं होते। उनकी id सिर्फ़ encryption.parts में रहती हैं, और कुछ नहीं।

ऐसा संदेश जो एन्क्रिप्टेड आया

यह पैकेज न encrypt करता है न decrypt। यह किसी और द्वारा encrypt किया गया संदेश नहीं खोल सकता, और encrypted संदेश भेज भी नहीं सकता। अगर send रिक्वेस्ट में encryption का कोई marker हो तो उसे अस्वीकार कर दिया जाता है, क्योंकि जिस क्लाइंट के पास कोई key नहीं, उसका ऐसा दावा करने का कोई मतलब नहीं। OpenEmail ऐप में बनी keys उसी ब्राउज़र में रहती हैं जिसने उन्हें बनाया, और यहाँ किसी चीज़ तक नहीं पहुँचतीं। जब वह ब्राउज़र किसी sealed संदेश को खोलता है तो plaintext उसी में रहता है, और यह कॉल जो सहेजा गया संदेश पढ़ती है वह अब भी ciphertext ही है। threads->get आपको जो देता है वह पहचाना गया envelope है। जो संदेश PGP या S/MIME में लिपटा हुआ आया, उसमें एक encryption array होता है, ताकि आपको सिर्फ़ ख़ाली 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() से branch करें, फ़ील्ड की मौजूदगी पर कभी नहीं। पाँच में से दो formats, pgp-signed और smime-signed, ऐसी बॉडी बताते हैं जो detached signature के साथ खुले रूप में आई, इसलिए मौजूदगी पर रोक लगाने से वह मेल छिप जाती है जिसे छिपाने की किसी को ज़रूरत नहीं थी, और उपयोगकर्ता न उसे देख सकता है न समझा सकता है। OpenEmail::isSealed() ठीक इसी कारण से है। सर्वर sealed समूह को एक बार बताता है, पैकेज की कॉपी उसी स्रोत से बनती है, और हाथ से लिखी गई तीसरी कॉपी ही वह कॉपी है जो भटक जाती है। OpenEmail\Constants\MessageEncryptionFormats पाँचों formats के नाम देता है।

अनुपस्थिति का अर्थ plaintext नहीं है। detection शिप होने से पहले संग्रहीत हर संदेश पर, और हर उस चीज़ पर जो मेलबॉक्स तक ऐसे रास्ते से पहुँची जहाँ detector कभी नहीं चला, encryption अनुपस्थित है। यह दर्ज करता है कि किसी ने देखा ही नहीं। यह तथ्य हमारी coverage के बारे में है, मेल के बारे में नहीं, और इसे कुछ भी backfill नहीं करता।

ये बाकियों से कहाँ अलग हैं

  • किसी thread के messages की हर प्रविष्टि वह array है जिसे मेलबॉक्स ने सहेजा, जिसमें फ़ील्ड की कोई तय सूची नहीं है, इसलिए encryption के अलावा कोई भी कुंजी ?? null के साथ पढ़ें। इससे ज़्यादा का वादा करना क्लाइंट का ऐसे normalisation का दावा करना होगा जो कोई करता ही नहीं। फिर भी encryption वह अकेला फ़ील्ड है जिसकी गारंटी API देता है, क्योंकि जो क्लाइंट उस पर branch नहीं कर सकता वह sealed संदेश को ख़ाली संदेश के रूप में पढ़ता है।
  • जिस रिक्वेस्ट को ईमानदारी से पूरा नहीं किया जा सकता, वह एक 422 capability_unsupported है, जो ValidationException के रूप में throw होता है, न कि ऐसा जवाब जो सही दिखे पर चुपचाप ग़लत हो।

पैरामीटर: threads->list

folderstring
कौन-सा फ़ोल्डर सूचीबद्ध करना है। सर्वर इसका डिफ़ॉल्ट `inbox` रखता है, इसलिए इसे छोड़ने से सूची सब कुछ तक फैलने के बजाय सिमटती है। यह `query:` खोज पर भी लागू होता है, जब तक कि query ख़ुद `in:` से या `is:sent` जैसे फ़ोल्डर वाले `is:` से कोई फ़ोल्डर न बताए।
querystring
मेलबॉक्स की खोज सिंटैक्स। सादे शब्द सभी मौजूद होने चाहिए, और हर एक ढीले ढंग से मेल खाता है: case, accents और विभाजक अनदेखे होते हैं और किसी लंबे शब्द का हिस्सा भी गिना जाता है, इसलिए `min` और `ben jamin` दोनों “Benjamin” ढूँढ लेते हैं। उद्धरण-चिह्नों वाला वाक्यांश case और accents को छोड़कर लिखे अनुसार मिलाया जाता है, इसलिए `"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` जैसे operators से सीमित करें, और उन्हें `OR`, कोष्ठकों और आगे लगे `-` से जोड़ें। जिस मान को खोज इस्तेमाल नहीं कर सकती उसे सीमित करने के बजाय अनदेखा कर दिया जाता है। शब्द और `from:`, `to:`, `cc:`, `subject:` तथा `body:` operators नवीनतम संदेश के प्रेषक, प्राप्तकर्ता, विषय और markup हटाकर उसकी बॉडी के पहले 4,000 वर्ण पढ़ते हैं, जबकि `filename:` और `has:` पूरी बातचीत का हर अटैचमेंट पढ़ते हैं, और लेबल तथा फ़ोल्डर पूरी बातचीत पढ़ते हैं। यह उसी index को सीमित करता है जिसे बिना फ़िल्टर वाली सूची पढ़ती है। sealed संदेश कोई बॉडी टेक्स्ट नहीं सहेजते, इसलिए सिर्फ़ उनके प्रेषक, प्राप्तकर्ता और विषय मेल खा सकते हैं। सादा शब्द बातचीत के किसी भी अटैचमेंट के नाम से भी मेल खाता है, चाहे वह किसी भी संदेश के साथ आया हो।
labelIdsstring or array
सूची को इन लेबल वाले threads तक सीमित करें। endpoint कॉमा से अलग की गई स्ट्रिंग लेता है, और क्लाइंट आपके लिए array को एक स्ट्रिंग में जोड़ देता है। आप कितने नाम दें, इसकी कोई सीमा नहीं है।
limitint
कितने थ्रेड लौटाने हैं, 1 से 100। छोड़ने पर handler 25 इस्तेमाल करता है। डिफ़ॉल्ट schema में नहीं, handler में रहता है, इसलिए अनुपस्थित मान और स्पष्ट 25 एक जैसा व्यवहार करते हैं।
cursorstring
पिछले पेज का `nextCursor`, जैसा का तैसा वापस भेजा गया। यह API का `pageToken` है, उस नाम से जो हर दूसरी सूची इस्तेमाल करती है, और यह अपारदर्शी है, इसलिए इसे कभी ख़ुद न बनाएँ और न बदलें।

जवाब: OpenEmail\Result\Page

itemsarray
इस पेज में हर thread के लिए एक array, API के `data` envelope से बाहर निकाला गया। हर एक में सिर्फ़ एक `object` marker और एक `id` होता है। सूची में कोई subject, snippet, प्रतिभागी या लेबल नहीं होते, इसलिए इससे ज़्यादा चाहिए तो जिन threads को आप चाहते हैं उन पर `threads->get` कॉल करें।
items[].idstring
थ्रेड की id, `$item['id']` के रूप में पढ़ी जाती है, जिसे `threads->get`, `threads->update` और बाक़ी को बिना बदले देना है। पंक्ति चाहे फ़िल्टर की गई सूची से आई हो या `query:` खोज से, id वही रहती है।
hasMorebool
क्या आगे कोई पेज है, जहाँ API बताता है वहाँ उससे लिया जाता है और जहाँ नहीं बताता वहाँ `nextCursor` से निकाला जाता है।
nextCursorstring or null
API का `nextPageToken`, अगले पेज के लिए `cursor:` के रूप में वापस भेजने के लिए, या आगे कोई पेज न होने पर null। ख़ाली टोकन को null में बदल दिया जाता है, इसलिए आपको बस null की जाँच करनी है।