تفتح شاشة إعداد البريد في الطابعة التي تمسح المستندات Scanner، أو في نظام الموارد البشرية، أو في Gitea وGrafana، فتجد حقلاً اسمه «SMTP Server» وبجانبه أحياناً «SMTP Relay» أو «Smarthost»، ثم تقرأ في دليل Postfix عن relayhost وmynetworks، وفي وثائق Microsoft 365 عن «SMTP relay» و«Direct Send»، فيبدو أن كل هذه الكلمات أسماء لشيء واحد. والسؤال الذي يصلنا كثيراً: ما الفرق بين SMTP والترحيل Relay، ولماذا أحتاج خادماً وسيطاً Smarthost أصلاً، ومتى يكون وجوده خطراً؟ وقد شرحنا الطريق الكامل للرسالة في مقال كيف يعمل البريد الإلكتروني، وهذا المقال يكمله من زاوية واحدة هي الترحيل Relay، ويجمع ما تفرق عنه في مقالات البريد على الموقع في مكان واحد.
وسوف نناقش في هذا المقال ما يلي:
- الفرق بين البروتوكول SMTP والدور الذي اسمه الترحيل Relay، وأين يقع كل منهما في طريق الرسالة.
- لماذا تحتاج خادماً وسيطاً Smarthost: حجب المنفذ 25 عند مزودي السحابة بتواريخ صفحاتهم الرسمية، والسمعة، والمخرج الواحد، والطابور.
- ستة أشكال للترحيل Relay تقابلها في العمل، ومنها الطابعات والتطبيقات القديمة مع Microsoft 365 وGoogle Workspace.
- مختبر كامل على جهازك: مرحل داخلي Internal Relay يقبل التطبيقات والطابعة بعنوان IP، ويقبل الموظف بتسجيل الدخول، ويرسل كل شيء عبر خادم وسيط Smarthost يطلب تسجيل الدخول، ثم نختبره من شبكة خارجية، ثم نكسره بإعداد
mynetworksواسع. - ماذا يحدث لـ SPF وDKIM وDMARC عندما تمر الرسالة بخادم وسيط Smarthost، كما تظهر في ترويسات Headers الرسالة نفسها.
البروتوكول هو SMTP، والترحيل Relay دور يقوم به السيرفر
بروتوكول نقل البريد البسيط Simple Mail Transfer Protocol، واختصاراً SMTP، هو اللغة التي يتكلم بها طرفان لنقل رسالة: EHLO ثم MAIL FROM ثم RCPT TO ثم DATA، وهذه المحادثة نفسها تجري على كل سهم في طريق الرسالة، سواء كان المرسل برنامج بريد أو طابعة أو سيرفراً. أما الترحيل Relay فليس بروتوكولاً آخر، وإنما وصف لما يفعله السيرفر بالرسالة بعد أن يستلمها، فإذا كانت لنطاق يملكه وضعها في الصندوق، وإذا كانت لنطاق غيره وقبلها ثم أرسلها إلى سيرفر آخر فهو يقوم بدور المرحل Relay في هذه الرسالة. وهذا ليس تعريفنا، فالمعيار RFC 5321 في القسم 2.3.10 يقسم أنظمة SMTP بحسب دورها إلى نظام يبدأ الرسالة Originating، ونظام تسليم Delivery يضعها في الصندوق، ومرحل Relay يستلم الرسالة من عميل SMTP وينقلها دون أن يغير محتواها، إلا أنه يضيف سطور التتبع Trace مثل Received، إلى سيرفر SMTP آخر. والصورة التالية تبين ذلك:

وفي الصورة أعلاه لاحظ التالي:
- السيرفر نفسه قد يقوم بأكثر من دور، فالمرحل الداخلي Internal Relay
relay.corp.testهو بوابة الاستلام من المستخدمين Mail Submission Agent، واختصاراً MSA، على المنفذ 587، وهو مرحل Relay للتطبيقات والطابعات على المنفذ 25، وقد شرحنا الأدوار MUA وMSA وMTA وMDA في قسم طريق الرسالة فلن نعيدها. - سيرفر المستلم
mx.inbox.testليس مرحلاً Relay، لأنه يقبل البريد لنطاقه فقط، وهذا بالضبط ما يجعل الرسالة إلى نطاق غيره ترفض عنده بالردRelay access denied. - الكلمة نفسها تظهر في سجلات Postfix في كل سطر تسليم، مثل
relay=smtp.sendco.test[10.88.50.20]:587، ومعناها هنا «السيرفر التالي الذي سلمت له الرسالة»، وليس أن السيرفر مرحل مفتوح Open Relay يرحل لأي شخص.
وقد يتساءل البعض: ما الفرق إذن بين بوابة الاستلام MSA والمرحل Relay؟ والإجابة أن المعيار RFC 6409 يفصل بينهما في الغرض وليس في البروتوكول، فبوابة الاستلام تستقبل رسالة جديدة من برنامج البريد، ويجب أن ترفض الأمر MAIL افتراضياً ما لم يسجل العميل دخوله أو يكون داخل شبكة محمية Protected Subnetwork كما في القسم 4.3، ويسمح لها المعيار بأن تصلح الرسالة قبل أن ترسلها، مثل إضافة Date أو Message-ID الناقصين. أما المرحل Relay فيستلم رسالة جاهزة وينقلها كما هي. وفي الواقع ترى الاثنين في سيرفر واحد، والمهم أن تعرف أي باب يفتح لمن، وهذا موضوع بقية المقال.
لماذا تحتاج خادماً وسيطاً Smarthost أصلاً؟
الحل البسيط أن يتصل كل تطبيق وكل طابعة مباشرة بسيرفر المستلم على المنفذ 25، كما يفعل أي MTA، وهذا الحل يعمل في المختبر ويفشل في الإنتاج لأسباب لا علاقة لها بجودة الكود، وفيما يلي أهمها:
- المنفذ 25 محجوب: أغلب مزودي السحابة يحجبون الاتصال الصادر على المنفذ 25، وتفصيل كل مزود في الجدول التالي، والسبب أن هذا المنفذ لا يحتاج تسجيل دخول كما شرحنا في قسم حجب المنفذ 25.
- السمعة Reputation وسجل PTR: عنوان IP جديد لسيرفر تطبيق ليس له سمعة عند Gmail وOutlook، وغالباً ليس له سجل PTR يطابق اسمه، فتتأخر رسائله أو تصل إلى Spam، وقد فصلنا ذلك في دليل الإرسال عبر Amazon SES.
- مخرج واحد Single Exit: عشرون تطبيقاً وثلاثون طابعة ترسل كلها من سيرفر واحد، فتوقع رسائلها بمفتاح DKIM واحد، ويكون عنوان مخرج واحد هو الذي يحتاجه سجل SPF، وتطبق عليه حدود المعدل Rate Limits وتسجيل الرسائل Logging في مكان واحد، وكلمة مرور المزود الخارجي محفوظة في سيرفر واحد بدل خمسين جهازاً.
- الطابور وإعادة المحاولة Queue and Retry: يقول RFC 5321 في القسم 2.1 إن عميل SMTP الذي لا يحتفظ بطابور يعيد منه المحاولة ليس «كامل القدرات» Fully-capable، وإن المرحل Relay الذي يستخدمه هذا العميل هو الذي يجب أن يقوم بالطابور وإعادة المحاولة. وأغلب الطابعات لا تملك طابوراً، فإذا رد السيرفر البعيد بـ
421ضاع المستند، أما المرحل Relay فيحتفظ به ويعيد المحاولة، وقد شرحنا الطابور في قسم الطابور وإعادة المحاولة. - الفحص والامتثال Compliance: بعض الجهات تشترط أن يمر كل بريد خارج منها ببوابة واحدة تفحصه وتحفظ نسخة منه، وهذا مستحيل إذا كانت الأجهزة ترسل مباشرة.
وبالطبع لا تحتاج خادماً وسيطاً Smarthost في كل حالة، فإذا كان لديك سيرفر بريد واحد، ومنفذه 25 مفتوح، وعنوانه نظيف وله PTR، فالإرسال المباشر أبسط كما ذكرنا في نهاية قسم لماذا ترسل عبر خادم وسيط Smarthost، وإذا كنت تكتب كود تطبيقك بنفسك فقد يكون الإرسال عبر HTTP API إلى خدمة الإرسال أنسب من SMTP كما في مقارنة SMTP وHTTP API.
من يحجب المنفذ 25، ومن يحجب غيره؟
الجدول التالي من الصفحات الرسمية لكل مزود كما اطلعنا عليها في 12 أكتوبر 2026، والتاريخ في العمود الأخير هو تاريخ التحديث المكتوب في الصفحة نفسها، وهذه السياسات تتغير، فارجع إلى الصفحة قبل أن تشتري:
| المزود | ما يحجبه افتراضياً | هل يمكن فتحه؟ | ما يوصي به | تحديث الصفحة |
|---|---|---|---|---|
| AWS | الصادر على 25 لكل EC2 وLambda على واجهات الشبكة | بطلب يقدم لكل منطقة Region على حدة | طلب رفع القيد مع وصف الاستخدام | 21 يوليو 2025 |
| Microsoft Azure | الصادر على 25 للأجهزة الافتراضية في أغلب أنواع الاشتراكات | لا يحجبه في اشتراكات Enterprise Agreement وMCA-E أصلاً | خدمة ترحيل Relay بتسجيل دخول على 587، ولا يقيد Azure هذا المنفذ | 16 يوليو 2026 |
| Google Cloud | الصادر على 25 إلى خارج شبكة VPC الخاصة بك | بعض المشاريع لا يطبق عليها الحجب | المنفذان 587 و465 دون أي قيد | 9 أكتوبر 2026 |
| DigitalOcean | المنافذ 25 و465 و587 معاً، على كل خوادم Droplet دون استثناء | لا تذكر الصفحة طريقة لفتحها | مزود خارجي لإرسال البريد | 13 يوليو 2026 |
| Hetzner Cloud | 25 و465 | بطلب بعد شهر من التسجيل ودفع أول فاتورة | المنفذ 587 مفتوح للإرسال عبر خدمة خارجية | لا يوجد تاريخ في الصفحة |
ولاحظ سطر DigitalOcean، فالحجب هناك يشمل منافذ الإرسال 465 و587 أيضاً، وبالتالي لا يحل الخادم الوسيط Smarthost على هذه المنافذ المشكلة من Droplet، والحل هو الإرسال عبر HTTP API لخدمة الإرسال على المنفذ 443. وفي Azure لاحظ أن التوصية الرسمية نفسها هي «خدمة ترحيل Relay بتسجيل دخول» Authenticated SMTP Relay، أي الشكل الرابع في القسم التالي.
ستة أشكال للترحيل Relay تقابلها في العمل
كلمة Relay في شاشات الإعداد تعني أشياء مختلفة بحسب مكان السيرفر ومن يقبل منه، والصورة التالية تجمع الأشكال الستة التي سوف تقابلها، والأحمر منها وحده هو الذي لا تريده أبداً:

- المرحل الداخلي Internal Relay: سيرفر داخل الشبكة يقبل على المنفذ 25 دون تسجيل دخول من عناوين IP معروفة فقط، مثل شبكة التطبيقات وعنوان الطابعة، والسبب أن كثيراً من الطابعات والأنظمة القديمة لا تعرف تسجيل الدخول ولا TLS حديثاً. وهذا ما تسميه Microsoft «Anonymous Relay» وتفرق بينه وبين المرحل المفتوح Open Relay، وهو قلب المختبر في هذا المقال.
- الاستلام بتسجيل الدخول Authenticated Submission: المنفذ 587 أو 465 لمن هو خارج الشبكات الموثوقة، مثل حاسوب الموظف في بيته أو تطبيق مستضاف عند طرف آخر، فيسجل دخوله أولاً ثم يرسل إلى أي مكان، وقد شرحنا المنافذ والتشفير في قسم المنافذ، وإعداد المنفذ 587 على سيرفر حقيقي في دليل Postfix وDovecot.
- المرحل المفتوح Open Relay: سيرفر يقبل من أي شخص رسالة إلى أي مكان، وهو خطأ إعداد دائماً، والاختبار الذي يكشفه رأيناه في قسم أرقام الردود، وسوف نصنعه بأيدينا في قسم الكسر لنرى ما الذي يخسره صاحبه بالضبط.
- الخادم الوسيط للصادر Outbound Smarthost: السيرفر الذي يرسل عبره سيرفرك كل بريده الخارج بدل الاتصال بسجلات MX مباشرة، مثل Amazon SES أو مزود إرسال أو بوابة المقر الرئيسي، والإعداد في Postfix هو
relayhostكما في دليل SES، وهو أيضاً أحد الحلول الوسط في دليل استضافة بريد المؤسسة عندما تستقبل على سيرفرك وترسل عبر غيره. - المرحل الوارد Inbound Relay: سجل MX لنطاقك يشير إلى بوابة أمن البريد Mail Gateway، فتستلم من الإنترنت وتفحص ثم تمرر النظيف إلى خادمك الداخلي، ومثله سجل MX الاحتياطي Backup MX، وقد شرحنا مكان البوابة في مقارنة بوابات أمن البريد، وربطها بالخادم الداخلي في دليل Proxmox Mail Gateway.
- الترحيل Relay نيابة عن عميل: منصة SaaS ترسل رسائل عميلها عبر بوابة بريد العميل نفسه، فتبقى سجلات SPF وDKIM للعميل كما هي، وهذا موضوع كامل في مقال تكامل منصة SaaS مع أنظمة العميل.
والطابعات عندما يكون البريد عند Microsoft أو Google، كيف ترسل؟
هذا هو السؤال الأكثر تكراراً، فالبريد عند Microsoft أو Google والطابعة في المكتب، فكيف ترسل؟ وكل منهما يقدم الأشكال السابقة نفسها بأسماء مختلفة. ففي صفحة Microsoft لإرسال البريد من الأجهزة والتطبيقات، المحدثة في 16 سبتمبر 2026، أربع طرق نلخص الثلاث المهمة منها:
| الطريقة كما تسميها Microsoft في صفحتها | المقابل في هذا المقال | المنفذ | كيف يتحقق من المرسل | إلى خارج المؤسسة؟ |
|---|---|---|---|---|
| Client SMTP submission | الاستلام بتسجيل الدخول | 587 (أو 25) | صندوق بريد مرخص وكلمة مروره، مع TLS 1.2 أو أحدث | نعم |
| SMTP relay | مرحل Relay يقبل بعنوان IP أو بشهادة | 25 | موصل وارد Inbound Connector بعنوان IP ثابت غير مشترك أو بشهادة TLS خاصة بنطاقك | نعم |
| Direct Send | ليس ترحيلاً Relay، وإنما تسليم مباشر إلى سيرفر MX للنطاق | 25 | لا يوجد | لا، للمستلمين داخل المؤسسة فقط |
وفي الصفحة نفسها ملاحظتان يجب أن تعرفهما قبل أن تختار: الأولى أن Client SMTP submission بتسجيل الدخول الأساسي Basic Authentication مجدول للإيقاف في Exchange Online، والثانية أن SMTP relay لا يصلح لإرسال بريد من خدمة مستضافة عند طرف ثالث مثل Azure، وأن الطابعة التي لا تدعم TLS 1.2 لا تستطيع استخدام الطريقة الأولى أصلاً. لذلك تقول Microsoft نفسها إنك إذا كان لديك سيرفر بريد داخلي فالأسهل أن تجعله مرحلاً Relay للأجهزة، وتسميه «Anonymous Relay (not open relay)»، وهذا بالضبط المرحل الداخلي Internal Relay الذي سوف نبنيه. وعند Google يوجد SMTP relay service على smtp-relay.gmail.com، ويقبل بعناوين IP محددة أو بتسجيل الدخول، ويسمح لك بأن تحدد من يرسل عبره: مستخدمو نطاقك المسجلون، أو أي عنوان من نطاقاتك، أو أي عنوان، والأخير تصفه Google نفسها بأنه غير مستحسن، والسبب أنه يقترب من المرحل المفتوح Open Relay لكل جهاز في قائمة عناوينك.
المختبر: مرحل داخلي Internal Relay أمام مزود الإرسال
الشرح النظري للترحيل Relay لا يكفي لكي تثق بإعدادك، والأفضل أن ترى كل رد بعينك، لذلك سوف نبني شركة صغيرة اسمها corp.test فيها مرحل داخلي Internal Relay، ومزود إرسال اسمه sendco.test يقوم بدور ال Smarthost، وسيرفر مستلم inbox.test يفحص SPF وDKIM وDMARC، وفي آخر الطريق Mailpit يستلم كل رسالة ولا يرسلها لأحد. والصورة التالية تبين المختبر:

والإصدارات المستخدمة: Postfix 3.10.13 وOpenDKIM 2.11.0 وCyrus SASL 2.1.28 من حزم Debian 13.4، وCoreDNS 1.14.7، وRspamd 4.2.2، وMailpit 1.31.5، وswaks 20240103.0 على Ubuntu 24.04، مع Docker Engine 29.8.0 وDocker Compose 5.5.1. وكل الشبكات من النوع internal، فلا يوجد أي طريق من المختبر إلى الإنترنت، ولا يوجد أي منفذ منشور على جهازك، وبالتالي لا تخرج رسالة واحدة حتى لو أخطأت في عنوان.
ملفات المختبر
نبدأ بالملف compose.yaml:
# Lab: an internal SMTP relay for a company (corp.test) in front of a sending provider (sendco.test).
# Every network is internal: no container can reach the Internet, and no port is published on the host.
name: lab-relay
x-tools: &tools
image: lab-relay-tools
command: ["sleep", "infinity"]
networks:
apps: { name: lab-relay-apps, internal: true, ipam: { config: [ { subnet: 10.88.10.0/24 } ] } }
devices: { name: lab-relay-devices, internal: true, ipam: { config: [ { subnet: 10.88.20.0/24 } ] } }
guest: { name: lab-relay-guest, internal: true, ipam: { config: [ { subnet: 10.88.40.0/24 } ] } }
wan: { name: lab-relay-wan, internal: true, ipam: { config: [ { subnet: 10.88.50.0/24 } ] } }
services:
# --- the company ----------------------------------------------------------------
relay:
build: ./mta
image: lab-relay-mta
container_name: lab-relay-relay
hostname: relay.corp.test
command: ["sh", "/start.sh"]
dns: [10.88.50.53]
volumes: ["./relay/start.sh:/start.sh:ro", "./run:/run/lab:ro"]
networks:
apps: { ipv4_address: 10.88.10.10 }
devices: { ipv4_address: 10.88.20.10 }
guest: { ipv4_address: 10.88.40.10 }
wan: { ipv4_address: 10.88.50.10 }
depends_on: [dns, smarthost]
app: # an application server in the apps subnet
<<: *tools
container_name: lab-relay-app
networks: { apps: { ipv4_address: 10.88.10.21 } }
printer: # a scanner that knows SMTP on port 25 without login
<<: *tools
container_name: lab-relay-printer
networks: { devices: { ipv4_address: 10.88.20.31 } }
rogue: # another device in the same subnet as the printer
<<: *tools
container_name: lab-relay-rogue
networks: { devices: { ipv4_address: 10.88.20.66 } }
laptop: # an employee outside the trusted networks: logs in on 587
<<: *tools
container_name: lab-relay-laptop
networks: { guest: { ipv4_address: 10.88.40.40 } }
stranger: # anyone else who can reach the relay (guest Wi-Fi, VPN, a mistaken port forward)
<<: *tools
container_name: lab-relay-stranger
networks: { guest: { ipv4_address: 10.88.40.66 } }
# --- outside world ----------------------------------------------------------------
dns:
image: coredns/coredns:1.14.7
container_name: lab-relay-dns
command: ["-conf", "/etc/coredns/Corefile"]
volumes: ["./dns/Corefile:/etc/coredns/Corefile:ro", "./dns/zones:/zones:ro"]
networks: { wan: { ipv4_address: 10.88.50.53 } }
smarthost:
image: lab-relay-mta
container_name: lab-relay-smarthost
hostname: smtp.sendco.test
command: ["sh", "/start.sh"]
dns: [10.88.50.53]
volumes: ["./smarthost/start.sh:/start.sh:ro", "./run:/run/lab:ro"]
networks: { wan: { ipv4_address: 10.88.50.20 } }
depends_on: [dns]
rx:
image: lab-relay-mta
container_name: lab-relay-rx
hostname: mx.inbox.test
command: ["sh", "/start.sh"]
volumes: ["./rx/start.sh:/start.sh:ro"]
networks: { wan: { ipv4_address: 10.88.50.30 } }
depends_on: [rspamd, mailpit]
rspamd:
image: rspamd/rspamd:4.2.2
container_name: lab-relay-rspamd
volumes: ["./rspamd/local.d:/etc/rspamd/local.d:ro", "./rspamd/override.d:/etc/rspamd/override.d:ro"]
networks: { wan: { ipv4_address: 10.88.50.31 } }
mailpit:
image: axllent/mailpit:v1.31.5
container_name: lab-relay-mailpit
networks: { wan: { ipv4_address: 10.88.50.32 } }في الإعداد أعلاه لاحظ التالي:
- المرحل Relay
lab-relay-relayهو الوحيد المتصل بالشبكات الأربع، كما يحدث في شركة حقيقية حين يكون للمرحل Relay واجهة في شبكة السيرفرات، وواجهة في شبكة الأجهزة، ويصل إليه أيضاً من في شبكة الضيوف أو VPN، وواجهة إلى الخارج. - الطابعة
.31والجهاز.66في الشبكة نفسها، والفرق بينهما في قائمة الثقة فقط كما سيأتي. - الأجهزة الخمسة من نوع واحد
lab-relay-tools، وهو Ubuntu 24.04 مع swaks وحزم TLS وSASL التي يحتاجها كما شرحنا في مختبر مقال كيف يعمل البريد.
ال Docker Image الخاصة بالسيرفرات الثلاثة واحدة، والملف mta/Dockerfile كما يلي:
# Postfix with Cyrus SASL and OpenDKIM: the internal relay, the provider's smarthost and the recipient's MX
FROM debian:13.4-slim
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
postfix opendkim sasl2-bin libsasl2-modules ssl-cert ca-certificates curl \
&& rm -rf /var/lib/apt/lists/*والإعداد المهم كله في ملف المرحل الداخلي Internal Relay، وهو الملف relay/start.sh التالي:
#!/bin/sh
# relay.corp.test: the company's internal relay. Apps and the printer by IP on 25, people by login on 587,
# everything leaves through the provider's smarthost with a DKIM signature of corp.test.
set -e
make-ssl-cert generate-default-snakeoil --force-overwrite
postconf -e \
'myhostname = relay.corp.test' 'mydomain = corp.test' 'myorigin = corp.test' \
'mydestination =' 'relay_domains =' 'local_recipient_maps =' 'inet_protocols = ipv4' \
'mynetworks = 127.0.0.0/8 10.88.10.0/24 10.88.20.31/32' \
'smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination' \
'relayhost = [smtp.sendco.test]:587' \
'smtp_sasl_auth_enable = yes' 'smtp_sasl_password_maps = texthash:/etc/postfix/sasl_passwd' \
'smtp_sasl_security_options = noanonymous' 'smtp_sasl_tls_security_options = noanonymous' \
'smtp_tls_security_level = encrypt' 'smtp_tls_loglevel = 1' \
'smtpd_sasl_type = cyrus' 'smtpd_sasl_path = smtpd' \
'smtpd_milters = inet:127.0.0.1:8891' 'non_smtpd_milters = $smtpd_milters' 'milter_default_action = tempfail' \
'maillog_file = /dev/stdout'
# 587: TLS first, then login, then relay for the logged-in user only, with the sender tied to the login
postconf -M submission/inet='submission inet n - n - - smtpd'
postconf -P 'submission/inet/syslog_name=postfix/submission' \
'submission/inet/smtpd_tls_security_level=encrypt' 'submission/inet/smtpd_tls_auth_only=yes' \
'submission/inet/smtpd_sasl_auth_enable=yes' \
'submission/inet/smtpd_sender_login_maps=inline:{[email protected][email protected]}' \
'submission/inet/smtpd_sender_restrictions=reject_sender_login_mismatch' \
'submission/inet/smtpd_relay_restrictions=permit_sasl_authenticated,reject'
postconf -F '*/*/chroot = n'
# login for people on 587 (Cyrus sasldb)
mkdir -p /etc/postfix/sasl
printf 'pwcheck_method: auxprop\nauxprop_plugin: sasldb\nmech_list: PLAIN LOGIN\n' > /etc/postfix/sasl/smtpd.conf
echo 'lab-ops-pass' | saslpasswd2 -p -c -u corp.test ops
chown postfix:postfix /etc/sasldb2 && chmod 640 /etc/sasldb2
# our account at the provider (the lab starts with a wrong password on purpose)
printf '[smtp.sendco.test]:587 [email protected]:%s\n' "$(cat /run/lab/smarthost-password)" > /etc/postfix/sasl_passwd
chmod 600 /etc/postfix/sasl_passwd
# OpenDKIM: sign every message From @corp.test that this relay accepted
mkdir -p /etc/opendkim/keys /run/opendkim && cp /run/lab/dkim/relay2026.key /etc/opendkim/keys/
chown -R opendkim:opendkim /etc/opendkim /run/opendkim && chmod 600 /etc/opendkim/keys/relay2026.key
printf 'relay2026._domainkey.corp.test corp.test:relay2026:/etc/opendkim/keys/relay2026.key\n' > /etc/opendkim/key.table
printf '*@corp.test relay2026._domainkey.corp.test\n' > /etc/opendkim/signing.table
cat > /etc/opendkim.conf <<'C'
Syslog no
Mode s
Canonicalization relaxed/simple
Socket inet:[email protected]
KeyTable refile:/etc/opendkim/key.table
SigningTable refile:/etc/opendkim/signing.table
# the relay only accepts what it trusts (by IP or by login), so it signs whatever it accepted
InternalHosts 0.0.0.0/0
UserID opendkim
PidFile /run/opendkim/opendkim.pid
C
opendkim -x /etc/opendkim.conf
exec postfix start-fgفي الإعداد أعلاه لاحظ التالي:
- الإعدادان
mydestinationوrelay_domainsفارغان، فهذا السيرفر لا يستقبل بريداً لأي نطاق، وكل ما يقبله يرحله، وهذا ما تسميه وثائق Postfix قريباً من العميل الصفري Null Client، والفرق أنه يقبل من الشبكة. - القائمة
mynetworksهي قائمة الثقة: شبكة التطبيقات كاملة10.88.10.0/24، والطابعة وحدها10.88.20.31/32وليس شبكتها، والسبب أن شبكة الأجهزة فيها كاميرات وأجهزة لا تديرها أنت. - القيد
smtpd_relay_restrictionsعلى المنفذ 25: يمرر لمن فيmynetworks، ولمن سجل دخوله، ويرفض أي رسالة لنطاق خارجي من غيرهما، والقاعدةreject_unauth_destinationموصوفة في وثائق Postfix. - على المنفذ 587 القيد أشد: لا ترحيل Relay إلا لمن سجل دخوله، و
reject_sender_login_mismatchمعsmtpd_sender_login_mapsيربط الحساب[email protected]بعنوانه، فلا يستطيع أن يرسل باسم المدير. - الإعداد
relayhostمع الأقواس المربعة يرسل كل البريد الخارج إلى الخادم الوسيط Smarthost دون البحث عن سجل MX لاسمه، وsmtp_tls_security_level = encryptيمنع إرسال كلمة المرور قبل TLS. وفي الإنتاج استخدمsecureبدلها ليتحقق Postfix من شهادة المزود أيضاً، وهذا ما لا نستطيعه هنا لأن شهادات المختبر ذاتية التوقيع. - الملف
sasl_passwdمن النوعtexthashحتى لا نحتاجpostmapفي المختبر، وفي سيرفر حقيقي استخدم النوع الافتراضي لنظامك معpostmapكما في دليل SES. - برنامج التوقيع OpenDKIM يوقع كل رسالة
Fromفيها منcorp.test، والسطرInternalHosts 0.0.0.0/0يجعله يوقع كل ما قبله Postfix، وهذا آمن بشرط واحد: أن يكون Postfix لا يقبل إلا ممن يثق به، وسوف نرى في قسم الكسر ماذا يحدث عندما يختل هذا الشرط.
أما الخادم الوسيط Smarthost عند المزود فالملف smarthost/start.sh:
#!/bin/sh
# smtp.sendco.test: a sending provider's smarthost. Login required on 587, then it delivers to the recipient's MX
# with its own envelope sender (bounces come back to the provider, like most sending services).
set -e
make-ssl-cert generate-default-snakeoil --force-overwrite
id bounces >/dev/null 2>&1 || useradd -m bounces
postconf -e \
'myhostname = smtp.sendco.test' 'mydomain = sendco.test' 'myorigin = sendco.test' \
'mydestination = sendco.test, localhost' 'inet_protocols = ipv4' 'mynetworks = 127.0.0.0/8' \
'sender_canonical_classes = envelope_sender' \
"sender_canonical_maps = static:$(cat /run/lab/mail-from)" \
'smtpd_sasl_type = cyrus' 'smtpd_sasl_path = smtpd' 'smtp_tls_security_level = may' \
'maillog_file = /dev/stdout'
postconf -M submission/inet='submission inet n - n - - smtpd'
postconf -P 'submission/inet/syslog_name=postfix/submission' \
'submission/inet/smtpd_tls_security_level=encrypt' 'submission/inet/smtpd_tls_auth_only=yes' \
'submission/inet/smtpd_sasl_auth_enable=yes' \
'submission/inet/smtpd_relay_restrictions=permit_sasl_authenticated,reject'
postconf -F '*/*/chroot = n'
mkdir -p /etc/postfix/sasl
printf 'pwcheck_method: auxprop\nauxprop_plugin: sasldb\nmech_list: PLAIN LOGIN\n' > /etc/postfix/sasl/smtpd.conf
echo 'provider-issued-secret' | saslpasswd2 -p -c -u sendco.test corp-relay
chown postfix:postfix /etc/sasldb2 && chmod 640 /etc/sasldb2
exec postfix start-fgوالسطران المهمان هنا sender_canonical_classes وsender_canonical_maps، فهما يغيران عنوان الظرف MAIL FROM وحده إلى [email protected] ويتركان From في الرسالة كما هو، وهذا ما تفعله أغلب خدمات الإرسال افتراضياً لكي تصلها الرسائل المرتدة Bounces، ومنها Amazon SES قبل أن تضبط نطاق MAIL FROM خاصاً بك. وسوف نرى أثر هذا على DMARC في قسم التطابق.
وسيرفر المستلم rx/start.sh يقبل بريد inbox.test، ويسأل Rspamd عن SPF وDKIM وDMARC قبل أن يقبل الرسالة، ثم يسلمها إلى Mailpit:
#!/bin/sh
# mx.inbox.test: the recipient's provider. Asks Rspamd for SPF, DKIM and DMARC, then hands the message to Mailpit.
set -e
postconf -e \
'myhostname = mx.inbox.test' 'mydestination =' 'relay_domains = inbox.test' 'inet_protocols = ipv4' \
'mynetworks = 127.0.0.0/8' 'transport_maps = inline:{ inbox.test=smtp:[10.88.50.32]:1025 }' \
'smtpd_milters = inet:10.88.50.31:11332' 'milter_protocol = 6' 'milter_default_action = accept' \
'maillog_file = /dev/stdout'
postconf -F '*/*/chroot = n'
exec postfix start-fgوملفات Rspamd هي نفسها التي شرحناها في التجربة العملية لتكامل منصة SaaS، ومنها القاعدة التي ترفض الرسالة عندما تفشل في DMARC والنطاق ينشر p=reject، والفرق الوحيد عنوان ال DNS في override.d/options.inc وهو هنا 10.88.50.53. وأخيراً السكربت setup.sh الذي يولد مفتاح DKIM وسجلات DNS للنطاقات الأربعة:
#!/bin/sh
# Generates the DKIM key of corp.test and the DNS zones of the lab.
set -e
mkdir -p run/dkim dns/zones
[ -f run/dkim/relay2026.key ] || openssl genrsa -out run/dkim/relay2026.key 2048 2>/dev/null
PUB=$(openssl rsa -in run/dkim/relay2026.key -pubout -outform DER 2>/dev/null | base64 -w0)
DKIM=$(echo "v=DKIM1; k=rsa; p=$PUB" | sed 's/.\{200\}/&" "/g')
SOA='@ 300 IN SOA ns.lab.test. hostmaster.lab.test. 1 300 60 86400 60'
cat > dns/zones/corp.test.db <<Z
\$ORIGIN corp.test.
$SOA
@ IN NS ns.lab.test.
@ IN TXT "v=spf1 -all"
relay2026._domainkey IN TXT ( "$DKIM" )
_dmarc IN TXT "v=DMARC1; p=reject; adkim=r; aspf=r"
bounce IN TXT "v=spf1 ip4:10.88.50.20 -all"
Z
cat > dns/zones/corp-shop.test.db <<Z
\$ORIGIN corp-shop.test.
$SOA
@ IN NS ns.lab.test.
@ IN TXT "v=spf1 -all"
_dmarc IN TXT "v=DMARC1; p=reject; adkim=r; aspf=r"
Z
cat > dns/zones/sendco.test.db <<Z
\$ORIGIN sendco.test.
$SOA
@ IN NS ns.lab.test.
smtp IN A 10.88.50.20
@ IN TXT "v=spf1 ip4:10.88.50.20 -all"
Z
cat > dns/zones/inbox.test.db <<Z
\$ORIGIN inbox.test.
$SOA
@ IN NS ns.lab.test.
@ IN MX 10 mx.inbox.test.
mx IN A 10.88.50.30
Z
echo "zones written"لاحظ أن سجل SPF للنطاق corp.test هو -all، أي أن الشركة لا ترسل من أي عنوان مباشرة، وأن النطاق corp-shop.test، وهو متجر الشركة، ينشر DMARC بسياسة reject ولا يوجد له مفتاح DKIM على المرحل Relay، وهذا النطاق سوف نحتاجه لنرى الفشل. وملف CoreDNS يخدم النطاقات الأربعة ويرد على أي اسم آخر بأنه غير موجود:
corp.test {
file /zones/corp.test.db
}
corp-shop.test {
file /zones/corp-shop.test.db
}
sendco.test {
file /zones/sendco.test.db
}
inbox.test {
file /zones/inbox.test.db
}
. {
# no Internet in this lab: every other name does not exist
template ANY ANY {
rcode NXDOMAIN
}
}الآن نجهز الملفات ونشغل المختبر، ونبدأ عمداً بكلمة مرور خاطئة لحساب المزود:
sh setup.sh
printf 'wrong-password' > run/smarthost-password
printf '[email protected]' > run/mail-from
docker build -t lab-relay-tools tools
docker compose up -d --build
docker compose ps --format 'table {{.Name}}\t{{.Image}}\t{{.Status}}'والمخرج سوف يكون كما يلي:
NAME IMAGE STATUS
lab-relay-app lab-relay-tools Up 22 seconds
lab-relay-dns coredns/coredns:1.14.7 Up 19 seconds
lab-relay-laptop lab-relay-tools Up 23 seconds
lab-relay-mailpit axllent/mailpit:v1.31.5 Up 15 seconds (healthy)
lab-relay-printer lab-relay-tools Up 42 seconds
lab-relay-rogue lab-relay-tools Up 25 seconds
lab-relay-rspamd rspamd/rspamd:4.2.2 Up 39 seconds (healthy)
lab-relay-rx lab-relay-mta Up 13 seconds
lab-relay-smarthost lab-relay-mta Up 17 seconds
lab-relay-stranger lab-relay-tools Up 35 secondsوالمرحل Relay غير موجود في القائمة لأنه توقف فور تشغيله، وفي سجله السبب:
postconf: fatal: -Pe does not accept whitespace in parameter valueفالأمر postconf -P الذي يكتب إعداداً لخدمة واحدة في master.cf لا يقبل مسافة داخل القيمة، وكنا كتبنا الخريطة inline:{ [email protected][email protected] } بمسافتين كما نكتبها مع postconf -e، فحذفنا المسافتين كما في الملف أعلاه، وأعدنا تشغيله بالأمر docker compose up -d --force-recreate relay.
التطبيق يرسل بعنوانه، والطابور يحفظ الرسالة
نرسل من سيرفر التطبيق 10.88.10.21 على المنفذ 25 دون تسجيل دخول، كما تفعل أغلب التطبيقات الداخلية:
docker exec lab-relay-app swaks --server 10.88.10.10 --port 25 --helo app.corp.test \
--from [email protected] --to [email protected] \
--header 'Subject: order 1042 confirmed' --body 'Your order is confirmed.'والمخرج سوف يكون كما يلي، واختصرنا منه أسطر EHLO ونص الرسالة:
<- 250 2.1.0 Ok
-> RCPT TO:<[email protected]>
<- 250 2.1.5 Ok
-> DATA
<- 354 End data with <CR><LF>.<CR><LF>
...
<- 250 2.0.0 Ok: queued as DF0A82C9825
-> QUIT
<- 221 2.0.0 Byeالتطبيق انتهى عمله، فالمرحل Relay قبل الرسالة، ولكنها لم تصل بعد، وهذا ما يقوله سجل المرحل Relay عنها:
relay postfix/smtp[218]: Untrusted TLS connection established to smtp.sendco.test[10.88.50.20]:587: TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature RSA-PSS (2048 bits) server-digest SHA256
relay postfix/smtp[218]: DF0A82C9825: to=<[email protected]>, relay=smtp.sendco.test[10.88.50.20]:587, delay=26, delays=0.74/12/14/0, dsn=4.7.8, status=deferred (SASL authentication failed; server smtp.sendco.test[10.88.50.20] said: 535 5.7.8 Error: authentication failed: authentication failure)في المخرج أعلاه لاحظ التالي:
- الخادم الوسيط Smarthost رفض كلمة المرور بالرد الدائم
535، ولكن Postfix جعل الحالةdeferredبالرمز4.7.8وأبقى الرسالة في الطابور، والسبب الإعدادsmtp_sasl_auth_soft_bounceوقيمته الافتراضيةyesمنذ Postfix 2.5 كما في وثائقه، فخطأ كلمة المرور خطأ إعداد عندك، وليس سبباً لإرجاع رسائل العملاء. - بدأ TLS قبل تسجيل الدخول، فكلمة المرور لم تمر نصاً واضحاً، وكلمة
Untrustedلأن شهادة المختبر ذاتية التوقيع. - لو كان التطبيق يرسل إلى المزود مباشرة لرأى هو هذا الخطأ، وغالباً لن يعيد المحاولة، أما الآن فالرسالة محفوظة في طابور المرحل Relay حتى تصل.
والطابور على المرحل Relay يبين ذلك:
docker exec lab-relay-relay postqueue -p-Queue ID- --Size-- ----Arrival Time---- -Sender/Recipient-------
DF0A82C9825 483 Mon Oct 12 10:51:45 [email protected]
(SASL authentication failed; server smtp.sendco.test[10.88.50.20] said: 535 5.7.8 Error: authentication failed: authentication failure)
[email protected]
-- 0 Kbytes in 1 Request.الآن نصحح كلمة المرور ونطلب من Postfix أن يحاول فوراً بدل أن ينتظر موعد المحاولة التالية:
docker exec lab-relay-relay sh -c "printf '[smtp.sendco.test]:587 [email protected]:provider-issued-secret\n' > /etc/postfix/sasl_passwd && postfix reload && postqueue -f"والمخرج في سجلات السيرفرات الثلاثة سوف يكون كما يلي:
relay postfix/smtp[272]: DF0A82C9825: to=<[email protected]>, relay=smtp.sendco.test[10.88.50.20]:587, delay=125, delays=121/2.9/0.69/0.73, dsn=2.0.0, status=sent (250 2.0.0 Ok: queued as 947C52CBD6B)
smtp postfix/submission/smtpd[202]: 947C52CBD6B: client=lab-relay-relay.lab-relay-wan[10.88.50.10], sasl_method=PLAIN, [email protected]
smtp postfix/qmgr[200]: 947C52CBD6B: from=<[email protected]>, size=1191, nrcpt=1 (queue active)
smtp postfix/smtp[209]: 947C52CBD6B: to=<[email protected]>, relay=mx.inbox.test[10.88.50.30]:25, delay=9.5, delays=0.83/0.12/3.1/5.5, dsn=2.0.0, status=sent (250 2.0.0 Ok: queued as A3E142CBEE6)
mx postfix/smtp[170]: A3E142CBEE6: to=<[email protected]>, relay=10.88.50.32[10.88.50.32]:1025, delay=5.8, delays=5.5/0.37/0/0, dsn=2.0.0, status=sent (250 2.0.0 Ok: queued as 7Cnjh2Nasdc9bRZwNworo0)الرسالة نفسها مرت بثلاثة أرقام: DF0A82C9825 على المرحل Relay، ثم 947C52CBD6B عند المزود، ثم A3E142CBEE6 عند المستلم، وكل سيرفر يكتب في سجله رقم السيرفر التالي بعد queued as، وهكذا تتبع رسالة عبر أكثر من مرحل Relay واحد. ولاحظ أن المزود سجل المرسل [email protected] وليس [email protected]، فهذا عنوان الظرف الذي وضعه. ويجدر الإشارة هنا إلى خطأ وقعنا فيه أثناء البناء: كتبنا اسم المستخدم عند المزود corp-relay وحده فرفضت كلمة المرور الصحيحة بنفس الرد 535، والسبب أن الحساب أنشئ في النطاق sendco.test، فاسمه الكامل [email protected]، وكثير من المزودين يعطونك اسم مستخدم طويلاً لا يشبه بريدك، فانسخه كما هو.
الطابعة تمر بعنوانها، وجارها في الشبكة نفسها لا يمر
الطابعة 10.88.20.31 ترسل على المنفذ 25 دون تسجيل دخول ودون TLS:
docker exec lab-relay-printer swaks --server 10.88.20.10 --helo scanner-2f.corp.test \
--from [email protected] --to [email protected] --header 'Subject: scan 0042' --body 'scanned document'<- 250 2.1.5 Ok
<- 354 End data with <CR><LF>.<CR><LF>
<- 250 2.0.0 Ok: queued as BE1642D197F
<- 221 2.0.0 Byeثم نجرب الشيء نفسه من الجهاز 10.88.20.66 في الشبكة نفسها:
docker exec lab-relay-rogue swaks --server 10.88.20.10 --helo cam-7.corp.test \
--from [email protected] --to [email protected] --quit-after RCPT<- 250 2.1.0 Ok
-> RCPT TO:<[email protected]>
<** 554 5.7.1 <[email protected]>: Relay access denied
-> QUIT
<- 221 2.0.0 Byeوالفرق الوحيد بينهما هو /32 في mynetworks، فلو كتبنا شبكة الأجهزة كاملة لمر الجهازان. والرمز 554 هو القيمة الافتراضية للإعداد relay_domains_reject_code، أما RFC 5321 فيقول في القسم 3.6.2 إن السيرفر الذي يرفض الترحيل Relay لأسباب السياسة يرد بـ 550، والمعنى واحد: رفض دائم لا يعاد بعده المحاولة.
الموظف من خارج الشبكة يسجل دخوله على 587
الحاسوب 10.88.40.40 في شبكة الضيوف، فلا يثق به المرحل Relay بعنوانه، ونجربه أولاً دون تسجيل دخول:
docker exec lab-relay-laptop swaks --server 10.88.40.10 --port 587 --tls --helo laptop.corp.test \
--from [email protected] --to [email protected] --quit-after RCPT<~ 250 2.1.0 Ok
<~* 553 5.7.1 <[email protected]>: Sender address rejected: not logged in
<~ 221 2.0.0 Byeالرفض جاء من القيد reject_sender_login_mismatch قبل أن يصل إلى قيد الترحيل Relay، لأن العنوان [email protected] مربوط بحساب ولم يسجل أحد دخوله. والآن بتسجيل الدخول:
docker exec lab-relay-laptop swaks --server 10.88.40.10 --port 587 --tls --helo laptop.corp.test \
--auth PLAIN --auth-user [email protected] --auth-password lab-ops-pass \
--from [email protected] --to [email protected] --header 'Subject: weekly report' --body 'report attached'<- 250-STARTTLS
<- 220 2.0.0 Ready to start TLS
<~ 250-AUTH PLAIN LOGIN
<~ 235 2.7.0 Authentication successful
<~ 250 2.0.0 Ok: queued as 65B1F2E72D9لاحظ أن AUTH لم يظهر في قائمة الإمكانيات إلا بعد TLS، فالعلامة <~ في swaks تعني رداً على اتصال مشفر، وهذا أثر smtpd_tls_auth_only=yes. ثم نجرب الحساب نفسه بعنوان المدير:
docker exec lab-relay-laptop swaks --server 10.88.40.10 --port 587 --tls --helo laptop.corp.test \
--auth PLAIN --auth-user [email protected] --auth-password lab-ops-pass \
--from [email protected] --to [email protected] --quit-after RCPT<~ 235 2.7.0 Authentication successful
<~ 250 2.1.0 Ok
<~* 553 5.7.1 <[email protected]>: Sender address rejected: not owned by user [email protected]
<~ 221 2.0.0 Byeوهذا مهم في المرحل Relay تحديداً، والسبب أن المرحل Relay يوقع بمفتاح الشركة كل ما يقبله، فلو قبل من أي حساب أي عنوان لأصبح كل موظف قادراً على إرسال رسالة موقعة باسم المدير.
اختبار المرحل المفتوح Open Relay من شبكة خارجية
الجهاز 10.88.40.66 في شبكة الضيوف يمثل أي شخص يصل إلى المرحل Relay ولا نعرفه: ضيف على شبكة Wi-Fi، أو جهاز في VPN، أو مهاجم من الإنترنت إذا نشر أحدهم المنفذ 25 بالخطأ. ويحاول أن يرسل باسم المدير إلى خارج الشركة:
docker exec lab-relay-stranger swaks --server 10.88.40.10 --helo guest.example \
--from [email protected] --to [email protected] --quit-after RCPT<- 250 2.1.0 Ok
-> RCPT TO:<[email protected]>
<** 554 5.7.1 <[email protected]>: Relay access denied
-> QUIT
<- 221 2.0.0 Byeوهذا الاختبار نفسه هو ما يجب أن تشغله بعد أي تغيير على المرحل Relay، ومن كل شبكة يمكن أن يصل منها أحد، وليس من شبكة التطبيقات فقط، لأن المرحل Relay يبدو سليماً تماماً من الشبكة الموثوقة.
اكسرها لتفهمها: mynetworks واسعة
الآن نصنع الخطأ الأكثر شيوعاً، فمدير الشبكة يريد أن يريح نفسه فيكتب نطاق الشركة كله بدل الشبكات المحددة، أي 10.88.0.0/16، وهذا النطاق يشمل شبكة الضيوف أيضاً دون أن ينتبه:
docker exec lab-relay-relay sh -c "postconf -e 'mynetworks = 127.0.0.0/8 10.88.0.0/16' && postfix reload"
docker exec lab-relay-stranger swaks --server 10.88.40.10 --helo guest.example \
--from [email protected] --to [email protected] \
--header 'Subject: urgent: wire transfer today' --body 'Please pay the attached invoice today.'والمخرج سوف يكون كما يلي:
<- 250 2.1.5 Ok
<- 250 2.0.0 Ok: queued as 326692E7B6Fوالرسالة وصلت إلى صندوق الضحية، وهذه ترويساتها كما يراها برنامج البريد عنده:
Authentication-Results: mx.inbox.test;
dkim=pass header.d=corp.test header.s=relay2026 header.b=DRdqdi7p;
dmarc=pass (policy=reject) header.from=corp.test;
spf=pass (mx.inbox.test: domain of [email protected] designates 10.88.50.20 as permitted sender) [email protected]
Received: from guest.example (lab-relay-stranger.lab-relay-guest [10.88.40.66])
by relay.corp.test (Postfix) with ESMTP id 326692E7B6F
for <[email protected]>; Mon, 12 Oct 2026 10:59:54 +0000 (UTC)
From: [email protected]
Subject: urgent: wire transfer todayفي المخرج أعلاه لاحظ التالي:
- رسالة احتيال من جهاز لا نعرفه، ومع ذلك نجح DKIM بمفتاح الشركة، ونجح DMARC بسياسة
reject، فلا يوجد أي فحص عند المستلم يستطيع أن يكشفها، لأنها خرجت فعلاً من البنية التي تثق بها الشركة. - كل ما بنيته من SPF وDKIM وDMARC يحميك من انتحال نطاقك من الخارج، ولا يحميك من مرحلك Relay أنت، فالمرحل المفتوح Open Relay عند شركة لها DMARC أخطر منه عند شركة ليس لها DMARC.
- الرسالة دفعت ثمنها أنت أيضاً: سمعة حسابك عند المزود، وحدود الإرسال، وربما إيقاف الحساب إذا أرسل المهاجم آلاف الرسائل.
- السطر
Receivedهو الدليل الوحيد، ففيه العنوان10.88.40.66، لذلك فكر جيداً قبل أن تحذف ترويساتReceivedالداخلية لتخفي عناوين شبكتك، فهي ما تحتاجه عند التحقيق.
وقد يتساءل البعض: إذا لم أكتب mynetworks أصلاً، فما قيمتها؟ والإجابة في وثائق Postfix: القيمة الافتراضية تحسب من mynetworks_style، وهي host منذ Postfix 3.0، أي السيرفر نفسه فقط، وكانت قبل ذلك subnet، أي كل الشبكات المتصلة بواجهات السيرفر، وكانت الوثائق تحذر من القيمة class لأنها قد تثق بشبكة المزود كلها. لذلك إذا وجدت سيرفراً قديماً، أو إعداداً منسوخاً من دليل قديم، فاطبع القيمة الفعلية بالأمر postconf mynetworks ولا تعتمد على ما تتوقعه. وفي Proxmox Mail Gateway رأينا الخطأ نفسه بسبب آخر تماماً، وهو عنوان السيرفر في /etc/hosts، كما في قسم الكسر في دليله.
والآن نعيد القائمة الصحيحة ونتأكد من الرفض مرة أخرى:
docker exec lab-relay-relay sh -c "postconf -e 'mynetworks = 127.0.0.0/8 10.88.10.0/24 10.88.20.31/32' && postfix reload && postconf mynetworks"mynetworks = 127.0.0.0/8 10.88.10.0/24 10.88.20.31/32ماذا يحدث لـ SPF وDMARC عندما يمر البريد عبر Smarthost في الطريق؟
هنا يقع أغلب الخلط، فالمستلم لا يرى المرحل الداخلي Internal Relay أبداً، وإنما يرى آخر سيرفر سلمه الرسالة، ويحكم بثلاثة أشياء كما شرحنا في قسم التطابق Alignment: هل نجح SPF لعنوان الظرف، وهل نجح DKIM، وهل يتطابق نطاق أحدهما مع نطاق From. والصورة التالية تبين أين يتقرر كل شيء، والنتائج الثلاث التي حصلنا عليها في المختبر:

الحالة A: المزود يضع عنوان ظرف خاصاً به
هذه ترويسات رسالة التطبيق الأولى كما وصلت إلى Mailpit، وقد حذفنا منها التوقيع الطويل:
Return-Path: <[email protected]>
Received: from smtp.sendco.test (lab-relay-smarthost.lab-relay-wan [10.88.50.20])
by mx.inbox.test (Postfix) with ESMTPS id A3E142CBEE6
for <[email protected]>; Mon, 12 Oct 2026 10:53:54 +0000 (UTC)
Authentication-Results: mx.inbox.test;
dkim=pass header.d=corp.test header.s=relay2026 header.b=bHOQUAXc;
dmarc=pass (policy=reject) header.from=corp.test;
spf=pass (mx.inbox.test: domain of [email protected] designates 10.88.50.20 as permitted sender) [email protected]
Received: from relay.corp.test (lab-relay-relay.lab-relay-wan [10.88.50.10])
by smtp.sendco.test (Postfix) with ESMTPSA id 947C52CBD6B
for <[email protected]>; Mon, 12 Oct 2026 10:53:50 +0000 (UTC)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=corp.test;
s=relay2026; t=1791802305;
h=Date:To:From:Subject;
Received: from app.corp.test (lab-relay-app.lab-relay-apps [10.88.10.21])
by relay.corp.test (Postfix) with ESMTP id DF0A82C9825
for <[email protected]>; Mon, 12 Oct 2026 10:51:45 +0000 (UTC)
From: [email protected]
Subject: order 1042 confirmedاقرأ أسطر Received من الأسفل إلى الأعلى، فهي طريق الرسالة بالترتيب: من التطبيق إلى المرحل Relay بالكلمة ESMTP أي دون تسجيل دخول، ثم من المرحل Relay إلى المزود بالكلمة ESMTPSA أي بتشفير وتسجيل دخول، ثم من المزود إلى المستلم. وفي المخرج أعلاه لاحظ التالي:
- النتيجة
spf=passولكن لنطاقsendco.test، لأن المستلم فحص العنوان الذي اتصل منه10.88.50.20مقابل عنوان الظرف الذي وضعه المزود، وهذا النطاق لا يطابقcorp.testفيFrom، فلا يحسب لـ DMARC. - النتيجة
dkim=pass header.d=corp.testتطابقFrom، وهذا وحده كاف لنجاح DMARC، لأن DMARC يكفيه تطابق واحد من الاثنين كما في RFC 9989 الذي حل محل RFC 7489 في مايو 2026. - سجل SPF لنطاق
corp.testنفسه-allولم يفحص أصلاً، فإضافة عنوان المزود إليه لا تغير شيئاً في هذه الحالة، وهذا خطأ نراه كثيراً: فريق يضيفincludeللمزود في SPF النطاق الرئيسي ويظن أنه أصلح التطابق. - التوقيع حدث على المرحل الداخلي Internal Relay قبل أن تخرج الرسالة، والمزود لم يغير الرسالة، فبقي التوقيع صحيحاً عند المستلم، ولو كان المزود يضيف تذييلاً أو يغير الروابط لتتبع النقرات لانكسر التوقيع، ولوجب أن يوقع هو بمفتاح لنطاقك.
الحالة B: نطاق MAIL FROM خاص بك
أغلب خدمات الإرسال تسمح لك بنطاق فرعي لعنوان الظرف من نطاقك، مثل bounce.corp.test، كما في إعداد MAIL FROM في SES، ونقلده في المختبر بتغيير السطر عند المزود، وفي ملف DNS سجل SPF جاهز لهذا النطاق الفرعي يسمح بعنوان المزود:
docker exec lab-relay-smarthost sh -c "postconf -e 'sender_canonical_maps = static:[email protected]' && postfix reload"
docker exec lab-relay-app swaks --server 10.88.10.10 --helo app.corp.test \
--from [email protected] --to [email protected] --header 'Subject: order 1043 confirmed' --body 'Your order is confirmed.'Return-Path: <[email protected]>
Authentication-Results: mx.inbox.test;
dkim=pass header.d=corp.test header.s=relay2026 header.b=LL8YVLMu;
dmarc=pass (policy=reject) header.from=corp.test;
spf=pass (mx.inbox.test: domain of [email protected] designates 10.88.50.20 as permitted sender) [email protected]الآن يتطابق SPF أيضاً، لأن bounce.corp.test نطاق فرعي من corp.test والتطابق في سجل DMARC مرن aspf=r. وقد يتساءل البعض: ما دام DKIM يكفي، فلماذا التعب؟ والإجابة أن التطابقين معاً يحميانك من الحالة التي ينكسر فيها أحدهما، مثل رسالة يعيد توجيهها Forwarding نظام في الطريق فيتغير عنوان الاتصال ويفشل SPF، أو تذييل يضيفه المزود فينكسر DKIM.
الحالة C: نطاق لا يملك المرحل Relay مفتاحه
تطبيق المتجر يرسل من المرحل Relay نفسه ولكن بنطاق المتجر corp-shop.test، والمرحل Relay لا يملك مفتاحاً لهذا النطاق:
docker exec lab-relay-app swaks --server 10.88.10.10 --helo app.corp.test \
--from [email protected] --to [email protected] --header 'Subject: your cart is waiting' --body 'Come back to the shop.'التطبيق يرى 250 2.0.0 Ok: queued as 88A9C2EA538، والمرحل Relay يرى أن المزود قبل الرسالة، وهذا ما حدث بعد ذلك في سجل المزود:
relay postfix/smtp[333]: 88A9C2EA538: to=<[email protected]>, relay=smtp.sendco.test[10.88.50.20]:587, delay=35, delays=4.9/12/2.8/15, dsn=2.0.0, status=sent (250 2.0.0 Ok: queued as 04FFA2EA763)
smtp postfix/smtp[225]: 04FFA2EA763: to=<[email protected]>, relay=mx.inbox.test[10.88.50.30]:25, delay=19, delays=4.9/5.7/4.7/3.2, dsn=5.7.1, status=bounced (host mx.inbox.test[10.88.50.30] said: 554 5.7.1 Rejected: DMARC policy of the From domain (no aligned SPF or DKIM pass) (in reply to end of DATA command))
smtp postfix/bounce[226]: 04FFA2EA763: sender non-delivery notification: 257C12EA99F
smtp postfix/local[227]: 257C12EA99F: to=<[email protected]>, relay=local, delay=0.27, delays=0.05/0.09/0/0.12, dsn=2.0.0, status=sent (delivered to mailbox)في المخرج أعلاه لاحظ التالي:
- سجل المرحل Relay يقول
status=sent، وسجل التطبيق يقولqueued، ومع ذلك لم تصل الرسالة، فالرفض حدث بعد مرحلتين عند سيرفر لا تملكه. - الرسالة المرتدة ذهبت إلى
[email protected]، أي إلى المزود وليس إليك، لأن عنوان الظرف عنوانه، لذلك يجب أن تأخذ تقارير الارتداد من المزود عبر Webhook أو ما يشبهه، كما في قسم الارتدادات في دليل SES، وإلا فلن تعرف أبداً أن رسائل المتجر كلها ترفض. - الحل أن يكون لكل نطاق يرسل عبر المرحل Relay مفتاح DKIM في
signing.table، أو نطاق MAIL FROM متطابق عند المزود، وأن تكون قائمة النطاقات المسموح لها بالمرور عبر المرحل Relay معروفة ومكتوبة.
أخطاء شائعة
- شبكة كاملة في
mynetworksمن أجل جهاز واحد: تكتب10.0.0.0/8لأن الطابعة «في مكان ما فيها»، فتثق بكل جهاز وكل ضيف، والصحيح عنوان الجهاز بـ/32، أو شبكة مخصصة للأجهزة التي تحتاج الترحيل Relay فقط. - الاختبار من الشبكة الموثوقة وحدها: المرحل المفتوح Open Relay لا يظهر إلا من الشبكات غير الموثوقة، فشغل اختبار الترحيل Relay من شبكة الضيوف ومن VPN ومن الإنترنت بعد كل تغيير.
- نشر المنفذ 25 للمرحل الداخلي Internal Relay على الإنترنت: المرحل الداخلي Internal Relay لا يحتاج أن يصل إليه أحد من الخارج، فاجعل جدار الحماية Firewall يسمح له بالخروج إلى الخادم الوسيط Smarthost فقط، ولا يسمح بأي اتصال وارد من الخارج.
- كلمة مرور المزود في كل تطبيق: خمسون تطبيقاً بنفس بيانات SMTP عند المزود يعني أن تسريب واحد منها يكشف الحساب كله، وأن تغيير كلمة المرور يحتاج خمسين تعديلاً، والصحيح أن تكون عند المرحل Relay وحده، وأن تعطي التطبيقات التي تحتاج تسجيل الدخول حسابات خاصة بها على المرحل Relay نفسه.
- حساب واحد على 587 لكل الناس: عندما يشترك عشرة أشخاص في حساب واحد لا تعرف من أرسل ماذا، ولا تستطيع أن تربط الحساب بعنوان مرسل، فاجعل لكل مرسل حسابه.
- اعتبار
status=sentعلى المرحل Relay وصولاً: هذا يعني أن السيرفر التالي قبلها فقط، والرفض قد يحدث بعد مرحلتين كما في الحالة C، فتابع تقارير المزود وتقارير DMARC. - إضافة المزود إلى SPF النطاق الرئيسي لإصلاح DMARC: إذا كان المزود يضع عنوان ظرف من نطاقه هو، فسجل SPF لنطاقك لا يفحص أصلاً، والذي يصلح التطابق هو DKIM بنطاقك أو نطاق MAIL FROM من نطاقك.
- إطفاء التحقق من شهادة الخادم الوسيط Smarthost: المستوى
encryptيشفر ولا يتحقق ممن تكلمه، فكلمة مرور المزود قد تذهب لسيرفر ينتحل اسمه، واستخدمsecureفي الإنتاج.
قائمة تحقق للمرحل Relay قبل التشغيل
- هل تعرف كل عنوان وشبكة في
mynetworksولماذا هي هناك؟ اطبعها بالأمرpostconf mynetworks. - هل يرفض المرحل Relay التمرير إلى الخارج من شبكة الضيوف ومن VPN ومن الإنترنت برد
554أو550؟ - هل المنفذ 587 يطلب TLS ثم تسجيل الدخول، ويربط كل حساب بعناوينه؟
- هل بيانات الخادم الوسيط Smarthost محفوظة على المرحل Relay وحده، بصلاحيات
600، والتحقق من شهادته مفعل؟ - هل لكل نطاق يرسل عبر المرحل Relay مفتاح DKIM، أو نطاق MAIL FROM متطابق عند المزود؟
- هل تصلك الرسائل المرتدة وتقارير DMARC من المزود؟
- هل تعرف أين الطابور وكيف تفرغه بعد إصلاح خطأ عند المزود، أي
postqueue -pوpostqueue -f؟
إزالة المختبر
كل ما أنشأه المختبر يبدأ بالاسم lab-relay-، فنحذفه بالأسماء، مع ال Volume المجهول الذي ينشئه Rspamd، فهو لا يحمل اسم المشروع، لذلك استخدم الخيار -v:
docker compose down -v
docker image rm lab-relay-mta lab-relay-toolsالخلاصة
وصلنا لنهاية الموضوع، وأهم ما فيه:
- البروتوكول على كل سهم هو SMTP، والترحيل Relay دور يقوم به السيرفر عندما يقبل رسالة لنطاق ليس له ثم يرسلها إلى سيرفر آخر، والسؤال الوحيد المهم في أي مرحل Relay: ممن يقبل؟
- تحتاج خادماً وسيطاً Smarthost عندما يحجب مزودك المنفذ 25، وعندما تريد مخرجاً واحداً بتوقيع DKIM واحد وطابور يعيد المحاولة نيابة عن الطابعات والتطبيقات، ولاحظ أن بعض المزودين مثل DigitalOcean يحجبون 587 و465 أيضاً.
- الأشكال الآمنة كلها مقيدة: بعنوان IP محدد للأجهزة الداخلية، أو بتسجيل الدخول على 587 لمن هو خارجها، والمرحل المفتوح Open Relay هو الشكل الوحيد الذي لا تريده.
- قائمة
mynetworksواسعة واحدة حولت المرحل Relay في المختبر إلى مرحل مفتوح Open Relay، وخرجت منه رسالة احتيال باسم المدير نجحت في DKIM وDMARC، لأن المرحل Relay يوقع كل ما يقبله. - عبر الخادم الوسيط Smarthost يحسم DKIM بنطاقك التطابق، وعنوان الظرف يقرره المزود، والرسائل المرتدة تذهب إليه، فاضبط نطاق MAIL FROM خاصاً بك، وتابع تقاريره.
وهذه المقالات تكمل كل جزء من الصورة:
دليلكيف يعمل البريد الإلكتروني؟ SMTP وIMAP للمبتدئينماذا يحدث بين ضغطك على «إرسال» ووصول الرسالة، ولماذا ترتد بعض الرسائل، وأي منفذ تفتح لكل غرض.
دليلإرسال بريد سيرفرك عبر Amazon SES دون أن يصل إلى Spamإذا كان مزودك يحجب المنفذ 25 أو تصل رسائلك إلى Spam، فسوف ترسل البريد الصادر عبر Amazon SES ويبقى الاستقبال على سيرفرك.
دليلشرح SPF وDKIM وDMARC للمبتدئين: كيف تثبت أن رسائلك منك فعلاًلماذا يستطيع أي شخص أن يرسل باسم نطاقك، وكيف يسد كل سجل من هذه السجلات الثلاثة ثغرة تركها الذي قبله.
دليلبوابات أمن البريد المفتوحة: أيها تضع أمام خادمك؟مقارنة بين بوابات أمن البريد المفتوحة والمستضافة ذاتياً، لتعرف أيها يناسب مؤسستك وأين تضعها أمام خادم البريد.
دليلتكامل منصة SaaS مع أنظمة العميل دون خلط المستأجرينكيف تربط منصتك متعددة المستأجرين بنطاق العميل وبريده وأنظمته دون أن تفتح باباً على بقية العملاء.
دليلTransactional Email: كيف تصل رسائل تطبيقك في وقتهالماذا تفصل رسائل تطبيقك مثل استعادة كلمة المرور عن النشرات البريدية، وكيف ترسلها بحيث تصل في وقتها ولا تذهب إلى Spam.سجل التحديثات
- أكتوبر 2026: كتابة المقال واختباره على Postfix 3.10.13 وOpenDKIM 2.11.0 من حزم Debian 13.4، وCoreDNS 1.14.7، وRspamd 4.2.2، وMailpit 1.31.5، وswaks 20240103.0، مع Docker Engine 29.8.0 وDocker Compose 5.5.1، ومراجعة سياسات المنفذ 25 عند AWS وAzure وGoogle Cloud وDigitalOcean وHetzner في 12 أكتوبر 2026.