المحادثات
اقرأ البريد ونظّمه.
ينفّذ أيًّا من الاستدعاءات الـ7 في هذه الصفحة على مساحة عملك، بمفتاحك أنت.
السرد
GET /threads?folder=inbox. وتمرير query يبحث في الفهرس المحلي نفسه. ويجب أن تظهر كل الكلمات المجردة، وتطابق كل منها مطابقة متساهلة تتجاهل حالة الأحرف والعلامات والفواصل، فيجد min الاسم "Benjamin". أما العبارة بين علامتي اقتباس فتُطابَق كما كُتبت عدا حالة الأحرف والعلامات، فلا يجد "ben jamin" الاسم "Ben-Jamin". وتُسقَط كلمات الحشو مثل the أو emails من قائمة الكلمات المجردة متى بقي غيرها للبحث عنه. وتضيّق البحثَ معاملات مثل from: وto: وsubject: وlabel: وis:unread وhas:pdf وafter:2026/01/31 وnewer_than:7d، ويجمع بينها OR والأقواس وعلامة - في المقدمة. والمستلمون مخزَّنون كقائمة واحدة بلا أدوار ولا تحمل نسخة مخفية أبداً، فيقرأ cc: الحقل ذاته الذي يقرأه to: ولا يطابق bcc: شيئاً خاصاً به. وfrom:me هو البريد الذي أرسلته، وto:me هو البريد الذي يحمل أحد عناوينك، بما فيها الأسماء البديلة، بين مستلميه أو بوصفه العنوان الذي سُلّم إليه.
الكلمات والمعاملات from: وto: وcc: وsubject: وbody: تقرأ أحدث رسالة في كل محادثة: مرسلها ومستلميها وموضوعها وأول 4,000 حرف من متنها. ويقرأ filename: وhas: كل مرفق في المحادثة كاملة، ويقرأ label: وin: وis: المحادثة كاملة. ويظل folder سارياً ما لم يسمِّ الاستعلام مجلداً بـ in:، أو بـ is: يشير إلى مجلد مثل is:sent، وin:anywhere يبحث في كل مجلد، وحده أو إلى جانب مصطلحات أخرى. وسرد المسوّدات هو الاستثناء ويبقى في المسوّدات مهما سمّى الاستعلام.
القيمة التي لا يستطيع البحث استخدامها تُتجاهل بدل أن تضيّق، فالخطأ المطبعي في قيمة يوسّع النتيجة بدل أن يفرّغها: category: وlarger: وsmaller: وsize: وmessagesize: وlist: وrfc822msgid: وreceived: وsent:، وكلمات التصنيف مثل is:promotions، وكلمة has: التي لا تسمّي نوع مرفق، وقيمة importance: غير high أو low، والتاريخ غير المقروء، والمدة التي ليست وحدتها h أو d أو w أو m أو y. أما اسم المعامل الذي لا يعرفه، project: مثلاً، فيُبحث عنه كنص عادي. وتقرأ التواريخ أحدث نشاط في المحادثة، بتوقيت UTC، إذ يشمل after: اليوم الذي يسمّيه ويستثنيه before:؛ واكتب التاريخ على صورة YYYY/MM/DD أو YYYY-MM-DD أو YYYYMMDD أو سنة مجردة أو ثوانٍ أو مِلّي ثانية منذ الحقبة.
الحقل nextPageToken معتم. أعد تمرير ما أُعطي لك بحرفيته؛ ولا تبنِ واحداً ولا تعدّله أبداً. فبنيته ليست جزءاً من العقد.
الجلب
يعيد GET /threads/{id} كل رسالة في المحادثة، لا الأحدث وحدها، مع تسمياتها وما إذا كان فيها شيء غير مقروء.
الرسائل التي وصلت مشفَّرة
هذا الـ API لا يشفّر ولا يفكّ التشفير. فهو لا يستطيع فتح رسالة شفّرها غيره، ولا يستطيع إرسال رسالة مشفَّرة. والطلب الذي يحمل علامة تشفير يُرفض بـ 422، لأن الواجهات الوحيدة التي يجوز لها ضبطها هي التي تحمل المفاتيح، ولا عميل API يحمل مفتاحاً. أما ما يفعله فهو التعرّف على ظرف مختوم عند الدخول، من Content-Type في المستوى الأعلى لا أكثر، ثم الإفصاح عن ذلك على الرسالة.
صار OpenEmail نفسه يحمل مفاتيح، ويجدر التدقيق في أي نصف منها وأين. يولّد صاحب صندوق البريد هوية OpenPGP في متصفحه وينشر المفتاح العام في دليل يستطيع مرسلو OpenEmail المسجَّلون حلّه. أما النصف الخاص فيُصنع في ذلك المتصفح، ولا يُرسل إلى هنا أبداً، ولا يمكن استرجاعه أبداً، فلا شيء في هذا الـ API قادر على فك تشفير أي شيء، ولا يُنتج طلب دعم أو أمر قضائي أو نسخة احتياطية لدينا مفتاحاً قادراً على ذلك. ويستطيع تطبيق الويب الآن فتح رسالة PGP/MIME أو PGP المضمَّن حين يكون المفتاح في متصفح القارئ، لكن فك التشفير هذا يقع في التبويب ولا يُكتب نصه الصريح إلى الخادم أبداً: تبقى الرسالة المخزَّنة نصاً مشفَّراً، ولا تحمل أي استجابة من هذا الـ API النص المفتوح. ويستطيع التطبيق الآن ختم رسالة جديدة في المتصفح وإرسالها: يشفّر المؤلِّف إلى المفاتيح المنشورة للمستلمين ويخرج البريد على هيئة PGP/MIME. ولا يزال هذا الـ API عاجزاً عن ختم أي شيء، فالحقل أدناه يصف البريد الذي شفّره غيرك والبريد الذي خُتم في تبويب OpenEmail على السواء.
ويستحق ذلك حقلاً بسبب ما كان البديل عليه. فالرسالة المختومة لا تخزّن متناً مقروءاً، ومن ثمّ يعود decodedBody بـ ""، وهي البايتات ذاتها التي تعود من رسالة لا محتوى لها فعلاً. والحقل encryption هو ما يتيح لك التمييز بينهما قبل أن تتصرف بإحداهما، وهو تصريح عن الظرف لا تحقّق: فرؤية أن الرسالة مختومة ليست كفتحها.
{ "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'- أي ظرف وصل. يُقرأ من `Content-Type` في المستوى الأعلى (من معامله `protocol` لـ PGP، ومن `smime-type` لـ S/MIME)، أو، في حالة `pgp-inline`، من متن يبدأ بترويسة درع PGP. والجزء `pkcs7-mime` الذي لا يحمل `smime-type` إطلاقاً يُقرأ بوصفه `smime-encrypted`، وهو ما يجعله RFC 8551 افتراضياً.
detectedAtstring- بصيغة ISO 8601، وقت تشغيل الكاشف، وهو وقت استيعاب الرسالة هنا. ولا يقول شيئاً عن وقت تشفير الرسالة ولا عمّن شفّرها.
rawRetainedboolean- ما إذا كانت بايتات RFC822 الأصلية قد حُفظت، بحيث يمكن إعادة الرسالة كاملة. وهو false على كل رسالة اليوم، إذ لا شيء هنا يحتفظ بالبريد الخام بعد. وقد وُضع في الاستجابة الآن حتى لا يكون يوم تغيّره هو نفسه يوم ترحيل كل رسالة مخزَّنة من جديد.
partsobject[]- أجزاء الظرف التي يستخدمها هذا التنسيق. موجودة كلما وُجد `encryption`، وفارغة حين لا يوجد ما يُسمّى: فـ `pgp-inline` لا جزء منفصل له إطلاقاً، لأن درعه هو المتن نفسه ويصل في `decodedBody`.
parts[].indexnumber- أي جزء MIME من الرسالة الأصلية كان هذا، معدوداً على الأجزاء كما وصلت لا على `attachments`. فالقائمتان مختلفتان، وهذا كل سبب تسجيل هذه القيمة.
parts[].attachmentIdstring- المعرّف الذي يحمله هذا الجزء في `attachments`، إن ظهر فيها أصلاً: معرّف الرسالة يليه فهرس الجزء. والجزء `ciphertext` مُدرَج ويُنزَّل كأي ملف آخر؛ أما `version` و`signature` فمحجوبان عن القائمة، فتربط معرّفاتهما بين العرضين لا أكثر. ولن تعيدهما نقطة نهاية المرفقات.
parts[].role'version' | 'ciphertext' | 'signature'- الجزء `version` هو جزء التحكم في PGP/MIME، و`ciphertext` هو الرسالة، و`signature` توقيع منفصل. ولا يستحق الجلب إلا `ciphertext`؛ أما الآخران فأثاث بروتوكولي كان يظهر كمرفقات نفاية ولم يعد كذلك.
| format | ما الذي وصل | المتن |
|---|---|---|
| pgp-mime | ظرف PGP/MIME: multipart/encrypted مع protocol=application/pgp-encrypted. | مختوم |
| pgp-inline | درع في المتن نفسه. ولا يُقرأ إلا من نص المتن، فلا يُحسب ردٌّ يقتبس كتلة مدرَّعة رسالةً مشفَّرة. | مختوم |
| smime-encrypted | جزء pkcs7-mime من S/MIME مع smime-type=enveloped-data، أو جزء بلا smime-type إطلاقاً. | مختوم |
| pgp-signed | توقيع PGP منفصل إلى جانب الرسالة: multipart/signed مع protocol=application/pgp-signature. | قابل للقراءة |
| smime-signed | توقيع S/MIME منفصل: بروتوكول pkcs7-signature، أو smime-type=signed-data. | قابل للقراءة |
الموقَّع ليس مختوماً، والتفريع على وجود encryption بدل format يقلب ذلك رأساً على عقب. فالتوقيع ادّعاء عمّن كتب الرسالة لا غلاف حولها: متن الرسالة الموقَّعة صريح ويُقرأ كأي متن آخر. عامل pgp-mime وpgp-inline وsmime-encrypted على أنها غير مقروءة، وعامل التنسيقين الموقَّعين على أنهما بريد عادي.
ما الذي يتغيّر في رسالة مختومة
لا تغيّر شيئاً إلا التنسيقات المختومة الثلاثة، والتغيير يقع عند الاستيعاب لا في هذه الاستجابة. وكل ما كان سيقرأ المتن يتنحّى، بدل أن يقرأ نصاً مشفَّراً ويبلّغ بنتيجة ما كان ليحصل عليها:
- البحث في المتن. تُفهرَس الرسالة بمقتطف متن فارغ، فتظل تُوجد بالمرسل والموضوع والعنوان والتسمية، ولا تُوجد بشيء مما بداخلها.
- مرور مقيِّم التصيّد على المتن. ويظل الحكم يصل ويقول ما عجز عنه: يحمل
risk.signalsالقيمةbody-encryptedويكونrisk.aiCheckedبقيمة false. - فحص تأليف الذكاء الاصطناعي، وهو يمتنع بدل أن يخمّن: تكون
aiWritten.levelبقيمةunknownوaiWritten.skippedبقيمةencrypted. - شروط المتن في القواعد. تعمل شروط الظرف والترويسة تماماً كما كانت؛ أما القاعدة التي سألت عن المتن فتُسجَّل غير مُقيَّمة بدل أن تُحسب عدم تطابق، لأن «لم تطابق» و«تعذّرت قراءته» جوابان مختلفان.
- استيراد دعوات التقويم. فالدعوة داخل النص المشفَّر، وبناء حدث من الظرف يضع مدخلاً خاطئاً في تقويم حقيقي.
- ملخّصات المحادثات والتضمينات، للمحادثة كلها. ويكفي ردّ مختوم واحد. فالملخّص قراءة نموذج للنص الصريح تُخزَّن بيانات وصفية غير مشفَّرة، وهو الموضع الوحيد في هذا المسار الذي يتسرّب فيه متن إلى مخزن لا يعدّه أحد متناً.
وكل ما لا يحتاج المتن يبقى بلا مساس:
- DMARC وDKIM وSPF. تُقرأ هذه من
Authentication-Results، وهو ما لا يخفيه النص المشفَّر، فتنال الرسالة المشفَّرة حكم استيثاق حقيقياً لا انعدامه. - بناء المحادثات وحفظ البريد المزعج وقائمة الحظر: كلها عمل على الظرف والترويسات.
- المرفقات. يبقى جزء
ciphertextفيattachments، باسمencrypted-message.ascحين يصل بلا اسم، ويُنزَّل عبر نقطة النهاية أدناه. وهو بعينه ما يجلبه قارئ تطبيق الويب ويفك تشفيره في المتصفح؛ أما لعميل API لا يحمل مفتاحاً فيبقى ذلك التنزيل السبيل الوحيد لقراءة البريد. افتحه في عميل يملك مفتاحاً. - الرسالة الموقَّعة لا تفقد شيئاً من هذا. فكل فحص مما سبق يظل يعمل عليها، ولا يُحجب شيء، ولهذا كانت قائمة المختوم قائمة من ثلاثة تنسيقات لا خمسة.
غياب encryption ليس ادّعاءً بأن النص صريح. بل يعني أن أحداً لم ينظر: الرسالة أقدم من الكشف، أو بلغت صندوق البريد بمسار لا يشغّل الكاشف. ولا شيء يملأه بأثر رجعي، فالحقل الذي يقول «لم نفحص» يجب ألّا يُقرأ أبداً على أنه «فحصنا فلم نجد».
وضع العلامات والتسميات
يقبل PATCH /threads/{id} الحقول read وaddLabelIds وremoveLabelIds. وحالة القراءة تسمية في كل خلفية يدعمها هذا المنتج، فضبط read وتحريك التسميات في طلب واحد يبقي الترتيب حتمياً.
{ "read": true, "addLabelIds": ["USER_INVOICES"] }تُرفض TRASH وSNOOZED هنا بـ label_not_directly_settable. فلا تحمل تسميةٌ وحدها أياً من الحالتين (الإرسال إلى السلة يمسح تسميات المجلدات أيضاً، والتأجيل يحتاج وقت إيقاظ مخزَّناً بجانبه)، وضبطهما يدوياً يترك المحادثة في حالة لا ينتجها التطبيق أبداً ولا يستطيع التعافي منها. استخدم نقاط النهاية أدناه.
السلة والتأجيل
| نقطة النهاية | ما تفعله |
|---|---|
| POST /threads/{id}/trash | ينقلها إلى سلة المحذوفات (Bin)، ويمسح INBOX وSPAM وSNOOZED وARCHIVE معاً. |
| POST /threads/{id}/snooze | المتن { "wakeAt": "…" }. يخفيها ويجدول عودتها. |
| POST /threads/{id}/unsnooze | يعيدها الآن، ويلغي العودة المجدولة. |
يكتب التأجيل شيئين: التسمية التي تخفي المحادثة، والمدخل الذي يعيدها. وأداء أحدهما دون الآخر هو بالضبط سبب كونها نقاط نهاية لا تعديلات تسميات.
المرفقات
يعيد GET /threads/{id}/messages/{messageId}/attachments كل مرفق مع filename وcontentType وsize وcontent بترميز base64. ويكون content سلسلة فارغة حين يتعذّر إيجاد البايتات المخزَّنة، فتحقّق من طوله قبل فك الترميز.
الظرف المشفَّر ليس كله هنا. النص المشفَّر موجود (فهو الرسالة، وتنزيله السبيل الوحيد ليقرأ عميل API هذا البريد)، أما جزء version في PGP/MIME وأي توقيع منفصل فمحجوبان عن القائمة، لأنهما كانا يظهران كمرفقات نفاية ولا شيء يستطيع المستدعي فعله بهما. ويحتفظ كلاهما بمعرّفه في encryption.parts، وهو ما يربط بين العرضين؛ ولا تعيدهما نقطة النهاية هذه.