إضافة Discourse Bounce Guard

المستودع: GitHub - overgrow/discourse-bounce-guard · GitHub
الرخصة: MIT

&tldr; ماذا ستحصل عليه: سجل أخطاء أنظف (تتوقف إعادة المحاولات الساعة إلى العناوين الميتة). يتوقف Discourse عن إرسال البريد الإلكتروني الذي لا يمكن توصيله. ومستخدمون أكثر سعادة: يمكنهم العودة إلى حساباتهم، لأن العنوان الميت يُستبدل بعنوان يعمل في الوقت المناسب.

إذا كان البريد الصادر يمر عبر وسيط خاص بك (Postfix، Exim، معظم الإعدادات ذاتية الاستضافة)، فمن المحتمل أنك رأيت /logs تمتلئ بأزواج مثل هذا:

SMTP Error Net::SMTPServerBusy with message: 450 4.1.2 <someone@gone-domain.com>: Recipient address rejected: Domain not found
Job exception: Net::SMTPServerBusy

النطاق قد اختفى ولن يعود. لكن الوسيط يرد برمز خطأ مؤقت (450)، لأن البحث في DNS قد يفشل مؤقتاً نظرياً. يتعامل Discourse مع أي خطأ مؤقت على أنه “أعد المحاولة بعد ساعة”، ويواصل Sidekiq المحاولة لأسابيع. لا يعمل كشف الارتداد (Bounce Detection) في النواة أبداً. فهو يستجيب فقط عندما يصل رسالة ارتداد، إما كبريد إلكتروني (VERP) أو كـ webhook من مزود البريد الخاص بك، وهنا يُرفض البريد في اللحظة، لذا لا توجد رسالة ارتداد أبداً. يبقى مؤشر الارتداد عند الصفر، وتستمر إعادة المحاولة، ويحتفظ المستخدم بعنوان لا يمكنه استلام أي شيء، بما في ذلك إعادة تعيين كلمة المرور.

تم طرح هذا الموضوع هنا عدة مرات دون إجابة مدمجة، على سبيل المثال معالجة رسائل البريد إلى نطاق غير موجود، تعطيل المستخدم مع ارتداد قاسٍ و كيف تعطل حسابات المستخدمين الذين لا يستلمون رسائل البريد. عانينا من نفس المشكلة في منتدانا، لذا بنينا إضافة (Plugin).

ما تفعله

يتصل Bounce Guard بمسار الإرسال نفسه ويصنف كل رفض SMTP:

  • تُحتسب الردود 5xx مع حالة محسّنة لمستلم سيئ (5.1.1، 5.1.2، 5.2.1 وأقرانها) كفشل قاسٍ.
  • أي رد يطابق قائمة عبارات قابلة للضبط (“النطاق غير موجود”، “المستخدم غير معروف”، …) يُحتسب كفشل قاسٍ بغض النظر عن الرمز. هذا يلتقط الوسائط التي تفشل بشكل مؤقت في الحالات الدائمة مع 450.
  • تُترك الرمادية (Greylisting) والبريد الممتلئ وحدود المعدل على حالها. سلوك إعادة المحاولة في النواة غير مساس.

ثم تتسلق الفاشلات القاسية المسجلة سلّم التصعيد:

  1. كل فشل يغذي مؤشر الارتداد في النواة كارتداد قاسٍ (مفعّل افتراضياً). بعد فشلين، يعمل عتبة النواة الخاصة بها ويتوقف Discourse عن إرسال البريد إلى المستخدم. ينتهي ضجيج السجل هنا، حتى لو لم تفعّل أي شيء إضافي أبداً.
  2. بعد عدد قابل للضبط (افتراضياً 3) موزع على فترة قابلة للضبط (افتراضياً 48 ساعة، حتى لا يخرج أي شخص بسبب انقطاع قصير)، تتصرف الإضافة. الإجراء الافتراضي هو log_only: إدخال في سجل إجراءات الموظفين يسجل أن المستخدم كان سيُعطَّل. بدّل إلى deactivate عندما تثق به.
  3. يعيد التعطيل توجيه المستخدم عبر تدفق التفعيل القياسي عند تسجيل الدخول التالي، حيث يتم بناء تغيير العنوان فيه. يتحقق من بريد إلكتروني يعمل ويواصل بحسابه سليماً. النقطة هي القابلية للاسترداد: حساب نشط يكون قناة استرداده الوحيدة صندوق بريد ميت هو قفل ينتظر أن يحدث.

إذا كان موقعك يستلم الارتدادات (VERP أو webhooks)، يمكنك أيضاً السماح للإضافة بالتصرف عندما يتجاوز مؤشر الارتداد في النواة مستوى تختاره، أعلى من الدرجة التي يتوقف عندها النواة عن الإرسال. تنطبق قواعد أمان قليلة في كل مكان. لا يُلمس الموظفون والروبوتات أبداً. بريد واحد عالق يعيد المحاولة كل ساعة يُحتسب كفشل واحد، بفضل نافذة برودة. لا يُحتسب الفشل ضد مستخدم إلا عندما يكون العنوان الذي رفضه الخادم هو العنوان الحالي لذلك المستخدم. وتُحفظ كل الفاشلات المسجلة لمدة 90 يوماً حتى تتمكن من التحقق مما حدث (استعلام Data Explorer في README).

التثبيت

تثبيت الإضافة القياسي:

hooks:
  after_code:
    - exec:
        cd: $home/plugins
        cmd:
          - git clone https://github.com/discourse/docker_manager.git
          - git clone https://github.com/overgrow/discourse-bounce-guard.git

فعّل bounce_guard_enabled، واترك bounce_guard_action على log_only لمدة أسبوع أو أسبوعين، راجع ما يميزه، ثم قرر بشأن التعطيل. مرجع الإعدادات موجود في README.

جانب الخادم فقط، لا مكونات سمة أو JS. تم بناؤه واختباره ضد النواة الحالية (2026.8)، 31 مواصفة، وتشغل CI سير عمل discourse-plugin القياسي. نرحب بالملاحظات، خاصة عبارات الرفض من وسائط أخرى تفقدها قائمة العبارات الافتراضية.