يحتوي النواة (Core) على نقطة نهاية (endpoint) عاملة لمعالجة الارتداد (bounce) الخاص بـ 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.
كنت أحاول إعداد هذا الأمر لكن واجهت بعض المشكلات التي أعتقد أنها تستحق المشاركة في حال كانت مفيدة للآخرين. أعلم أن هذا استخدام نادر، حيث توقف Mandrill عن تقديم خدمة “SMTP فقط” منذ أكثر من عقد من الزمان، ولا يوفر خدمة SMTP الآن إلا كإضافة إلى منتج البريد الإلكتروني المعاملاتي (transactional email) الخاص بـ Mailchimp. ومع ذلك، كان لدى أحد عملائي هذا الإعداد بالضبط، لذا ها نحن هنا!
1. لا يوجد توثيق لها في أي مكان يمكنني العثور عليه. لا يذكر موضوع VERP هذا الأمر، وهو أمر معقول، لأن VERP ليس هو الطريقة التي يعمل بها Mandrill: فوثائق Mailchimp نفسها تقول إنه يتعامل مع الارتدادات بنفسه حتى عند إعداد نطاق Return-Path مخصص، وبالتالي لا تصل الارتدادات لكل رسالة إلى مستلهم بريد إلكتروني ذاتي الاستضافة (self-hosted mail-receiver). نقاط النهاية الخاصة بالمزود (provider 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 بنفس الطريقة:
{"status":"error","code":-98,"name":"ValidationError","message":"Unable to validate webhook URL"}
سجل المصدر لمحاولة عبر واجهة المستخدم:
"POST /webhooks/mandrill HTTP/2.0" "Mandrill-Webhook/1.0" 406
المزودون الآخرون لديهم رموز على مستوى الحساب، مما يتيح للمشرف تعيين سر (secret) عشوائي قبل إنشاء الـ Webhook. مفتاح Mandrill يصدر لكل Webhook على حدة، فقط بعد حفظ الـ Webhook - لذا فهي معضلة (Catch-22).
أنا متأكد إلى حد كبير من أن التدفق يمكن إصلاحه، ربما بالرد بـ 200 على أول تحقق (الذي لا يحتوي على حمولة mandrill_events)، مما يسمح بالإنشاء في Mandrill، ثم يمكن نسخ السر إلى Discourse يدويًا، وسنكون على ما يرام. يسعدني فتح طلب سحب (PR) إذا كان ذلك مقبولاً.
3. حل بديل، إذا احتاجه أي شخص اليوم. أنشئ الـ Webhook مقابل عنوان URL يعيد بالفعل 200، بدون محفزات (حمولات الارتداد تحتوي على عناوين المستلمين، لذا لا تقم أبدًا بتوجيه 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.