تخطَّ إلى المستندات
قاعدة المعرفة

Webhooks

تُخبر نقطة النهاية لديك عند وصول البريد، بدل أن تضطرك إلى الاستقصاء المتكرر.

التفاصيل

  • قابلة للاستخدام اليوم من Settings → Webhooks وعبر API: سجّل نقطة نهاية https، واختر أيًّا من الأحداث العشرين تريدها، وانسخ سر التوقيع whsec_ الذي يُعرض عند الإنشاء وعند التدوير ولا يُعرض بعد ذلك أبدًا. والتسليمات طلبات POST موقّعة حقيقية يطلقها صندوق البريد نفسه لا أي استدعاء لـ API، فتنطلق عند وصول البريد وعند عمليات الفتح والنقر أيًّا كان ما أرسل الرسالة. ويطلق الإرسال من كل واجهة، وكان يطلق من بعضها فقط: فالإرسال عبر API أو MCP أو قالب أو قاعدة كان يطلق email.sent بينما لا يفعل ذلك ما يُرسل من نافذة الإنشاء في التطبيق، لأن نافذة الإنشاء تكتب إلى صندوق البريد مباشرة لا عبر خدمة الإرسال التي كانت تصدر الحدث. أما الآن فيُطلق الحدث عند صندوق البريد نفسه، وهو الموضع الذي تلتقي فيه كلها، فصار الإنشاء في التطبيق والجدولة ليوم الثلاثاء والإرسال إلى API ثلاث طرق لإحداث الـ webhook نفسه. والإرسال المؤجّل يعلن عن نفسه مرتين: email.scheduled أو email.queued عند قبوله، وemail.sent عند خروجه فعليًا، وemail.cancelled إن تراجعت عنه بينهما. عشر نقاط نهاية لكل صندوق بريد، تُطبَّق حيثما سُجّلت واحدة لا على هذه الشاشة وحدها.
  • تأتي الأحداث في ثلاث عائلات. خمسة عشر منها يخص رسالة واحدة: email.received وemail.replied وemail.sent وemail.delivered وemail.failed وemail.cancelled وemail.scheduled وemail.queued (شقيق scheduled الخاص بالتراجع عن الإرسال) وemail.delivery_delayed وemail.bounced وemail.complained وemail.suppressed وemail.opened وemail.clicked وemail.downloaded. ويعني email.sent أن خدمة الإرسال قبلت الرسالة، ويعني email.delivered أن الخادم المستقبِل قبلها، ويعني email.delivery_delayed أنها لم تصل بعد ولا تزال قيد إعادة المحاولة. وينطلق email.replied إلى جانب email.received عندما تكون الرسالة الواصلة ردًّا على رسالة موجودة في صندوق البريد، فالمستهلك الذي يريد الاثنين يحصل عليهما. وينطلق email.downloaded عندما يجلب شخص ملفًا خرج على هيئة رابط تنزيل، مع المصنّف نفسه الذي يبقي الماسحات ومعاينات الروابط خارج العدّ، وهو لا يسمّي أي مستلم، لأن الرابط واحد لكل من أُرسلت إليهم الرسالة. وثلاثة أحداث تخص نطاقًا: domain.verified عندما يبدأ الاستقبال، وdomain.sending_changed عندما يتغيّر حكم الإرسال الخاص به، وdomain.deleted عند إزالته، سواء طلبت ذلك أم أسقطه حاصد الأيام السبعة وهو غير موثّق. وحدثان يخصان قائمة الحجب نفسها، وهي شيء مختلف عن email.suppressed: ‏suppression.added عندما يُضاف عنوان، وsuppression.removed عندما يُسمح بعنوان من جديد. والاشتراك في لا شيء منها يعني كل أحداث الرسائل عدا email.replied، أي أربعة عشر اليوم، ولا تُضاف إليها عائلة لاحقًا أبدًا، وتعيدها API قراءةً بوصفها ["*"]. سمِّ الأحداث التي تريدها إن كنت تفضّل التصريح. وتحمل كل عملية تسليم X-OpenEmail-Signature بصيغة t=<unix>,v1=<hex>، وهو HMAC-SHA-256 على الطابع الزمني ونقطة والجسم الخام، إضافة إلى X-OpenEmail-Event وX-OpenEmail-Delivery. تحقّق مقابل البايتات كما وصلت: فالتحليل وإعادة التسلسل يعيدان ترتيب المفاتيح ويكسران التوقيع. ونافذة إعادة التشغيل البالغة 300 ثانية مسؤولية المستقبِل، ومُتحقِّق SDK يعتمدها افتراضيًا.
  • يُرفض التسجيل لأي شيء ليس https أو غير قابل للتوجيه علنًا (loopback وRFC1918 وlink-local وCGNAT وما يقابلها في IPv6)، ولا تُتبَع عمليات إعادة التوجيه، فيُسجَّل رد 3xx تسليمًا فاشلًا بدل ملاحقته إلى مكان آخر. ويحصل المستقبِل على 5 ثوانٍ، وتُسلَّم نقاط النهاية بالتوازي فتكلّف عشر منها 5 ثوانٍ لا 50، وتُدرج المحاولات الأخيرة في صفحة تلك النقطة مع رمز الاستجابة والمدة التي استغرقتها.
  • تُحاوَل عملية التسليم حتى خمس مرات. تخرج الأولى لحظة وقوع الحدث؛ والفشل الذي يُحتمل أن يزول من تلقاء نفسه يُعاد بعد دقيقة واحدة، ثم 5، ثم 25، ثم ساعتين، ما يوزّع حدثًا واحدًا على نحو ساعتين ونصف. وتُحفظ إعادات المحاولة عملًا دائمًا لا في الذاكرة، فلا يضيّعها نشر يقع في منتصف تلك النافذة. ولا يُعاد إلا الفشل الذي يستحق التكرار: انتهاء مهلة، أو اتصال مرفوض، أو 408 أو 425 أو 429 أو أي 5xx. وأي 4xx آخر هو رفض متعمَّد من نقطة النهاية للحمولة، والسؤال أربع مرات إضافية سيكون أربعة أضعاف الحِمل للجواب نفسه. ويُصكّ معرّف الحدث مرة واحدة وتحمله كل محاولة في X-OpenEmail-Delivery، فالمستقبِل الذي يرى المعرّف نفسه مرتين يستطيع إسقاط الثانية بدل التصرف بناءً عليها مرتين. وبعد فشل 100 حدث متتالٍ في كل محاولاتها، تُعطَّل نقطة النهاية، وتُراسَل مساحة العمل بالبريد، ويكون السبب مقروءًا على نقطة النهاية نفسها. أما نقطة النهاية التي تجيب بـ 410 Gone فتُعطَّل في الحال.
  • نقطة النهاية التي تفشل 100 مرة متتالية تُطفأ بدل الاتصال بها إلى الأبد، ويُراسَل بالبريد كل من لديه وصول إلى webhooks لإبلاغه: أي نقطة، وماذا أفادت آخر محاولة، وأن شيئًا لم يُوضع في الطابور أثناء فشلها. والعدّ متتالٍ، وأي محاولة مُسلَّمة تصفّره، فلا يمكن لظهيرة سيئة في مارس الماضي أن تتراكم حتى تعطّل نقطة نهاية اليوم. وإعادة تشغيلها تمسح العدّ معها. وتميّز وحدة التحكم بين الحالتين بدل عرض مفتاح واحد: فنقطة أوقفتها أنت تبدو مختلفة عن نقطة أوقفناها نحن.
  • إدارة نقاط النهاية مهمة واحدة لها بابان. عبر API هي POST /webhooks والتعديل والحذف وتدوير السر والاختبار وسجل التسليم، ولكل منها طريقة في SDK؛ وفي التطبيق هي Settings → Webhooks، مقابل السجل نفسه لا سجل ثانٍ. والقراءة محكومة بـ webhooks:read، فيستطيع أي شخص يبني تكاملًا أن يرى نقاط النهاية وتاريخ تسليمها (أيها انطلق، وبماذا أجاب المستقبِل، وكم استغرق) من دون أن يكون المالك. أما التسجيل والتحرير والاختبار والتدوير والحذف فتحتاج إلى webhooks:write وإلى ملكية صندوق البريد معًا، على الواجهتين، وهذا الشطر الثاني متعمَّد: فليس لنقطة النهاية محور عناوين، فهي تستقبل كل عنوان تملكه مساحة العمل بموضوعاته ومستلميه، ولا يوجد إذن يعني «يجوز إرسال كل ذلك إليه». والدور الذي يبني التكاملات ولا يقرأ البريد يشغّلها بمفتاح مساحة عمل بدلًا من ذلك.