في هذا الدليل سوف نثبت Postal على سيرفر مستقل، ونجعل تطبيقاتك ترسل منه رسائل التسجيل واستعادة كلمة المرور والفواتير عبر SMTP أو عبر HTTP API، ثم نتابع كل رسالة في سجل الرسائل Message Log ونستقبل نتيجتها في تطبيقك عبر Webhooks. أما ما هو البريد التفاعلي Transactional Email، ولماذا يجب أن يخرج من مسار مستقل عن بريد الموظفين والنشرات، وكيف تختار بين خدمة مدارة ومنصة تستضيفها بنفسك، فقد شرحناه في مقال البريد التفاعلي Transactional Email، والصورة الكاملة لبريد المؤسسة في دليل استضافة البريد الإلكتروني للمؤسسات، وهنا نبدأ من قرار أنك تريد منصة الإرسال على سيرفرك.
في هذا المقال سوف نتناول:
- ما هو Postal وما الذي يقدمه، ومتى تختاره بدل Amazon SES، ولماذا لا يصلح خادماً لبريد الموظفين.
- المتطلبات والمنافذ Ports، وسجلات DNS الخاصة بالمنصة نفسها، ثم التثبيت بالأداة الرسمية
postalمع MariaDB وCaddy. - إنشاء المؤسسة Organization وخادم البريد Mail Server والنطاق Domain، والسجلات التي يطلبها Postal لكل نطاق ترسل منه.
- بيانات الدخول Credentials، والإرسال عبر SMTP على المنفذين 25 و587 مع TLS، والإرسال عبر HTTP API، مع مخرجات حقيقية.
- Webhooks لنتائج التسليم والارتدادات Bounces، والشهادة خلف Reverse Proxy، والأمان، والنسخ الاحتياطي Backup، والتحديث Upgrade، وأشهر المشكلات وحلولها.
ما هو Postal وما الذي يقدمه؟
Postal منصة إرسال بريد Mail Delivery Platform مفتوحة المصدر تحت ترخيص MIT، وفكرتها أن تقدم لك على سيرفرك ما تقدمه خدمات مثل Mailgun وAmazon SES: تطبيقك يسلم الرسالة إلى Postal بكلمة مرور خاصة به، وPostal يوقعها ويضعها في الطابور Queue ويسلمها إلى سيرفرات المستلمين، ويعيد المحاولة Retry إذا تأخر المستلم، ويسجل كل محاولة، ثم يخبر تطبيقك بالنتيجة. والإصدار الحالي هو 3.3.7 الصادر في يونيو 2026، ويعمل في ثلاث Containers من نفس ال Docker Image هي web للواجهة وAPI، وsmtp لاستقبال الرسائل، وworker للتسليم والمهام الخلفية، ومعها قاعدة بيانات MariaDB، ولم يعد يحتاج منذ الإصدار 3 إلى RabbitMQ كما كانت الإصدارات القديمة.
والمفاهيم التي سوف تتعامل معها في الواجهة قليلة ومرتبة من الأعلى إلى الأسفل كما يلي:
- المؤسسة Organization: المستوى الأعلى في Postal، وتجعل فيه فريقاً أو عميلاً، ولكل مؤسسة مستخدموها ونطاقاتها.
- خادم البريد Mail Server: ليس سيرفراً حقيقياً، وإنما مساحة إرسال منطقية داخل المؤسسة، ولكل واحد منها قاعدة بيانات مستقلة للرسائل، وبيانات دخول ونطاقات وWebhooks خاصة به، لذلك فالأنسب أن تنشئ خادماً لكل تطبيق أو لكل بيئة، مثل خادم لتطبيق الإنتاج وآخر لبيئة الاختبار.
- بيانات الدخول Credentials: من ثلاثة أنواع: SMTP لكلمة مرور يستخدمها التطبيق في SMTP، وAPI لمفتاح يرسل في الترويسة
X-Server-API-Key، وSMTP-IP للسماح بالإرسال من عنوان IP دون كلمة مرور. - المسارات Routes ونقاط التسليم Endpoints: للبريد الوارد، فرسالة تصل إلى
[email protected]مثلاً يحولها Postal إلى رابط HTTP في تطبيقك أو إلى سيرفر SMTP آخر أو إلى عنوان بريد. - Webhooks: طلبات HTTP يرسلها Postal إلى تطبيقك عند كل حدث، مثل التسليم والتأخير والفشل والارتداد.
- سجل الرسائل: كل رسالة بترويساتها ومحتواها ومحاولات تسليمها، مع البحث والتصفية، وقائمة الحظر Suppression List للعناوين التي فشل التسليم إليها مرات متكررة.
- مجموعات عناوين IP (IP Pools): إذا كان لسيرفرك أكثر من عنوان IP، فتستطيع أن تخصص عنواناً لكل خادم بريد أو لكل نوع من الرسائل كما يشرح التوثيق.
Postal ليس خادماً لبريد الموظفين
لا يوجد في Postal صناديق بريد Mailboxes ولا IMAP ولا بريد عبر الويب Webmail، فلا يستطيع موظف أن يفتح Outlook ويقرأ بريده منه، والمشروع نفسه يقول في صفحة الأسئلة الشائعة إنه وكيل نقل بريد Mail Transport Agent وليس مدير قوائم بريدية. لذلك فإذا كنت تبحث عن بريد للموظفين فالخيارات الصحيحة هي mailcow أو Stalwart أو Postfix مع Dovecot كما في دليل استضافة البريد الإلكتروني للمؤسسات، وإذا كنت تبحث عن نشرات بريدية Newsletters بقوائم مشتركين وإلغاء اشتراك فهذا عمل listmonk، وPostal يمكن أن يكون الجهة التي يرسل عبرها listmonk.
متى تختار Postal بدل Amazon SES؟
وقد يتساءل البعض: لماذا أتعب نفسي بسيرفر إرسال بينما يرسل Amazon SES الرسالة بجزء صغير من السنت كما في دليل الإرسال عبر Amazon SES؟ والإجابة أن الفرق ليس في السعر، وإنما في من يملك السمعة Reputation ومن يملك البيانات. ففي SES تخرج رسائلك من عناوين IP لها تاريخ طويل مع Gmail وOutlook، ولا تحتاج إلى المنفذ 25 ولا إلى سجل PTR، ولكن محتوى رسائلك وعناوين عملائك يمر عبر AWS، وحسابك معلق بنسب الارتداد والشكاوى التي تراقبها AWS. أما في Postal فالرسائل والسجلات على سيرفرك، ولا يوجد حد لعدد الرسائل غير قدرة السيرفر، ولكنك أنت المسؤول عن سمعة عنوان IP من أول رسالة، وعن قوائم الحظر Blocklists، وعن سجل PTR والمنفذ 25.
لذلك فالحكم العملي كما يلي: إذا كان مزودك يحجب المنفذ 25، أو كان حجم إرسالك صغيراً وتريد أن تصل الرسائل من اليوم الأول، فابدأ بـ Amazon SES. أما إذا كانت سياسة جهتك تمنع خروج بيانات العملاء إلى مزود خارجي، أو كنت ترسل بحجم كبير ومستقر يكفي لبناء سمعة عنوانك، أو تريد سجلاً كاملاً لكل رسالة تحت يدك، فـ Postal هو الأنسب، بشرط أن تخصص له سيرفراً بعنوان IP نظيف.
ما تحتاجه قبل أن تبدأ Requirements
السيرفر
- سيرفر مخصص Dedicated لـ Postal وحده: صفحة المتطلبات توصي بشدة ألا يعمل على السيرفر أي برنامج آخر، والسبب أن Postal يعمل بشبكة السيرفر مباشرة Host Network ويحتاج إلى المنافذ 25 و80 و443، وأن عنوان IP هو سمعة الإرسال نفسها، فلا تخلطه بتطبيقات أخرى.
- الذاكرة RAM والمعالج CPU والقرص Disk: الحد الأدنى في التوثيق 4 GB من الذاكرة ونواتان و25 GB من القرص، وزد القرص بحسب حجم الرسائل ومدة الاحتفاظ بها كما سيأتي.
- المنفذ 25 الصادر Outbound مفتوح، وسجل PTR: كثير من المزودين يحجبون المنفذ 25 ولا يسمحون بتعديل PTR إلا بطلب، فتحقق من الأمرين قبل الشراء كما شرحنا في دليل اختيار خادم VPS، وتأكد أن عنوان IP ليس في قوائم الحظر، فالتوثيق نفسه ينبه إلى أن العنوان الذي يعطيك إياه المزود قد يكون محظوراً من مستخدم سابق.
- النظام: أي نظام يعمل عليه Docker، ونحن نفترض Ubuntu 24.04 مع Docker Engine وإضافة Docker Compose كما في دليل تثبيت Docker على Ubuntu، وإذا كان السيرفر جديداً فابدأ بـ دليل تأمين خادم VPS من أول دخول.
وتأكد من المنفذ 25 الصادر قبل أي شيء آخر، فإذا انتهت المهلة Timeout دون اتصال فالمزود يحجبه، ولا فائدة من أي إعداد بعد ذلك:
nc -vz -w 5 gmail-smtp-in.l.google.com 25عنوان IP جديد يحتاج إلى تسخين Warm-up
السيرفرات الكبيرة مثل Gmail وOutlook تتعامل بحذر مع عنوان IP ليس له تاريخ إرسال، فإذا بدأ عنوان جديد يرسل آلاف الرسائل في يومه الأول فسوف تؤخرها أو تضعها في Spam، وهذا ما يذكره التوثيق صراحة بأن العناوين الجديدة التي ترسل بحجم كبير لن تصل رسائلها جيداً في البداية. لذلك ابدأ بالرسائل التي ينتظرها المستلم فعلاً، مثل رموز التحقق واستعادة كلمة المرور، فهي التي تفتح ويرد عليها، وزد الحجم تدريجياً على مدى أسابيع، واستخدم حد الإرسال Send Limit الذي يضبطه المدير لكل خادم بريد من Settings › Advanced Settings كما في صفحة إعدادات الخادم، فإذا تجاوز التطبيق العدد المسموح في الساعة يحجز Postal الرسائل الزائدة Held بدل أن يرسلها، ويرسل لك تنبيهاً.
المنافذ: ما الذي يفتح للإنترنت؟
| المنفذ | الخدمة | القرار |
|---|---|---|
| 25/tcp | SMTP في Postal | مفتوح، وبه يستقبل الارتدادات والبريد الوارد، ويقبل عليه أيضاً رسائل تطبيقاتك بعد المصادقة Authentication |
| 587/tcp | SMTP إضافي للتطبيقات | اختياري، ولا يستمع عليه Postal افتراضياً، ونضيفه في قسم الإرسال عبر SMTP |
| 80 و443/tcp | Caddy | مفتوحان، للواجهة وAPI ولإصدار الشهادة |
| 5000/tcp | واجهة Postal دون تشفير | على 127.0.0.1 فقط، ويصل إليها Caddy من داخل السيرفر |
| 3306/tcp | MariaDB | على 127.0.0.1 فقط، ولا يفتح للإنترنت أبداً |
| 9090 و9091/tcp | فحص الصحة Health Check للعامل و SMTP | على 127.0.0.1 افتراضياً |
ولأن Containers الخاصة بـ Postal تعمل بشبكة السيرفر مباشرة، فالمنفذ الذي يستمع عليه البرنامج هو المنفذ المفتوح فعلاً على السيرفر دون أي ربط Publish من Docker، لذلك فقواعد جدار الحماية Firewall هنا تعمل كما تتوقع:
sudo ufw allow 25,587,80,443/tcpسجلات DNS الخاصة بالمنصة نفسها
هنا نوعان من السجلات يخلط بينهما كثيرون، فالأول سجلات المنصة نفسها، وتنشرها مرة واحدة قبل التثبيت لاسم السيرفر مثل postal.example.com، والثاني سجلات كل نطاق ترسل منه تطبيقاتك، وسوف يولدها Postal لك بعد التثبيت. ولن نشرح هنا معنى كل نوع من السجلات، فقد شرحنا ذلك في شرح DNS وسجلاته للمبتدئين وشرح SPF وDKIM وDMARC للمبتدئين، وإنما نكتب ما يطلبه Postal بالضبط بحسب صفحة إعداد DNS، وضع عنوان سيرفرك مكان 203.0.113.10:
postal.example.com. A 203.0.113.10
mx.postal.example.com. A 203.0.113.10
spf.postal.example.com. TXT "v=spf1 ip4:203.0.113.10 ~all"
rp.postal.example.com. A 203.0.113.10
rp.postal.example.com. MX 10 postal.example.com.
rp.postal.example.com. TXT "v=spf1 a mx include:spf.postal.example.com ~all"
postal._domainkey.rp.postal.example.com. TXT (القيمة من الأمر postal default-dkim-record بعد التثبيت)
routes.postal.example.com. MX 10 postal.example.com.
track.postal.example.com. A 203.0.113.10في السجلات أعلاه لاحظ التالي:
- spf.postal.example.com: سجل SPF مركزي فيه عناوين المنصة، وكل نطاق ترسل منه يضيف
include:spf.postal.example.comإلى سجله، فإذا غيرت عنوان IP يوماً ما عدلت هذا السجل وحده. - rp.postal.example.com: نطاق مسار الرجوع Return Path الافتراضي، وهو العنوان الذي تعود إليه الارتدادات، ويوقع به Postal الرسائل بمفتاح المنصة إذا لم يكن مفتاح DKIM الخاص بنطاقك جاهزاً بعد، لذلك يحتاج إلى سجل DKIM خاص به سوف نولده بعد التثبيت.
- routes وtrack: الأول لاستقبال البريد الذي تحوله إلى المسارات، والثاني لتتبع الفتح والنقرات Open and Click Tracking، وإذا لم تكن تحتاجهما الآن فاتركهما.
- PTR: اضبطه لدى مزود السيرفر على
postal.example.comتماماً، وهو نفس الاسم الذي يقدمه Postal في أمر EHLO، وإذا كنت تستخدم Cloudflare فاجعل هذه السجلات على وضع DNS only، لأن ال Proxy لا يمرر SMTP.
التثبيت بالأداة الرسمية postal
طريقة التثبيت الرسمية ليست ملف Compose تكتبه بيدك، وإنما مستودع مساعد التثبيت وفيه سكربت Bash باسم postal يولد ملف Compose من قالب Template لكل إصدار، ويغلف أوامر Docker Compose في أوامر قصيرة مثل postal start وpostal upgrade كما في صفحة الأمر postal، وسوف نلتزم بها لأن التحديث لاحقاً يعتمد عليها.
الأدوات ومستودع التثبيت
بعد تثبيت Docker ثبت الأدوات التي يحتاجها السكربت، ثم انسخ المستودع واجعل الأمر postal متاحاً في كل مكان:
sudo apt install -y git curl jq
sudo git clone https://github.com/postalserver/install /opt/postal/install
sudo ln -s /opt/postal/install/bin/postal /usr/bin/postalقاعدة البيانات MariaDB
يحتاج Postal إلى MariaDB بالإصدار 10.6 أو أحدث، ولا يدعم MySQL بدلاً منها، ويحتاج إلى صلاحية إنشاء قواعد البيانات، والسبب أنه ينشئ قاعدة مستقلة باسم postal-server-N لكل خادم بريد تنشئه في الواجهة. أنشئ كلمة مرور قوية واحفظها في مدير كلمات المرور Password Manager:
openssl rand -hex 24ثم شغل MariaDB كما في التوثيق مع ثلاثة تعديلات سوف نشرحها، وضع كلمة المرور مكان DB_PASSWORD:
docker run -d \
--name postal-mariadb \
-p 127.0.0.1:3306:3306 \
--restart always \
-v postal-mariadb:/var/lib/mysql \
-e MARIADB_DATABASE=postal \
-e MARIADB_ROOT_PASSWORD=DB_PASSWORD \
mariadb:11.8.9 \
--innodb-log-file-size=256Mفي الإعداد أعلاه لاحظ التالي:
- الوسم Tag: ثبتنا
mariadb:11.8.9بدلmariadbدون وسم كما في التوثيق، والسبب أن الأمر دون وسم يسحب أحدث إصدار رئيسي في أي يوم تعيد فيه إنشاء ال Container، وقاعدة البيانات آخر ما تريد أن يتحدث دون قرار منك. - ال Volume باسم: أمر التوثيق لا يحدد Volume، فتذهب البيانات إلى Volume مجهول الاسم Anonymous Volume يضيع أثره إذا حذفت ال Container وأنشأته من جديد، أما
postal-mariadbفتعرفه وتنسخه وتنقله. - حجم سجل InnoDB: تنبه صفحة المتطلبات إلى أن
innodb_log_file_sizeيجب أن يكون عشرة أضعاف أكبر رسالة ترسلها على الأقل، وأكبر رسالة يقبلها Postal افتراضياً 14 MB، لذلك رفعناه إلى 256M. - المنفذ 3306: مربوط بالعنوان
127.0.0.1فقط، لأن Postal يعمل بشبكة السيرفر ويصل إليه من هناك، ولا يوجد سبب ليراه أحد من الخارج.
توليد الإعداد بالأمر postal bootstrap
الآن نولد ملفات الإعداد لاسم السيرفر، ونمرر رقم الإصدار صراحة حتى نعرف ما الذي نثبته بدل أن يسأل السكربت GitHub عن أحدث إصدار:
sudo postal bootstrap postal.example.com --version 3.3.7والمخرج سوف يكون كما يلي:
Using version: 3.3.7
=> Creating /opt/postal/config/postal.yml
=> Creating /opt/postal/config/Caddyfile
=> Creating signing private keyثلاثة ملفات في /opt/postal/config: ملف الإعداد postal.yml، وملف Caddyfile للواجهة، ومفتاح التوقيع signing.key، وهو مفتاح RSA يوقع به Postal رسائل نطاق مسار الرجوع وطلبات Webhooks، فاعتبره من أهم ما على السيرفر. وولد السكربت أيضاً /opt/postal/install/docker-compose.yml من القالب، وفيه ال Image ghcr.io/postalserver/postal:3.3.7 للخدمات الثلاث.
افتح الآن /opt/postal/config/postal.yml، وسوف تجد أنه يحتاج إلى ثلاثة تعديلات، والملف بعد التعديل سوف يكون كما يلي:
version: 2
postal:
web_hostname: postal.example.com
web_protocol: https
smtp_hostname: postal.example.com
main_db:
host: 127.0.0.1
username: root
password: DB_PASSWORD
database: postal
message_db:
host: 127.0.0.1
username: root
password: DB_PASSWORD
prefix: postal
smtp_server:
default_bind_address: "::"
dns:
mx_records:
- mx.postal.example.com
spf_include: spf.postal.example.com
return_path_domain: rp.postal.example.com
route_domain: routes.postal.example.com
track_domain: track.postal.example.com
smtp:
host: 127.0.0.1
port: 25
username:
password:
from_name: Postal
from_address: [email protected]
rails:
secret_key: القيمة-التي-ولدها-السكربتفي الإعداد أعلاه لاحظ التالي:
- كلمة مرور قاعدة البيانات: القالب يأتي بكلمة المرور
postalفي القسمينmain_dbوmessage_db، فضع مكانها كلمة المرور التي ولدتها. - قسم
smtp: هذا ليس إعداد الإرسال لتطبيقاتك، وإنما السيرفر الذي يرسل عبره Postal رسائله الخاصة، مثل استعادة كلمة مرور الواجهة وتنبيهات حد الإرسال. والقالب يضع المنفذ2525بينما يستمع Postal على 25، لذلك غيرناه، وسوف نملأ اسم المستخدم وكلمة المرور بعد أن ننشئ بيانات دخول في الواجهة. - العنوان
from_address: بعد الأمرbootstrapستجد القيمةpostal.example.comدون علامة @، والسبب أن السكربت يستبدل النصpostal.yourdomain.comبتعبيرsedتطابق فيه النقطة أي حرف، فتطابق أيضاً[email protected]وتحولها إلى اسم السيرفر، فصححها بيدك إلى عنوان كامل. - قسم
dns: نفس الأسماء التي نشرتها في DNS، فإذا اخترت أسماء أخرى فاكتبها هنا قبل الخطوة التالية، والقائمة الكاملة للخيارات في ملف الإعداد المرجعي.
تهيئة قاعدة البيانات والمستخدم الأول
الأمر التالي يسحب ال Image وينشئ الجداول في قاعدة postal:
sudo postal initializeوالمخرج سوف ينتهي بما يلي:
Initializing database
Loading config from /config/postal.yml
Database 'postal' already exists
No schema migrations exist. Loading schema with db:schema:loadثم أنشئ المدير الأول Global Administrator، والأمر يسألك عن البريد والاسم وكلمة المرور:
sudo postal make-userوالمخرج سوف يكون كما يلي:
Postal User Creator
Enter the information required to create a new Postal user.
This tool is usually only used to create your initial admin user.
E-Mail Address : [email protected]
First Name : Admin
Last Name : User
Initial Password : ****************
User has been created with e-mail address [email protected]وهذا الأمر ينشئ مديراً في كل مرة، فإذا أغلقت على نفسك الدخول يوماً ما فهو طريقك للعودة. والآن اطبع سجل DKIM الخاص بنطاق مسار الرجوع، وانشره باسم postal._domainkey.rp.postal.example.com كما في جدول السجلات أعلاه:
sudo postal default-dkim-recordوالمخرج سوف يكون سطراً واحداً بهذا الشكل، والمفتاح عندك مختلف:
v=DKIM1; t=s; h=sha256; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCnGFfFLoVr69y7u4F9wX1C2CE1EZEf3weso0pwMbZErJiiiv1roifep6ugxQ7qYUBlJ+RPtjcERbCyWx+1ncViUuo6K8WPihYbMp+hxd2e8jhmkwQ/gwGAVswhx4CGKQV71KJgvBP1vPTwxRKPDXGAOc8HntW0mzo5uW4hbIEqQIDAQAB;تشغيل Postal وCaddy
الآن سوف نقوم بالتشغيل:
sudo postal start
sudo postal statusستجد ثلاث Containers باسم المشروع postal هي postal-web-1 وpostal-smtp-1 وpostal-worker-1، وإذا ظهر تحذير أن الحقل version في ملف Compose قديم Obsolete فهو تحذير من Docker Compose عن سطر في القالب، ولا يؤثر على التشغيل. وتحقق من السجل أن SMTP يستمع على المنفذ 25:
sudo postal logs smtp | grep Listeningوالمخرج سوف يكون مشابهاً لما يلي:
smtp-1 | 2026-10-05 05:24:22 +0000 INFO Listening on :::25 component=smtp-serverبقيت الواجهة، فهي تستمع على 127.0.0.1:5000 دون تشفير، ويقدمها للإنترنت Caddy الذي يصدر شهادة Let's Encrypt ويجددها بنفسه، والملف Caddyfile الذي ولده bootstrap يوجه postal.example.com إلى 127.0.0.1:5000، فشغله كما في صفحة التثبيت مع تثبيت الوسم:
docker run -d \
--name postal-caddy \
--restart always \
--network host \
-v /opt/postal/config/Caddyfile:/etc/caddy/Caddyfile \
-v /opt/postal/caddy-data:/data \
caddy:2.11.6بعد دقيقة افتح https://postal.example.com وادخل بحساب المدير، وإذا كان لديك Reverse Proxy على السيرفر يملك المنفذين 80 و443 فلا تشغل Caddy، وانتقل إلى قسم الشهادة خلف Reverse Proxy.
المؤسسة الأولى وخادم البريد
بعد الدخول تعرض الصفحة الرئيسية الزر Create the first organization، فاكتب اسم المؤسسة مثل Example Inc ومعرفها المختصر Permalink مثل example، ثم من صفحة المؤسسة اضغط Build your first mail server، واكتب الاسم App Notifications والمعرف app، واختر الوضع Mode:
- Live: ترسل الرسائل إلى المستلمين فعلاً.
- Development: تحجز كل رسالة Held ولا تخرج، ولكنها تظهر في السجل بمحتواها، وهذا مفيد جداً لخادم بريد مخصص لبيئة الاختبار، فلا يصل بريد تجريبي إلى عميل حقيقي بالخطأ.
ولاحظ أن إنشاء خادم البريد قد يستغرق دقيقة، لأن Postal ينشئ له قاعدة بيانات كاملة في MariaDB. والصورة التالية تبين صفحة الخادم بعد إرسال الرسائل التجريبية التي سوف نرسلها في الأقسام التالية:

والقائمة في الأعلى هي كل ما يخص هذا الخادم: Messages للسجل، وDomains للنطاقات، وRouting للبريد الوارد، وCredentials لبيانات الدخول، وWebhooks، وSettings. والتحذير الأحمر 1 domain has misconfigured DNS records سببه أن سجلات النطاق الذي أضفناه لم تنشر بعد، وهو ما نعالجه في القسم التالي.
إضافة النطاق والسجلات التي يطلبها Postal
لا يرسل Postal من أي عنوان إلا إذا كان نطاقه مضافاً إلى خادم البريد أو إلى المؤسسة ومتحققاً منه. افتح Domains ثم Add new domain واكتب example.com، وإذا كنت المدير فالنطاق يعتبر متحققاً منه مباشرة، أما المستخدم العادي فيثبت ملكيته بسجل TXT قيمته postal-verification ثم رمز عشوائي، أو برمز يصل إلى عنوان مثل postmaster@ كما في صفحة نطاقات الإرسال. وبعد الإضافة تفتح صفحة DNS Setup مباشرة:

example.com: سجل SPF، ثم سجل DKIM باسم المحدد الذي ولده Postal، ثم CNAME لمسار الرجوع، وفي الأسفل MX للبريد الواردوالسجلات التي تعرضها الصفحة بصيغة نصية كما يلي، مع اختصار المفتاح:
example.com. TXT "v=spf1 a mx include:spf.postal.example.com ~all"
postal-OA6ryL._domainkey.example.com. TXT "v=DKIM1; t=s; h=sha256; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQD6ElQQ…IDAQAB;"
psrp.example.com. CNAME rp.postal.example.com.
example.com. MX 10 mx.postal.example.com.في السجلات أعلاه لاحظ التالي:
- SPF: إذا كان لنطاقك سجل SPF لبريد الموظفين فلا تنشئ سجلاً ثانياً، وإنما أضف
include:spf.postal.example.comإلى السجل الموجود، والواجهة نفسها تقول ذلك، وإذا لم يجد Postal هذا الجزء يعرض الحالةInvalid. - DKIM: يولد Postal لكل نطاق مفتاحاً خاصاً به، واسم المحدد Selector يبدأ بـ
postal-ثم ستة حروف عشوائية، فانسخ الاسم والقيمة من صفحتك وليس من هنا. - مسار الرجوع psrp: اختياري، ولكنه يجعل عنوان المغلف Envelope Sender على نطاقك بدل
rp.postal.example.com، وبدونه لا يتطابق SPF مع نطاق المرسل عند فحص DMARC. وPostal لا يولد سجل DMARC، فسياسة DMARC لنطاقك تكتبها أنت كما شرحنا في مقال SPF وDKIM وDMARC. - MX: فقط إذا أردت أن يستقبل Postal بريد هذا النطاق، ولا تضفه أبداً إلى نطاق عليه صناديق بريد الموظفين، لأن البريد الوارد سوف يذهب إلى Postal بدل صناديقهم. وهذا سبب إضافي لما أوصينا به في مقال البريد التفاعلي من أن ترسل من نطاق فرعي Subdomain مثل
notify.example.com، فإذا فعلت ذلك فأضفه هو في Postal بدلexample.com، ولاحظ أن Postal يطابق النطاق حرفياً، فالإرسال منnotify.example.comيحتاج إلى إضافته هو تحديداً.
بعد النشر اضغط Check my records are correct، ويعيد Postal الفحص تلقائياً كل ساعة، ويستعلم من سيرفرات الأسماء الرسمية Authoritative Nameservers للنطاق مباشرة، فلا تنتظر انتهاء التخزين المؤقت Cache. والنطاق يعتبر جاهزاً عندما يكون SPF وDKIM بالحالة OK.
وماذا يحدث إذا أرسلت قبل أن يصبح DKIM جاهزاً؟ لا يرفض Postal الرسالة، وإنما يوقعها بمفتاح المنصة على نطاق مسار الرجوع، وهذه ترويسات رسالة خرجت قبل نشر سجلات example.com، مع حذف قيم التوقيع وبعض الأسطر:
Return-Path: <[email protected]>
Resent-Sender: [email protected]
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
d=rp.postal.example.com;
s=postal; t=1791178923;
h=date:to:from:subject:message-id;
X-Postal-MsgID: ZLheqYCxZXBrOUr3
From: [email protected]لاحظ أن التوقيع بالنطاق d=rp.postal.example.com والمحدد s=postal، أي بالسجل الذي نشرته من postal default-dkim-record، بينما المرسل في From هو example.com، وبالتالي فالتوقيع صحيح ولكنه لا يتطابق مع نطاق المرسل، وسوف تفشل الرسالة في DMARC لدى من يطبقه. لذلك لا ترسل بريداً حقيقياً من نطاق قبل أن يصبح DKIM الخاص به OK، وعندها يصبح التوقيع d=example.com.
بيانات الدخول لكل تطبيق Credentials
افتح Credentials ثم Add another credential، واختر النوع واكتب اسماً يدل على التطبيق، واترك الحقل Key فارغاً ليولده Postal، وفي الحقل Hold تستطيع أن تجعل كل رسائل هذا المفتاح تحجز دون إرسال، وهذا مفيد لبيئة اختبار تشارك نفس الخادم. أنشأنا مفتاحين، الأول web-app من نوع SMTP للتطبيق الذي يرسل عبر SMTP، والثاني backend من نوع API:

والقاعدة هنا مفتاح لكل تطبيق، والسبب أنك إذا اشتبهت في تسريب مفتاح فتحذفه وحده دون أن يتوقف بقية التطبيقات، وفي صفحة كل رسالة يظهر اسم المفتاح الذي أرسلها، فتعرف مباشرة أي تطبيق أرسل رسالة غريبة. ولاحظ أن المفتاح يعمل على خادم البريد كله، أي على كل نطاقاته، ولا يوجد في Postal مفتاح مقيد بنطاق واحد، فإذا أردت فصلاً أشد بين تطبيقين فاجعل لكل واحد منهما خادم بريد مستقلاً. أما النوع SMTP-IP فيسمح بالإرسال من عنوان أو شبكة دون كلمة مرور، والتوثيق يحذر من أنه قد يجعل سيرفرك مفتوحاً للترحيل Open Relay إذا وضعت شبكة أوسع من اللازم، فلا تستخدمه إلا لعنوان سيرفر تعرفه.
والآن عد إلى قسم smtp في postal.yml وضع في password مفتاح SMTP، وفي username أي قيمة مثل example/app، ثم أعد التشغيل واختبر رسائل المنصة نفسها:
sudo postal restart
sudo postal test-app-smtp [email protected]الإرسال عبر SMTP
أغلب التطبيقات تعرف SMTP، ويكفيها أربعة إعدادات: السيرفر postal.example.com، والمنفذ، واسم المستخدم، وكلمة المرور. ويقبل Postal في المصادقة الطرق PLAIN وLOGIN وCRAM-MD5، ومع PLAIN وLOGIN يمكن أن يكون اسم المستخدم أي نص، وكلمة المرور هي قيمة المفتاح، أما مع CRAM-MD5 فاسم المستخدم هو معرف المؤسسة ومعرف الخادم بالصيغة example/app. ولكن قبل أن تعطي التطبيق هذه البيانات هناك أمران يجب أن تصلحهما.
تفعيل TLS على SMTP
يعمل SMTP في Postal افتراضياً دون TLS كما تنص صفحة SMTP TLS، وهذا يعني أن مفتاح التطبيق ومحتوى الرسائل يمران دون تشفير إذا كان التطبيق على سيرفر آخر، وأن السيرفرات الأخرى لا تستطيع تسليمك الارتدادات بتشفير. والحل أن تضع شهادة لاسم السيرفر في الملفين /opt/postal/config/smtp.cert و/opt/postal/config/smtp.key بصيغة PEM، ثم تفعل TLS في postal.yml:
smtp_server:
default_bind_address: "::"
tls_enabled: trueوبما أن Caddy يصدر شهادة Let's Encrypt لنفس الاسم postal.example.com، فأسهل مصدر للشهادة هو مجلد بيانات Caddy، والسكربت التالي ينسخها إلى مجلد الإعداد إذا تغيرت ويعيد تشغيل خدمات SMTP فقط، فاحفظه في /usr/local/bin/postal-smtp-cert وأضفه إلى crontab الخاص بالمستخدم root ليعمل يومياً:
#!/bin/bash
set -e
SRC=/opt/postal/caddy-data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/postal.example.com
if ! cmp -s "$SRC/postal.example.com.crt" /opt/postal/config/smtp.cert; then
install -o 999 -g 999 -m 644 "$SRC/postal.example.com.crt" /opt/postal/config/smtp.cert
install -o 999 -g 999 -m 600 "$SRC/postal.example.com.key" /opt/postal/config/smtp.key
postal dc restart smtp
fiفي السكربت أعلاه لاحظ التالي:
- البرنامج داخل ال Container يعمل بالمستخدم
postalورقمه UID 999 كما في ملف Dockerfile، بينما ملف المفتاح الذي يكتبه Caddy لا يقرؤه إلا root، لذلك ننسخه بالأمرinstallبملكية 999 حتى يستطيع Postal قراءته دون أن نفتحه لبقية المستخدمين. - يقرأ Postal الشهادة عند التشغيل فقط، وبدون إعادة التشغيل يستمر بالشهادة القديمة حتى تنتهي صلاحيتها، ولاحظ أن
postal dcيمرر أي أمر إلى Docker Compose، فنعيد تشغيل SMTP وحده دون الواجهة. - إذا أضفت خدمة المنفذ 587 في الخطوة التالية فأضف اسمها
smtp-submissionبعدsmtpفي سطر إعادة التشغيل.
المنفذ 587 للتطبيقات
يستمع Postal على المنفذ 25 فقط، وتستطيع تطبيقاتك أن ترسل عليه بعد المصادقة، ولكن كثيراً من مزودي السحابة يحجبون المنفذ 25 الصادر من سيرفرات التطبيقات نفسها، وبعض المكتبات تفترض 587 للإرسال، لذلك نضيف عملية SMTP ثانية على 587. ويسمح Postal بذلك عبر متغير البيئة PORT كما في صفحة الإعداد، ولكن ملف docker-compose.yml يولد من جديد مع كل تحديث، لذلك نضع الخدمة في ملف /opt/postal/install/docker-compose.override.yml الذي يدمجه Docker Compose تلقائياً ولا يلمسه التحديث:
services:
smtp-submission:
extends:
file: docker-compose.yml
service: smtp
environment:
PORT: "587"
SMTP_SERVER_DEFAULT_HEALTH_SERVER_PORT: "9092"في الإعداد أعلاه لاحظ التالي:
extendsيأخذ تعريف الخدمةsmtpكما هو من الملف المولد، بما في ذلك ال Image ورقم إصداره، وبالتالي فعندما يغيرpostal upgradeالإصدار فيdocker-compose.ymlتتبعه هذه الخدمة دون أن تعدل شيئاً.- غيرنا منفذ فحص الصحة إلى 9092، والسبب أن الخدمتين تعملان بشبكة السيرفر نفسها، وكلاهما يحاول افتراضياً أن يستمع على 9091، فتفشل الثانية.
ثم شغل الخدمة الجديدة، وتحقق من السجل:
sudo postal start
sudo postal logs smtp-submission | grep Listeningوالمخرج سوف يكون مشابهاً لما يلي:
smtp-submission-1 | 2026-10-05 05:40:07 +0000 INFO Listening on :::587 component=smtp-serverإرسال أول رسالة عبر SMTP
الآن نرسل من سيرفر التطبيق رسالة استعادة كلمة مرور بأداة swaks (تثبتها بالأمر sudo apt install swaks) على المنفذ 587 مع STARTTLS، وضع مفتاح SMTP مكان كلمة المرور، و[email protected] عنوان مستلم تملكه:
swaks --server postal.example.com --port 587 --ehlo app.example.com --tls \
--auth PLAIN --auth-user web-app --auth-password 'CvGcSGVHYdceAefrukViRRJz' \
--from [email protected] --to [email protected] \
--header 'Subject: Verify your email address' \
--body 'Your verification code is 482913.'والمخرج سوف يكون كما يلي، وهنا الأسطر المهمة فقط:
<- 220 postal.example.com ESMTP Postal/723HDHIY
<- 250-My capabilities are
<- 250-STARTTLS
<- 250 AUTH CRAM-MD5 PLAIN LOGIN
<- 220 Ready to start TLS
=== TLS started with cipher TLSv1.3:TLS_AES_256_GCM_SHA384:256
<~ 250-My capabilities are
<~ 250 AUTH CRAM-MD5 PLAIN LOGIN
<~ 235 Granted for example/app
<~ 250 OK
<~ 250 OK
<~ 354 Go ahead
<~ 250 OK
<~ 221 Closing Connectionفي المخرج أعلاه لاحظ التالي:
- السطر
250-STARTTLSلا يظهر إلا بعد تفعيل TLS، فإذا لم تجده فالسيرفر يقبل كلمة المرور دون تشفير، والأسطر التي تبدأ بـ<~هي ما بعد بدء التشفير. - الرد
235 Granted for example/appيخبرك أي مؤسسة وأي خادم بريد يملك المفتاح، وهذا مفيد عندما يكون لديك عدة خوادم وتريد أن تتأكد أن التطبيق يستخدم المفتاح الصحيح. - الرد
250 OKبعد البيانات يعني أن Postal قبل الرسالة ووضعها في الطابور، وليس أنها وصلت، فالتسليم يقوم به ال Worker بعد ذلك، ونتيجته في السجل وفي Webhooks.
والآن لنكسر الأمر لنفهمه، فنغير ثلاثة أشياء في نفس الأمر واحداً واحداً، وهذه ردود Postal:
# --from [email protected] (نطاق غير مضاف إلى الخادم)
<** 530 From/Sender name is not valid
# --auth-password بمفتاح خاطئ
<** 535 Invalid credential
# دون --auth إلى عنوان خارجي
<** 530 Authentication requiredالأول هو أشهر خطأ عند ربط تطبيق جديد، والسبب أن التطبيق يرسل باسم عنوان على نطاق لم تضفه إلى هذا الخادم، أو على نطاق فرعي لم تضفه بعينه. والثالث هو ما تريد أن تراه، أي أن سيرفرك لا يرحل بريداً لأحد دون مصادقة.
الإرسال عبر HTTP API
إذا كان تطبيقك يستطيع أن يرسل طلب HTTP فال API أبسط من SMTP في أمور كثيرة: يعود لك مباشرة رقم الرسالة الذي تخزنه مع الطلب في قاعدة بياناتك، وتضيف وسماً Tag يصنف الرسالة في السجل. ويرسل الطلب بصيغة JSON مع المفتاح في الترويسة X-Server-API-Key كما في توثيق ال API، والحقول كلها في مرجع الإرسال:
curl -s -X POST https://postal.example.com/api/v1/send/message \
-H 'X-Server-API-Key: HiyP7aQQjhacodqgHsxbgXGl' \
-H 'Content-Type: application/json' \
-d '{"to":["[email protected]"],"from":"[email protected]","subject":"Your order has shipped","plain_body":"Order 1042 is on its way.","tag":"shipping"}'والمخرج سوف يكون كما يلي:
{"status":"success","time":0.99,"flags":{},"data":{"message_id":"[email protected]","messages":{"[email protected]":{"id":2,"token":"dl5RdM1uAMzlrgor"}}}}لاحظ أن الرد يعطيك رقماً لكل مستلم في messages، فإذا أرسلت إلى ثلاثة مستلمين فهي ثلاث رسائل منفصلة في Postal لكل واحدة نتيجتها، وهذا الرقم id هو ما يصلك لاحقاً في Webhooks، فخزنه. ولاحظ أيضاً أن ال API يعيد الحالة HTTP 200 حتى عند الخطأ، والخطأ في الحقل status، فلا يكفي في الكود أن تفحص رمز HTTP:
{"status":"error","time":0.002,"flags":{},"data":{"message":"The API token provided in X-Server-API-Key was not valid.","token":"wrongkey","code":"InvalidServerAPIKey"}}
{"status":"error","time":0.053,"flags":{},"data":{"message":"The From address is not authorised to send mail from this server","code":"UnauthenticatedFromAddress"}}سجل الرسائل: ما الذي حدث لكل رسالة؟
كل رسالة تقبلها المنصة تظهر في Messages › Outgoing Messages بحالتها، وتبحث فيها بالمستلم أو الوسم أو الحالة:

SENTوفي صفحة كل رسالة تبويب Activity بمحاولات التسليم، وفيه رد سيرفر المستلم كما هو، وهذه أسطر ال Worker لنفس الرسالة من الأمر postal logs worker:
INFO Sending message 1::2 to [email protected] log_id=ITQYXPYO component=worker
INFO Accepted by 192.168.80.2:1025 (mailpit) for [email protected] log_id=ITQYXPYO component=worker
INFO message processing complete component=workerولاحظ أن هذه الأسطر من سيرفر يوجه كل البريد الصادر إلى سيرفر SMTP وسيط عبر الخيار postal.smtp_relays، وهو هنا Mailpit المحلي، لذلك ترى mailpit مكان سيرفر المستلم. وعلى سيرفرك سوف ترى اسم سيرفر MX الخاص بالمستلم ورده مثل 250 2.0.0 OK. وإذا كانت الحالة SoftFail فـ Postal يعيد المحاولة بفواصل تزيد تدريجياً حتى 18 محاولة، وإذا كانت HardFail فالمستلم رفض الرسالة نهائياً، وبعد فشلين نهائيين للعنوان نفسه خلال 24 ساعة يضعه Postal في قائمة الحظر Suppressions ويحجز ما يرسل إليه بعد ذلك 30 يوماً، وهذا يحمي سمعة عنوانك من تكرار الإرسال إلى عناوين غير موجودة.
نتائج التسليم والارتدادات عبر Webhooks
الرد 250 OK من Postal لا يخبر تطبيقك هل وصلت الرسالة، والطريقة الصحيحة لمعرفة ذلك أن يرسل Postal إلى تطبيقك طلب HTTP عند كل حدث. افتح Webhooks ثم أضف رابطاً في تطبيقك مثل https://app.example.com/postal/events، واختر كل الأحداث أو بعضها، وأهمها كما في صفحة Webhooks:
MessageSentعند التسليم، وMessageDelayedعند كل تأخير، وMessageDeliveryFailedعند الفشل النهائي، وMessageHeldعند الحجز.MessageBouncedعندما يصل ارتداد لرسالة قبلها سيرفر المستلم ثم أعادها، وفيه الرسالة الأصلية والارتداد معاً.DomainDNSErrorعندما يكتشف الفحص الدوري أن سجلات أحد النطاقات تعطلت، وهذا تنبيه مفيد جداً إذا عدل أحد سجلات DNS دون أن يخبرك.MessageLinkClickedوMessageLoadedإذا فعلت تتبع النقرات والفتح.
والطلب الذي يصل إلى تطبيقك بعد تسليم رسالة أرسلت عبر ال API سوف يكون كما يلي:
{"event":"MessageSent","timestamp":1791179215.160438,"payload":{"message":{"id":4,"token":"Hx1U6NMXAZHBwkPZ","direction":"outgoing","message_id":"[email protected]","to":"[email protected]","from":"[email protected]","subject":"Your invoice is ready","timestamp":1791179202.885616,"spam_status":"NotChecked","tag":"invoice"},"status":"Sent","details":"Message for [email protected] accepted by 192.168.80.2:1025 (mailpit)","output":"250 2.0.0 Ok: queued as 60fYlvoUrilHmnDA4N1mPI","sent_with_ssl":false,"timestamp":1791179214.844055,"time":0.08},"uuid":"d3a987cf-b6fe-4dab-8b33-3179e3cf26a7"}في الطلب أعلاه لاحظ التالي:
payload.message.idهو نفس الرقم الذي أعاده ال API عند الإرسال، فتربط به الحدث بالطلب في قاعدة بياناتك، وتعرف مثلاً أن رسالة التفعيل لمستخدم معين فشلت فتعرض له خيار تغيير البريد.- يحمل الطلب الترويسة
X-Postal-Signature-256، وهي توقيع RSA-SHA256 لمحتوى الطلب بمفتاحsigning.key، والمفتاح العام منشور علىhttps://postal.example.com/.well-known/jwks.json، فتحقق من التوقيع في تطبيقك قبل أن تثق بالطلب، والسبب أن رابط Webhook معروف لكل من يرى إعدادك، وأي شخص يستطيع أن يرسل إليه طلباً مزيفاً. - يجب أن يرد تطبيقك بسرعة برمز 2xx، وإذا فشل الطلب يعيد Postal المحاولة لاحقاً، فاجعل معالجة الحدث تتحمل وصوله مرتين.
والآن لنكسر الأمر لنفهمه، فلنفرض أن تطبيقك على شبكة داخلية وأن رابط Webhook يشير إلى عنوانه الخاص، فلن يصل أي طلب، وسوف تجد في سجل ال Worker ما يلي:
ERROR Received -4 status code. That's not OK. component=worker thread=work1 webhook=1 webhook_request=1
INFO Will retry 2026-10-05 05:46:24 UTC (this was attempt 1) component=worker thread=work1 webhook=1 webhook_request=1والسبب أن الإصدار 3.3.7 أضاف كما في ملاحظات الإصدار حماية من SSRF، أي أن Postal يرفض أن يرسل طلبات Webhooks ونقاط التسليم HTTP إلى العناوين الخاصة مثل 10.0.0.0/8 و192.168.0.0/16 و127.0.0.1 وعنوان بيانات السحابة 169.254.169.254، والرمز -4 هو رمزه الداخلي لطلب محظور. فإذا كان تطبيقك فعلاً على شبكة خاصة فأضف عنوانه أو اسمه صراحة إلى القائمة allowed_request_destinations في postal.yml ثم نفذ postal restart:
postal:
web_hostname: postal.example.com
web_protocol: https
smtp_hostname: postal.example.com
allowed_request_destinations:
- 10.0.0.5ولا تضف شبكة كاملة إلى هذه القائمة، والسبب أن كل مستخدم في أي مؤسسة يستطيع أن ينشئ Webhook، فإذا سمحت بالشبكة كلها فقد أعطيته طريقاً إلى كل خدماتك الداخلية.
البريد الوارد والمسارات Routes
إذا أردت أن يستقبل تطبيقك البريد، مثل الردود على تذاكر الدعم، فأضف سجل MX للنطاق كما في صفحة DNS Setup، ثم من Routing أنشئ نقطة تسليم Endpoint من نوع HTTP برابط في تطبيقك، ثم مساراً Route يربط عنواناً مثل support أو * لكل عناوين النطاق بها، فيرسل Postal كل رسالة واردة إلى تطبيقك طلب POST. وإذا كان للنطاق سيرفر بريد آخر فلا تغير MX، وإنما حول الرسائل منه إلى العنوان الخاص بالمسار على routes.postal.example.com كما يشرح توثيق المسارات. وينبه التوثيق نفسه إلى أن الخيار STARTTLS في نقاط التسليم من نوع SMTP لا يعمل في 3.3.7 بسبب خطأ في الكود ويتصرف كأنه دون تشفير، فاستخدم Auto أو TLS حتى يصلح.
الواجهة خلف Reverse Proxy
إذا كان على السيرفر Reverse Proxy يملك المنفذين 80 و443 مثل Nginx Proxy Manager أو Traefik أو Caddy الذي تستخدمه لتطبيقات أخرى، فلا تشغل Caddy الخاص بـ Postal، ووجه ال Proxy إلى الواجهة. والقاعدة الأولى أن ال Proxy يخدم HTTP فقط، أي الواجهة وال API وروابط التتبع، أما SMTP على 25 و587 فيبقى مباشرة من Postal، لأنه بروتوكول TCP وليس HTTP، ولأن فحص SPF على البريد الوارد يعتمد على عنوان المرسل الحقيقي.
والمسألة الثانية أن الواجهة تستمع على 127.0.0.1:5000 في شبكة السيرفر، بينما ال Proxy في Container على شبكة Docker لا يرى 127.0.0.1 الخاص بالسيرفر. والحل أن تجعل الواجهة تستمع على عنوان شبكة Docker الافتراضية في السيرفر بدل أن تفتحها على كل العناوين، ثم توجه ال Proxy إلى هذا العنوان، وتخبر Postal أن هذا ال Proxy موثوق حتى يقرأ عنوان المستخدم الحقيقي من الترويسات:
postal:
web_hostname: postal.example.com
web_protocol: https
smtp_hostname: postal.example.com
trusted_proxies:
- 172.16.0.0/12
web_server:
default_bind_address: 172.17.0.1في الإعداد أعلاه لاحظ التالي:
172.17.0.1هو عنوان السيرفر على الشبكةdocker0في التثبيت الافتراضي، فتأكد منه بالأمرip -4 addr show docker0، ثم وجه ال Proxy Host إلىhttp://172.17.0.1:5000، وإذا كانufwمفعلاً فاسمح بالمنفذ 5000 من شبكات Docker فقط، كما شرحنا في دليل شبكات Docker.- يجب أن يمرر ال Proxy الترويسة
Hostكما هي، لأن Postal يرفض أي طلب لا يطابقweb_hostnameكما سيأتي في المشكلات الشائعة، وNPM وTraefik يفعلان ذلك افتراضياً. - إذا فعلت تتبع النقرات فاسم التتبع مثل
click.example.comيحتاج إلى Proxy Host خاص يضيف الترويسةX-Postal-Track-Host: 1كما في توثيق التتبع.
تأمين المنصة
- واجهة الإدارة: من يدخل الواجهة يقرأ كل الرسائل ويولد مفاتيح جديدة ويرسل باسم نطاقاتك، لذلك لا تتركها مفتوحة للعالم إذا لم تكن مضطراً، وقيدها في ال Proxy بعناوين مكتبك، أو اجعلها لا تفتح إلا عبر شبكة WireGuard الخاصة، مع ملاحظة أن مسار ال API
/api/وروابط التتبع يجب أن تبقى متاحة للتطبيقات والمستلمين. - الصلاحيات Permissions: بحسب صفحة المستخدمين والصلاحيات فالمستخدم العادي في مؤسسة يملك صلاحية كاملة على كل خوادم البريد فيها، ولا توجد صلاحية قراءة فقط ولا صلاحية لخادم واحد، لذلك افصل الفرق في مؤسسات مختلفة، وقلل عدد المديرين. وإذا كان لديك نظام دخول موحد SSO فـ Postal يدعم OpenID Connect، ويمكنك أن تربطه بـ authentik.
- المفاتيح: مفتاح لكل تطبيق كما ذكرنا، وفي الكود ضعه في متغير بيئة أو مدير أسرار Secrets Manager، ولا تكتبه في المستودع، وإذا ظهر في سجل أو في رسالة دعم فاحذفه وأنشئ غيره.
- مفتاح التوقيع: الأمر
bootstrapيجعل صلاحيةsigning.keyالقيمة 644، أي يقرؤه أي مستخدم على السيرفر، ومن يملكه يستطيع أن يوقع رسائل باسم نطاق مسار الرجوع وطلبات Webhooks تقبلها تطبيقاتك، فاجعل ملكيته للمستخدم 999 الذي يعمل به Postal واقصر قراءته عليه بالأمرينsudo chown 999:999 /opt/postal/config/signing.keyوsudo chmod 600 /opt/postal/config/signing.key. - التحديثات الأمنية: الإصدار 3.3.7 أغلق ثغرة SQL Injection في قواعد بيانات الرسائل، وثغرة SSRF في Webhooks، كما في ملاحظات الإصدار، فلا تؤجل التحديثات، وراقب صفحة الإصدارات.
التحقق من النجاح Verification
- صفحة النطاق تعرض SPF وDKIM بالحالة
OK، والتحذير الأحمر في أعلى صفحة الخادم اختفى. - رسالة عبر SMTP ورسالة عبر ال API بالحالة
SENTفي السجل، وكل منهما يظهر عليها المفتاح الذي أرسلها. - أرسل رسالة إلى حساب Gmail وافتح Show original، وتأكد أن SPF وDKIM وDMARC بالحالة
PASS، وأن التوقيعd=example.comوليسd=rp.postal.example.com، وللتقييم الكامل أرسل إلى mail-tester.com كما يقترح التوثيق. - تطبيقك استقبل حدث
MessageSentوتحقق من توقيعه. - محاولة إرسال دون مصادقة إلى نطاق خارجي ترد بـ
530 Authentication required.
سجل PTR يعيد اسم السيرفر، والاسم يعيد نفس العنوان:
dig +short -x 203.0.113.10
dig +short A postal.example.comالنسخ الاحتياطي والاستعادة Restore
كل ما يحتاجه Postal في مكانين: قواعد البيانات في MariaDB، أي postal للإعداد والمستخدمين والنطاقات والمفاتيح، وقاعدة postal-server-N لكل خادم بريد وفيها الرسائل، ثم مجلد /opt/postal/config وفيه postal.yml وsigning.key وشهادة SMTP. ولا تنس signing.key بالذات، فإذا ضاع فسوف تحتاج إلى نشر سجل DKIM جديد لنطاق مسار الرجوع، وإلى تحديث المفتاح العام في كل تطبيق يتحقق من توقيع Webhooks. أما ملف docker-compose.override.yml إذا أضفته فاحفظه معهما.
والسكربت التالي ينسخ كل قواعد Postal بأداة mariadb-dump دون إيقاف الخدمة، والخيار --single-transaction يأخذ صورة متسقة من جداول InnoDB أثناء العمل، ثم ينسخ مجلد الإعداد:
#!/bin/bash
set -e
DB_PASSWORD='DB_PASSWORD'
OUT=/opt/backup/postal
mkdir -p "$OUT"
DBS=$(docker exec postal-mariadb mariadb -uroot -p"$DB_PASSWORD" -N -e "SHOW DATABASES LIKE 'postal%'")
docker exec postal-mariadb mariadb-dump -uroot -p"$DB_PASSWORD" \
--single-transaction --routines --databases $DBS | gzip > "$OUT/postal-db-$(date +%F).sql.gz"
tar czf "$OUT/postal-config-$(date +%F).tar.gz" -C /opt/postal config install/docker-compose.override.yml
chmod 600 "$OUT"/*في السكربت أعلاه لاحظ التالي:
- ملفات النسخ فيها الرسائل ومفتاح التوقيع، لذلك جعلنا صلاحيتها 600، ثم انقلها إلى تخزين خارجي Offsite Storage بأداة مثل restic، والسبب أن نسخة على قرص السيرفر نفسه لا تحميك إذا تعطل السيرفر.
- إذا لم تكن قد أضفت ملف
docker-compose.override.ymlفاحذف اسمه من سطرtar.
الاستعلام SHOW DATABASES LIKE 'postal%' يجمع القاعدة الرئيسية وكل قواعد خوادم البريد، فإذا أنشأت خادماً جديداً غداً دخل في النسخة تلقائياً، والمخرج في سيرفر عليه خادم بريد واحد كما يلي:
postal
postal-server-1وللاستعادة على سيرفر جديد ثبت Docker ومستودع التثبيت وMariaDB كما في قسم التثبيت بنفس كلمة المرور، ثم فك مجلد الإعداد إلى /opt/postal، واستورد قواعد البيانات، ثم شغل الإصدار نفسه:
sudo tar xzf postal-config-2026-10-05.tar.gz -C /opt/postal
gunzip -c postal-db-2026-10-05.sql.gz | docker exec -i postal-mariadb mariadb -uroot -p'DB_PASSWORD'
sudo postal set-version 3.3.7
sudo postal upgrade-db
sudo postal startالأمر set-version يولد ملف Compose للإصدار الذي أخذت منه النسخة، والتوثيق يوصي بالأمر upgrade-db بعد استعادة قاعدة من تثبيت آخر حتى تطبق أي تعديلات ناقصة على الجداول. وتذكر بعد الاستعادة أن عنوان IP تغير غالباً، فعدل سجل spf.postal.example.com وسجلات A وPTR، وإذا كان العنوان الجديد جديداً فعلاً فهو يحتاج إلى تسخين من البداية.
التحديث إلى إصدار أحدث
التحديث في Postal أمر واحد كما في صفحة التحديث، فهو يسحب آخر نسخة من مستودع التثبيت، ثم ال Image الجديد، ثم يطبق تعديلات قاعدة البيانات، ثم يعيد تشغيل كل الخدمات، ولكنه ليس تحديثاً دون توقف Zero Downtime، لذلك نفذه في وقت هادئ وبعد نسخة احتياطية:
cd /opt/postal/install
sudo git pull origin
sudo postal upgrade 3.3.7ولاحظ أننا كتبنا رقم الإصدار، والسبب أن postal upgrade دون رقم ينتقل إلى أحدث إصدار منشور على GitHub أياً كان، أما مع الرقم فأنت تقرأ ملاحظات الإصدار أولاً ثم تقرر، فضع مكان 3.3.7 الإصدار الذي تريد الانتقال إليه. وبعد التحديث تأكد من الإصدار الفعلي:
sudo postal versionوالمخرج سوف يكون كما يلي:
Loading config from /config/postal.yml
3.3.7ثم احذف ال Image القديم بوسمه، مثل docker image rm ghcr.io/postalserver/postal:3.3.6، فكل Image من Postal قرابة 1.3 GB. ولا تحدث MariaDB في نفس اليوم، بل حدثها في نافذة منفصلة بعد نسخة احتياطية، حتى إذا ظهرت مشكلة تعرف سببها.
مشكلات شائعة وحلولها Troubleshooting
الواجهة ترد 403 Forbidden
يقبل Postal الطلبات فقط إذا كانت الترويسة Host تطابق web_hostname في postal.yml، فإذا فتحت الواجهة بعنوان IP أو باسم آخر، أو كان ال Proxy يغير الترويسة، فالرد 403 دون أي صفحة خطأ، وفي سجل الواجهة postal logs web ستجد الطلبات بالرمز 403. افتحها بالاسم الذي كتبته في الإعداد، وإذا غيرت الاسم فعدل web_hostname ثم نفذ postal restart.
postal initialize يفشل بخطأ طويل من Rails
إذا انتهى المخرج بالسطر Tasks: TOP => db:create بعد صفحة من أسطر Ruby، فـ Postal لم يستطع الاتصال بـ MariaDB، والسبب الأشهر أنك نفذت الأمر قبل أن تكمل MariaDB تهيئتها في أول تشغيل، أو أن كلمة المرور في postal.yml لا تطابق MARIADB_ROOT_PASSWORD. انتظر حتى تجد في docker logs postal-mariadb السطر ready for connections، وراجع كلمة المرور، ثم أعد الأمر، فهو آمن للتكرار.
التطبيق يأخذ 530 From/Sender name is not valid
نطاق العنوان في From غير مضاف إلى خادم البريد الذي يملك المفتاح ولا إلى مؤسسته، أو أنه نطاق فرعي لم تضفه بعينه، أو أن النطاق مضاف ولم يتحقق منه بعد. وفي ال API نفس المشكلة تظهر بالرمز UnauthenticatedFromAddress. أضف النطاق إلى الخادم الصحيح، وتأكد أن التطبيق يستخدم مفتاح هذا الخادم.
Webhooks لا تصل والسجل يقول -4
الرابط يشير إلى عنوان خاص أو داخلي، وحماية SSRF في 3.3.7 تمنعه، فأضف العنوان بعينه إلى allowed_request_destinations كما في قسم Webhooks. وإذا كان الرمز -3 فالمشكلة في شهادة HTTPS لتطبيقك، فـ Postal يتحقق منها ولا يقبل شهادة موقعة ذاتياً Self-signed. وتجد تاريخ كل طلب ورده في Webhooks › View History.
رسائل استعادة كلمة مرور الواجهة لا تصل
هذه الرسائل تخرج عبر قسم smtp في postal.yml وليس عبر خادم بريد في الواجهة، فتأكد أن المنفذ 25 وليس 2525 كما في القالب، وأن from_address عنوان كامل بعلامة @ وليس postal.example.com الذي تركه bootstrap، وأن كلمة المرور مفتاح SMTP صالح ونطاق العنوان مضاف إلى خادم البريد، ثم اختبر بالأمر postal test-app-smtp.
رسائلك تصل إلى Spam
- التوقيع بنطاق المنصة: إذا وجدت
d=rp.postal.example.comفي ترويسة DKIM فسجل DKIM الخاص بنطاقك ليسOKبعد، فراجع صفحة DNS Setup. - PTR واسم EHLO: تأكد أن PTR يعيد
postal.example.com، وهو نفسsmtp_hostnameالذي يقدمه Postal للسيرفرات الأخرى. - عنوان IP في قوائم الحظر: افحصه لدى Spamhaus وMXToolbox.
- عنوان جديد بحجم كبير: خفف الحجم وارجع إلى قسم التسخين، واستخدم حد الإرسال حتى لا يغرق تطبيق واحد سمعة السيرفر كله.
الرسائل تبقى في الطابور Queue
افتح Messages › Queue ثم سجل ال Worker بالأمر postal logs worker، فإذا وجدت محاولات اتصال تنتهي مهلتها إلى المنفذ 25 لكل المستلمين فالمزود يحجب المنفذ الصادر، وارجع إلى اختبار nc في قسم المتطلبات. وإذا كانت الرسائل بالحالة Held وليست في الطابور، فالسبب أحد أسباب الحجز: الخادم في وضع Development، أو المفتاح مضبوط على Hold، أو المستلم في قائمة الحظر، أو تجاوز حد الإرسال، وتجد السبب في صفحة الرسالة.
الخلاصة
وصلنا لنهاية الموضوع، وأهم ما فيه:
- Postal منصة إرسال مثل Amazon SES على سيرفرك تحت ترخيص MIT، للبريد الذي ترسله تطبيقاتك وليس لصناديق الموظفين، وتختاره عندما تريد البيانات والسجل تحت يدك وتستطيع أن تبني سمعة عنوان IP بنفسك.
- ثبته بالأداة الرسمية
postalعلى سيرفر مخصص فيه المنفذ 25 مفتوح وPTR صحيح، مع MariaDB بوسم ثابت وVolume باسم، وصحح فيpostal.ymlكلمة المرور ومنفذ قسمsmtpوالعنوانfrom_address. - انشر سجلات المنصة مرة واحدة، ثم لكل نطاق انشر SPF وDKIM و
psrpالتي تعرضها صفحة DNS Setup، ولا ترسل بريداً حقيقياً قبل أن يصبح DKIM بالحالةOK، ولا تضع MX لـ Postal على نطاق عليه بريد الموظفين. - مفتاح لكل تطبيق، وفعل TLS على SMTP، وأضف المنفذ 587 بملف override، واستخدم ال API عندما تريد رقم الرسالة مباشرة، ثم Webhooks موقعة لتعرف ما حدث لكل رسالة.
- انسخ قواعد
postal%ومجلد الإعداد ومعهsigning.keyيومياً إلى تخزين خارجي، وحدث بالأمرpostal upgradeمع رقم الإصدار بعد قراءة ملاحظاته.
وإذا قررت أن البداية الأنسب لك خدمة مدارة، أو أردت أن تراجع المفاهيم التي بني عليها هذا الدليل، فهذه المقالات تكمل الصورة:
دليلالبريد التفاعلي Transactional Email: كيف تصل رسائل تطبيقك في وقتهارابط استعادة كلمة المرور الذي لا يصل يعني مستخدماً لا يستطيع الدخول، لذلك نشرح ما هو البريد التفاعلي ولماذا تفصله عن النشرات بنطاق وسمعة مستقلين، وكيف ترسله من تطبيقك بطابور وإعادة محاولة، ومتى تختار Amazon SES أو منصة تستضيفها بنفسك مثل Postal.
دليلإرسال بريد خادمك عبر Amazon SES مع mailcow وPostfix وStalwartإذا كان مزودك يحجب المنفذ 25 أو كانت رسائل سيرفرك الجديد تصل إلى Spam، فسوف نرسل البريد الصادر عبر Amazon SES ويبقى الاستقبال والصناديق على سيرفرك، مع DKIM وMAIL FROM وDMARC والارتدادات، والإعداد الدقيق لكل خادم بريد وأي تطبيق.
دليلشرح SPF وDKIM وDMARC للمبتدئين: كيف تثبت أن رسائلك منك فعلاًلماذا يستطيع أي شخص أن يرسل باسم نطاقك، وكيف جاء كل من SPF وDKIM وDMARC ليسد ثغرة تركها الذي قبله، مع قراءة السجلات جزءاً جزءاً وطرق اختبار بريدك بالخدمات المجانية.
دليلكيف تستضيف بريد مؤسستك بنفسك: دليل عملي من القرار إلى التشغيلمتى تستضيف بريد شركتك أو جامعتك بنفسك ومتى يكون البريد المستضاف أنسب، وما الذي تجهزه قبل البدء، وكيف تنقل المستخدمين على مراحل دون أن تضيع رسالة، مع مسار قراءة يجمع كل أدلة البريد على الموقع.سجل التحديثات Changelog
- أكتوبر 2026: كتابة الدليل واختباره على Postal 3.3.7.