معالجة الارتدادات في Mandrill: مدعومة في النواة لكن غير موثقة (حسب ما يبدو لي)

يحتوي النواة (Core) على نقطة نهاية (endpoint) عاملة لارتدادات Mandrill - POST /webhooks/mandrill داخل WebhooksController - التي تعالج أحداث hard_bounce و soft_bounce، وتطبق hard_bounce_score / soft_bounce_score، وتعطل العناوين التي تتجاوز bounce_score_threshold. يعتمد التطابق على ترويسة message_id في X-MC-Metadata التي يضيفها Email::Sender عندما يكون DISCOURSE_SMTP_ADDRESS مطابقًا تمامًا لـ smtp.mandrillapp.com.

كنت أحاول إعداد هذا النظام لكنني واجهت بعض المشكلات التي أعتقد أنها تستحق المشاركة في حال كانت مفيدة للآخرين. أعلم أنها حالة استخدام متخصصة (niche use-case)، لأن Mandrill توقفت عن تقديم خدمة “SMTP فقط” منذ أكثر من عقد من الزمان، ولا تقدم الآن خدمة SMTP إلا كإضافة (add-on) لمنتج البريد الإلكتروني المعاملاتي (transactional email) الخاص بـ Mailchimp. ومع ذلك، كان لدى أحد عملائي هذا الإعداد بالضبط، لذا ها نحن هنا!

1. لا يوجد توثيق لها في أي مكان يمكنني العثور عليه. موضوع VERP لا يذكرها، وهو أمر معقول، لأن VERP ليس هو الطريقة التي تعمل بها Mandrill: توثيقات Mailchimp الخاصة بها تقول إنها تعالج الارتدادات بنفسها حتى عند إعداد نطاق Return-Path مخصص، لذا فإن ارتدادات الرسائل الفردية لا تصل أبدًا إلى مستلم بريد ذاتي الاستضافة. نقاط نهاية Webhooks المزود هي المسار الوحيد، ولا يوجد مؤشر واضح عليها لأي شخص يبدأ من موضوع VERP.

2. لا يمكن إنشاء Webhook فعليًا. يعيد WebhooksController#mandrill استجابة 406 دائمًا عندما تكون mandrill_authentication_key فارغة. هذا السلوك مقصود - فهو الإصلاح الخاص بـ CVE-2026-26077 (Discourse 2025.12.2 / 2026.1.1 / 2026.2.0)، الذي أغلق إمكانية تزوير حمولات الارتداد (bounce payloads) غير المصادق عليها عبر نقاط نهاية SendGrid، Mailjet، Mandrill، Postmark، SparkPost و Mailpace. لذا، هذا ليس طلبًا لتخفيف القيود؛ بل إن الإصلاح ترك Mandrill دون أي وسيلة للتبويب الأولي (bootstrap).

يقوم Mandrill بتحقق من Webhook جديد عبر طلب POST (وليس HEAD الذي كُتب من أجله mandrill_head)، فيرى الاستجابة 406، ويرفض حفظ الـ Webhook - لذا فإن المفتاح الذي تحتاجه لا يمكن عرضه أبدًا. تفشل المسار البرمجي (API path) بنفس الطريقة:

{"status":"error","code":-98,"name":"ValidationError","message":"Unable to validate webhook URL"}

سجل المصدر لمحاولة عبر الواجهة الرسومية (UI):

"POST /webhooks/mandrill HTTP/2.0" "Mandrill-Webhook/1.0" 406

المزودون الآخرون لديهم رموز (tokens) على مستوى الحساب، مما يسمح للمشرف بإعداد سر (secret) عشوائي قبل إنشاء الـ Webhook. مفتاح Mandrill يُصدر لكل Webhook على حدة، فقط بعد حفظ الـ Webhook - لذا فهو مأزق (Catch-22).

متأكد إلى حد كبير من أن التدفق يمكن إصلاحه، ربما بالرد بـ 200 على التحقق الأول (الذي لا يحتوي على حمولة mandrill_events)، مما يسمح بالإنشاء في Mandrill، ثم يمكن نسخ السر يدويًا إلى Discourse، ونكون قد انتهينا. يسعدني فتح طلب دمج (PR) إذا كان ذلك مقبولاً.

3. حل بديل (Workaround)، إذا احتاج إليه أحد اليوم. أنشئ الـ Webhook مقابل عنوان URL يعيد بالفعل 200، بدون أي محفزات (تحتوي حمولات الارتداد على عناوين المستلمين، لذا لا توجه أبدًا Webhook مفعّل المحفزات إلى عنوان بديل)، ثم ثبّت المفتاح في Discourse، وبعد اكتمال ذلك انقل عنوان URL الخاص بالـ Webhook:

# 1. أنشئ مقابل عنوان بديل، بدون أحداث
curl -sS -X POST https://mandrillapp.com/api/1.0/webhooks/add.json \
  -H 'Content-Type: application/json' \
  -d '{"key":"<api-key>","url":"https://httpbin.org/status/200","description":"Discourse bounce handling","events":[]}'

# 2. ضع مفتاح المصادقة (auth_key) المُعاد في إعداد الموقع mandrill_authentication_key

# 3. أعد توجيهه إلى المنتدى وفعّل المحفزات - هذا سيمر الآن التحقق
curl -sS -X POST https://mandrillapp.com/api/1.0/webhooks/update.json \
  -H 'Content-Type: application/json' \
  -d '{"key":"<api-key>","id":<id>,"url":"https://<forum>/webhooks/mandrill","description":"Discourse bounce handling","events":["hard_bounce","soft_bounce"]}'

تم الاختبار على 2026.9.0-latest.