لنفرض أن لديك منتجاً برمجياً، وفي موقعه نموذج «جرب مجاناً». يصل كل يوم عدد من الطلبات، فيرسل لها أحد الفريق رسالة ترحيب بيده عندما يتذكر، ولا يعرف أحد من هؤلاء عاد إلى صفحة الأسعار ومن نسي المنتج بعد ساعة. وفي مقال أتمتة التسويق وتقييم العملاء شرحنا الفكرة: نموذج يجمع جهة الاتصال، ونقاط Points تقيس اهتمامها بسلوكها، وحملة Campaign ترسل الرسالة المناسبة في وقتها وتنبه فريق المبيعات عندما يصبح العميل جاهزاً. وفي هذا الدليل سوف نطبق ذلك على Mautic، وهو أشهر نظام مفتوح المصدر لأتمتة التسويق Marketing Automation، على سيرفرنا بـ Docker، ونمر بالمسار كاملاً مع زائرين ببيانات وهمية.
المواضيع في السلسلة:
دليلأتمتة التسويق Marketing Automation وتقييم العملاء Lead Scoringكيف تتابع كل عميل محتمل في وقته بقواعد من محفز وشرط وإجراء، وكيف تقيمه بالنقاط لكي يبدأ فريق المبيعات بالأقرب للشراء.
دليلHubSpot وبدائله مفتوحة المصدر: مقارنة عمليةشغلنا Mautic وOdoo Community وSuiteCRM وTwenty في Docker وقارناها بـ HubSpot في أتمتة التسويق والعربية والترخيص والسعر.
دليلاستبيانات وآراء العملاء مع Formbricks على سيرفركاستبيان NPS عربي يحمل مصدر العميل في حقول مخفية، واستبيان داخل التطبيق بعد أول فاتورة، ثم NPS لكل قناة من الـ API.
دليلمنظومة تسويق كاملة على سيرفرك: من الرابط إلى العميلنجمع Umami وShlink وMautic في ملف Compose واحد، ونتتبع عميلاً من رابط مختصر في منشور حتى يظهر تحويله ومصدره في التقارير.وفي هذا الدليل سوف نقوم بما يلي:
- نشغل Mautic 7.2.1 بملف Compose واحد فيه الواجهة والمهام المجدولة Cron وعامل الطابور Worker وقاعدة البيانات، مع Mailpit لاستقبال الرسائل بدل إرسالها للإنترنت.
- نبني نموذجاً عربياً لطلب نسخة تجريبية، ونضعه في صفحة هبوط Landing Page مع ملف التتبع.
- نعرف قواعد التقييم Lead Scoring: 20 نقطة لطلب التجربة، و5 لفتح الرسالة، و10 لزيارة صفحة الأسعار.
- نبني حملة ترسل رسالة ترحيب، ثم تنتظر زيارة صفحة الأسعار، ثم تفحص النقاط وتنبه فريق المبيعات.
- نختبر إلغاء الاشتراك Unsubscribe، ونرى ما الذي يعمل بالعربية وما الذي يحتاج انتباهك.
والصورة التالية تبين ما سوف نبنيه:

المتطلبات
- سيرفر Linux عليه Docker وDocker Compose، وقد اختبرنا على Docker Engine 29.8.0.
- ذاكرة: بعد انتهاء التجربة كانت قاعدة البيانات تستهلك 519 ميجابايت، والواجهة 463، والـ Worker حوالي 333، أي حوالي 1.3 جيجابايت لـ Mautic وحده، فلا تقم بتشغيله على سيرفر بذاكرة 1 جيجابايت، والأنسب 2 جيجابايت على الأقل و4 إذا كانت معه خدمات أخرى.
- مساحة القرص: الـ Image الخاصة بـ Mautic حجمها 3.21 جيجابايت بعد التنزيل، والخاصة بـ MySQL حوالي 1.1 جيجابايت.
- نطاق فرعي Subdomain مثل
m.example.comبشهادة TLS عبر Reverse Proxy مثل Caddy، لأن التتبع والنماذج تعمل من مواقع أخرى، وسوف نرى في قسم قبل التشغيل الفعلي لماذا لا يكفي HTTP. - حساب إرسال SMTP حقيقي للتشغيل الفعلي، أما في هذا الدليل فنستخدم Mailpit لكي لا تخرج أي رسالة من السيرفر.
التشغيل بملف Compose
مشروع Mautic ينشر Docker Image رسمية ومعها أمثلة Compose، والفكرة فيها أن نفس الـ Image تعمل بثلاثة أدوار يحددها المتغير DOCKER_MAUTIC_ROLE: الواجهة، والمهام المجدولة Cron، وعامل الطابور Worker الذي يرسل الرسائل في الخلفية. أنشئ المجلد /opt/mautic وفيه الملف compose.yaml:
name: mke-mautic
x-mautic: &mautic
image: mautic/mautic:7.2.1-apache
restart: unless-stopped
env_file: .mautic_env
volumes:
- mautic-config:/var/www/html/config
- mautic-logs:/var/www/html/var/logs
- mautic-media:/var/www/html/docroot/media
services:
db:
image: mysql:8.4.11
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: mautic
MYSQL_USER: mautic
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
volumes:
- mysql-data:/var/lib/mysql
healthcheck:
test: mysqladmin --user=mautic --password=$$MYSQL_PASSWORD ping
interval: 5s
timeout: 5s
retries: 60
start_period: 300s
mautic_web:
<<: *mautic
ports:
- "127.0.0.1:18790:80"
healthcheck:
test: curl -fs http://localhost/ -o /dev/null || exit 1
interval: 10s
timeout: 5s
retries: 60
start_period: 300s
depends_on:
db:
condition: service_healthy
mautic_cron:
<<: *mautic
environment:
DOCKER_MAUTIC_ROLE: mautic_cron
depends_on:
mautic_web:
condition: service_healthy
mautic_worker:
<<: *mautic
environment:
DOCKER_MAUTIC_ROLE: mautic_worker
depends_on:
mautic_web:
condition: service_healthy
mailpit:
image: axllent/mailpit:v1.31.5
ports:
- "127.0.0.1:18791:8025"
site:
image: nginx:1.31.6-alpine
volumes:
- ./site:/usr/share/nginx/html:ro
ports:
- "127.0.0.1:18792:80"
volumes:
mysql-data:
mautic-config:
mautic-logs:
mautic-media:ثم الملف .mautic_env الذي تقرؤه الأدوار الثلاثة:
MAUTIC_DB_HOST=db
MAUTIC_DB_PORT=3306
MAUTIC_DB_DATABASE=mautic
MAUTIC_DB_USER=mautic
MAUTIC_DB_PASSWORD=${MYSQL_PASSWORD}
MAUTIC_MESSENGER_DSN_EMAIL=doctrine://default
MAUTIC_MESSENGER_DSN_HIT=doctrine://default
PHP_INI_VALUE_DATE_TIMEZONE=UTCوننشئ كلمات المرور في الملف .env ثم نشغل الخدمات:
cd /opt/mautic
mkdir -p site
printf 'MYSQL_PASSWORD=%s\nMYSQL_ROOT_PASSWORD=%s\n' "$(openssl rand -hex 16)" "$(openssl rand -hex 16)" > .env
chmod 600 .env
docker compose up -dفي الإعداد أعلاه لاحظ التالي:
- المنافذ مربوطة بالعنوان
127.0.0.1فقط، أي لا يصل إليها أحد من خارج السيرفر إلا عبر الـ Reverse Proxy، والخدمةsiteهي صفحة الهبوط التجريبية، وفي موقعك الحقيقي تكون صفحة في موقعك الموجود. - القيمتان
doctrine://defaultتعنيان أن الرسائل وزيارات التتبع تدخل طابوراً Queue في قاعدة البيانات، ثم يسحبها الـ Worker ويعالجها، فلا ينتظر الزائر إرسال الرسالة قبل أن يرى صفحة الشكر. والتفاصيل في توثيق الطابور. - أضفنا
start_periodطويلاً لقاعدة البيانات، والسبب أن تهيئة MySQL في أول تشغيل قد تأخذ أكثر من دقيقة على سيرفر بطيء، وبدونه تعلن Compose أن قاعدة البيانات غير سليمة Unhealthy وتوقف بقية الخدمات، وإذا قمت بإيقافها وسط التهيئة فقد تفشل في التشغيل التالي برسالةCannot create redo log files because data files are corrupt، والحل حذف الـ Volume الجديد والبدء من جديد. - المنطقة الزمنية لـ PHP هي
UTCوليستAsia/Riyadh، والسبب في القسم التالي.
التثبيت من سطر الأوامر
يمكن إكمال التثبيت من المتصفح، ولكن الأمر mautic:install أوضح ويمكن تكراره، وهو موثق في دليل التثبيت:
docker exec -u www-data -w /var/www/html mke-mautic-mautic_web-1 \
php bin/console mautic:install https://m.example.com --force \
--admin_firstname=Admin --admin_lastname=Lab --admin_username=admin \
[email protected] --admin_password="$(openssl rand -hex 12)"في المحاولة الأولى وضعنا PHP_INI_VALUE_DATE_TIMEZONE=Asia/Riyadh فتوقف التثبيت عند فحص المتطلبات:
0 - Checking installation requirements...
Missing requirements:
- [0] Your default timezone is not supported by PHP. Check for typos in your PHP configuration.
Install canceledوالاسم صحيح، ولكن فاحص Mautic يقارن المنطقة الزمنية بقائمة DateTimeZone::listAbbreviations() في PHP، وهذه القائمة لا تحتوي Asia/Riyadh ولا Asia/Dubai ولا Asia/Kuwait، بينما فيها Africa/Cairo وUTC، وقد تحققنا من ذلك داخل الـ Container نفسه. لذلك اجعل PHP على UTC، ثم ضع منطقتك في إعدادات Mautic نفسها كما في القسم التالي، فتظهر الأوقات في الواجهة بتوقيت الرياض. وبعد التعديل اكتمل التثبيت في حوالي ثلاث دقائق، ومعه هذا التنبيه:
Missing optional settings:
- [0] It is recommended to secure your installation with an SSL certificate (https). ...
Ready to Install!
1 - Creating database...
1.1 - Creating schema...
1.2 - Loading fixtures...
2 - Creating admin user...
3 - Final steps...
================
Install complete
================ولاحظ أن كلمة مرور المدير في الأمر أعلاه تولد عشوائياً وتظهر في سجل الأوامر Shell History، فقم بتغييرها من الواجهة بعد أول دخول.
إعداد البريد والتتبع
إعدادات Mautic محفوظة في الملف config/local.php داخل الـ Volume المسمى mautic-config، ويمكن تعديلها من الواجهة في Settings ثم Configuration، وأضفنا هنا القيم مباشرة لكي تكون واضحة:
'mailer_dsn' => 'smtp://mailpit:1025',
'mailer_from_name' => 'فريق المنتج',
'mailer_from_email' => '[email protected]',
'default_timezone' => 'Asia/Riyadh',
'api_enabled' => true,
'api_enable_basic_auth' => true,
'track_private_ip_ranges' => true,
'cors_restrict_domains' => true,
'cors_valid_domains' => array('http://localhost:18792'),في الإعداد أعلاه لاحظ التالي:
mailer_dsnيوجه الإرسال إلى Mailpit، وفي الإنتاج يصبح عنوان مزود SMTP حقيقي مثل Amazon SES.track_private_ip_rangesمفعل لأننا نجرب على نفس الجهاز، فالطلبات تصل إلى الـ Container من عنوان شبكة Docker الداخلية، وMautic يتجاهل العناوين الخاصة Private IPs افتراضياً فلا يسجل أي زيارة. وفي الإنتاج خلف Reverse Proxy قم بإيقافه، واجعل الـ Proxy يمرر عنوان الزائر الحقيقي.cors_valid_domainsقائمة المواقع المسموح لها باستدعاء Mautic من المتصفح، فضع فيها نطاق موقعك فقط.- فعلنا الـ API بالمصادقة الأساسية Basic Auth لأننا أنشأنا العناصر بالأوامر، وإذا لم تكن تحتاجه فاتركه مغلقاً، وإذا احتجته في الإنتاج فالأفضل OAuth2 مع مستخدم بصلاحيات محدودة.
بعد ذلك أعد تشغيل الـ Worker والـ Cron، وهذه خطوة لا تتجاوزها:
docker compose restart mautic_worker mautic_cronوالسبب أن الـ Worker يقرأ الإعدادات مرة واحدة عند تشغيله، وقد بدأ عندنا قبل التثبيت، فبقي يرسل إلى العنوان الافتراضي localhost:1025، وعندما أرسلت الحملة أول رسالة ظهر في سجله ما يلي، ثم حذف الرسالة من الطابور بعد ثلاث محاولات، بينما الواجهة تقول إن الرسالة أرسلت:
messenger.CRITICAL: Error thrown while handling message Symfony\Component\Mailer\Messenger\SendEmailMessage.
Removing from transport after 3 retries. Error: "Handling "Symfony\Component\Mailer\Messenger\SendEmailMessage" failed:
Connection could not be established with host "localhost:1025": stream_socket_client(): Unable to connect to localhost:1025 (Connection refused)"لذلك بعد أي تعديل في إعدادات البريد أعد تشغيل الـ Worker، ثم أرسل رسالة اختبار وتأكد أنها وصلت فعلاً، ولا تعتمد على عداد Sent في الواجهة.
النموذج Form في صفحة الهبوط
قبل النموذج أنشأنا شريحة Segment باسم «طلبات التجربة» تجمع كل من يطلب نسخة تجريبية، وحقلاً مخصصاً Custom Field لجهة الاتصال اسمه team_size من نوع قائمة اختيار، لأن حجم فريق العميل يهم فريق المبيعات. ثم أنشأنا النموذج بأربعة حقول عربية، كل منها مربوط بحقل في جهة الاتصال Contact، ومعه إجراءان Actions يعملان عند الإرسال: إضافة 20 نقطة، وإضافة جهة الاتصال إلى الشريحة. وهذا جزء من ملف النموذج الذي أرسلناه إلى الـ API، ويمكنك إنشاء نفس الشيء من Components ثم Forms:
{
"name": "طلب نسخة تجريبية",
"formType": "standalone",
"postAction": "message",
"postActionProperty": "شكراً لك، وصلنا طلبك وسوف تصلك رسالة على بريدك خلال دقائق.",
"fields": [
{"label": "الاسم", "alias": "firstname", "type": "text", "leadField": "firstname", "isRequired": true},
{"label": "البريد الإلكتروني", "alias": "email", "type": "email", "leadField": "email", "isRequired": true},
{"label": "الشركة", "alias": "company", "type": "text", "leadField": "company"},
{"label": "حجم الفريق", "alias": "team_size", "type": "select", "leadField": "team_size"},
{"label": "أرسل الطلب", "alias": "submit", "type": "button"}
],
"actions": [
{"name": "نقاط طلب التجربة", "type": "lead.pointschange", "properties": {"operator": "plus", "points": 20}},
{"name": "إضافة إلى شريحة طلبات التجربة", "type": "lead.changelist", "properties": {"addToLists": [2]}}
]
}وأرسلنا الملف بالأمر التالي، ولاحظ --data-binary @form.json:
curl -s -u "admin:$MAUTIC_PASSWORD" -H 'Content-Type: application/json;charset=utf-8' \
-X POST http://localhost:18790/api/forms/new --data-binary @form.jsonوالسبب أننا في أول محاولة كتبنا النص العربي داخل الأمر نفسه في Git Bash على Windows، فوصل إلى Mautic علامات استفهام ????? ??????? وحفظت هكذا في قاعدة البيانات. المشكلة هنا في ترميز سطر الأوامر وليست في Mautic، لذلك ضع النص العربي في ملف UTF-8 دائماً عند استخدام الـ API.
بعد ذلك نضع في صفحة الهبوط سطرين من Mautic: ملف التتبع mtc.js الذي يسجل الزيارات، وسكربت النموذج الذي يرسم الحقول، كما في توثيق التتبع وتوثيق النماذج:
<script>
(function(w,d,t,u,n,a,m){w['MauticTrackingObject']=n;
w[n]=w[n]||function(){(w[n].q=w[n].q||[]).push(arguments)},a=d.createElement(t),
m=d.getElementsByTagName(t)[0];a.async=1;a.src=u;m.parentNode.insertBefore(a,m)
})(window,document,'script','http://localhost:18790/mtc.js','mt');
mt('send', 'pageview');
</script>
<script type="text/javascript" src="http://localhost:18790/form/generate.js?id=1"></script>ونضع نفس سكربت التتبع في صفحة الأسعار pricing.html، لأنها الصفحة التي سوف نعطي زيارتها نقاطاً. والنموذج يظهر في الصفحة العربية من اليمين إلى اليسار لأنه يأخذ اتجاه الصفحة dir="rtl":

وعند الإرسال يتغير نص الزر إلى Please wait... بالإنجليزية، وهو نص ثابت من Mautic لا تكتبه في النموذج. وبعد الإرسال تظهر رسالة الشكر التي كتبناها:

ولاحظ أمرين ظهرا معنا في النموذج:
- بعد إنشاء النموذج أرسلنا طلب
PATCHلربط حقلي الشركة وحجم الفريق، فرجع الإعدادpostActionمنmessageإلى القيمة الافتراضيةreturnدون أي تنبيه، ولم تظهر رسالة الشكر للزائر، وكررنا ذلك في تجربة ثانية بنفس النتيجة. لذلك أرسلpostActionوpostActionPropertyمع كل تعديل للنموذج بالـ API، وراجع إجراء ما بعد الإرسال Successful Submit Action في الواجهة. - Mautic يولد اسماً مختصراً Alias للنموذج من اسمه، ومع الاسم العربي «طلب نسخة تجريبية» حوله إلى حروف لاتينية
tlb_nskht، أي أنه لا يرفض الأسماء العربية، ولكن معرفات الحقول في الصفحة أصبحت مثلmauticform_input_tlbnskhttjrybyt_email، فانتبه لها إذا كتبت CSS أو اختبارات للنموذج.
النقاط Points: تقييم العميل بسلوكه
تقييم العميل المحتمل Lead Scoring في Mautic يعتمد على إجراءات النقاط Point Actions: كل إجراء يحدد سلوكاً وعدد النقاط التي يضيفها، والتفاصيل في توثيق النقاط. ونموذجنا في هذا الدليل بسيط ومقصود، لكي تتبع الأرقام بعينك:
| السلوك | أين عرفناه | النقاط | لماذا |
|---|---|---|---|
| طلب نسخة تجريبية | إجراء في النموذج نفسه | 20 | أوضح إشارة على الاهتمام |
| فتح رسالة بريد | Points ثم Manage Actions، النوع Opens an email | 5 | إشارة ضعيفة لأن بعض البرامج تفتح الرسائل آلياً |
| زيارة صفحة الأسعار | Points ثم Manage Actions، النوع Visits specific URL | 10 | من يسأل عن السعر أقرب إلى الشراء |
والعميل الذي يصل إلى 30 نقطة نعتبره جاهزاً للتسويق Marketing Qualified Lead، واختصاراً MQL، وهذا يحدث فقط إذا طلب التجربة ثم عاد وزار صفحة الأسعار.

وفي إجراء زيارة الصفحة كتبنا العنوان مع علامة النجمة http://localhost:18792/pricing.html*، لكي تحسب الزيارة حتى إذا جاءت مع معاملات UTM في نهاية الرابط. وأيضاً عندما أنشأنا هذا الإجراء من الـ API ضاع العنوان وحفظ الإجراء بخصائص فارغة، فأكملناه من الواجهة، لذلك افتح كل إجراء نقاط تنشئه بالـ API وتأكد من حقوله.
الحملة Campaign: الرسالة ثم القرار ثم التنبيه
أنشأنا رسالتين من نوع Template: رسالة ترحيب للعميل بالعربية من اليمين إلى اليسار dir="rtl" فيها زر إلى صفحة الأسعار ورابط إلغاء الاشتراك {unsubscribe_url}، ورسالة تنبيه داخلية لفريق المبيعات فيها بيانات العميل ونقاطه. وفي رسالة الترحيب فعلنا معاملات UTM لكي تعرف أدوات التحليلات أن الزيارة جاءت من هذه الرسالة:
"utmTags": {"utmSource": "mautic", "utmMedium": "email", "utmCampaign": "trial-welcome", "utmContent": "welcome"}ثم بنينا الحملة في محرر الحملات Campaign Builder، ومصدرها Contact Source هو شريحة «طلبات التجربة»:

وفي الحملة ثلاثة أنواع من العناصر، والفرق بينها مهم:
- الإجراء Action: شيء يفعله Mautic، مثل إرسال رسالة أو إضافة وسم Tag أو تنبيه مستخدم.
- القرار Decision: شيء ينتظر Mautic أن يفعله العميل، مثل زيارة صفحة أو فتح رسالة، فتبقى جهة الاتصال عنده حتى يحدث السلوك.
- الشرط Condition: فحص لبيانات موجودة، مثل عدد النقاط أو قيمة حقل، فيذهب العميل إلى فرع نعم أو لا.
وقد يتساءل البعض: لماذا وضعنا انتظار دقيقة قبل شرط النقاط؟ والإجابة أن زيارة صفحة الأسعار تطلق القرار وتضيف النقاط في نفس اللحظة، ولا تضمن أيهما يعالج أولاً، فإذا فحص الشرط النقاط قبل إضافة العشر فسوف يجد 25 ويذهب العميل إلى فرع لا. والانتظار القصير يحل ذلك. ولاحظ أيضاً أن الأدوات المختلفة تسمي العنصر الذي ينتظر سلوك العميل بأسماء مختلفة، والمهم أن تفصل في تصميمك بين ما تنتظره من العميل وما تفحصه في بياناته.
وتفاصيل كل عنصر في توثيق محرر الحملات.
تجربة المسار كاملاً
الآن نمر بالمسار كزائرة اسمها نورة، من متصفح Chrome بلغة عربية. فتحت صفحة الهبوط فسجل ملف التتبع زيارتها كزائرة مجهولة، ثم أرسلت النموذج، فأصبحت جهة اتصال لها 20 نقطة في شريحة «طلبات التجربة». بعد ذلك تأتي المهام المجدولة، وهذا ما يجب أن تعرفه عنها: الـ Container المسمى mautic_cron يشغلها بالجدول التالي، كما هو في الـ Image:
# Segments Update - every 15 min
0,15,30,45 * * * * php /var/www/html/bin/console mautic:segments:update
# Campaigns Update - every 15 min starting at 5 past the hour
5,20,35,50 * * * * php /var/www/html/bin/console mautic:campaigns:update
# Trigger Update - every 15 min starting at 10 past the hour
10,25,40,55 * * * * php /var/www/html/bin/console mautic:campaigns:triggerأي أن العميل الذي يرسل النموذج بعد الدقيقة الخامسة ينتظر حتى تضيفه campaigns:update إلى الحملة، ثم حتى تنفذ campaigns:trigger رسالة الترحيب، وهذا قد يصل إلى 20 دقيقة، وهو وقت طويل لرسالة تقول «حسابك جاهز». فإذا كانت الرسالة الأولى مهمة فأرسلها من إجراء في النموذج نفسه اسمه Send email to contact، فهو يعمل لحظة الإرسال دون انتظار الـ Cron، أو قم بتقصير الجدول بملف Cron خاص تربطه بالمجلد /opt/mautic/cron كما في التعليق الموجود في ملف Compose الرسمي، مع مراعاة الحمل على السيرفر. وفي التجربة شغلنا الأوامر بأنفسنا لكي لا ننتظر:
docker exec -u www-data mke-mautic-mautic_web-1 php /var/www/html/bin/console mautic:campaigns:update
docker exec -u www-data mke-mautic-mautic_web-1 php /var/www/html/bin/console mautic:campaigns:triggerوالمخرج سوف يكون كما يلي:
Rebuilding contacts for campaign 1
1 total contact(s) to be added in batches of 300
Triggering events for campaign 1
Triggering events for newly added contacts
1 total event was executedوخلال ثانيتين وصلت رسالة الترحيب إلى Mailpit، والاسم فيها من حقل جهة الاتصال {contactfield=firstname}:

ثم فتحت نورة الرسالة في نفس المتصفح فحمل بكسل التتبع Tracking Pixel وأضاف 5 نقاط، وضغطت الزر فمر الرابط على Mautic أولاً ثم وصلت إلى:
http://localhost:18792/pricing.html?utm_source=mautic&utm_medium=email&utm_campaign=trial-welcome&utm_content=welcomeوبعد دقيقة شغلنا campaigns:trigger مرة أخرى فنفذت ثلاثة أحداث: الشرط والوسم والتنبيه. وسجل النقاط في قاعدة البيانات يبين من أين جاءت كل نقطة:
+---------+-------+-------+---------------------------------------+---------------------+
| lead_id | delta | type | event_name | date_added |
+---------+-------+-------+---------------------------------------+---------------------+
| 2 | 20 | form | 1:طلب نسخة تجريبية | 2026-10-25 10:07:17 |
| 2 | 5 | email | 2: فتح رسالة بريد | 2026-10-25 10:08:27 |
| 2 | 10 | url | 1: زيارة صفحة الأسعار | 2026-10-25 10:08:32 |
| 3 | 20 | form | 1:طلب نسخة تجريبية | 2026-10-25 10:25:13 |
+---------+-------+-------+---------------------------------------+---------------------+في المخرج أعلاه لاحظ التالي:
- نورة (رقم 2) جمعت 35 نقطة في حوالي دقيقة وربع، فتجاوزت الحد 30 وأصبحت MQL.
- أحمد (رقم 3) زائر ثان أرسل النموذج فقط ولم يزر صفحة الأسعار، فبقي عند 20 نقطة، ووصلته رسالة الترحيب، ولكن الحملة تنتظره عند قرار زيارة صفحة الأسعار، ولن يصل تنبيه بشأنه إلى المبيعات.
- الأوقات في قاعدة البيانات بتوقيت UTC، بينما تظهر في الواجهة بتوقيت الرياض لأننا وضعنا
default_timezone.
وملف جهة الاتصال في Mautic يجمع كل ذلك في سجل واحد History من الأحدث إلى الأقدم: الزيارة المجهولة، ثم إرسال النموذج والتعرف عليها، ثم الإضافة إلى الشريحة والحملة، ثم الرسالة وفتحها، ثم معاملات UTM وزيارة صفحة الأسعار، ثم الشرط والوسم والتنبيه:

ولاحظ أن Mautic أنشأ شركة Company باسم «شركة الرياض للبرمجيات Riyadh Soft» من حقل الشركة في النموذج وربطها بنورة، وهذا مفيد إذا كان لك أكثر من عميل محتمل من نفس الشركة. وهذه رسالة التنبيه التي وصلت لفريق المبيعات:

وفي الرسالة مشكلة صغيرة لا علاقة لها بـ Mautic: القيمة 11-50 ظهرت 50-11 لأن المتصفح يعرض الأرقام المفصولة بشرطة في سطر عربي بترتيب الاتجاه الثنائي Bidirectional Text. لذلك في القوالب العربية اكتب المدى بالكلمات «من 11 إلى 50»، أو ضع القيمة داخل <span dir="ltr">.
وفي قائمة جهات الاتصال يظهر الفرق بين العميلين بالأرقام، ومع نورة علامة حمراء بعد أن ألغت اشتراكها كما في القسم التالي:

إلغاء الاشتراك Unsubscribe
أضاف Mautic إلى كل رسالة تسويقية ترويسة إلغاء الاشتراك بضغطة واحدة One-Click Unsubscribe دون أن نطلب ذلك، ولهذا ظهر رابط Unsubscribe بجانب اسم المرسل في Mailpit:
List-Unsubscribe: <http://localhost:18790/email/unsubscribe/6addd5172313d781718912/[email protected]/f3dc...>
List-Unsubscribe-Post: List-Unsubscribe=One-Click
Content-Type: multipart/alternative; boundary=QCXq6y3h
Content-Type: text/plain; charset=utf-8
Content-Type: text/html; charset=utf-8وهذه الترويسة يطلبها Gmail في الرسائل التسويقية ممن يرسل أكثر من 5000 رسالة يومياً، وتطلبها Yahoo أيضاً، وطريقتها موصوفة في RFC 8058. وجربناها كما يفعل برنامج البريد، بطلب POST على الرابط:
curl -s -o /dev/null -w '%{http_code}\n' -X POST -d 'List-Unsubscribe=One-Click' "$UNSUBSCRIBE_URL"فرجع الرمز 200، وسجلت نورة في جدول عدم التواصل Do Not Contact لقناة البريد، ثم حاولنا إرسال رسالة الترحيب لها من الـ API فرفض Mautic:
lead_id channel reason comments
2 email 1 User unsubscribed.
{"success":false,"failed":[]}وهذا هو السلوك الصحيح، وهو أيضاً ما يطلبه نظام حماية البيانات الشخصية في المادة الخامسة والعشرين: لا ترسل مواد دعائية إلى وسائل الاتصال الشخصية إلا بموافقة المتلقي، ومع آلية واضحة يوقف بها الإرسال متى أراد. ولاحظ أن نص الرسالة البديل Plain Text الذي يولده Mautic تلقائياً جاء بالعربية سليماً، ولكن الرابط إلى صفحة الأسعار فيه مكتوب كما هو دون تتبع ودون معاملات UTM، فالنقرات من برامج البريد النصية لن تظهر في تقاريرك.
العربية في Mautic: ما الذي يعمل؟
جمعنا ما وجدناه في التجربة في جدول واحد:
| الجزء | النتيجة |
|---|---|
| الحقول والنصوص العربية في النموذج | تعمل، والنموذج يأخذ اتجاه الصفحة من اليمين إلى اليسار |
| عبارة الانتظار أثناء الإرسال | Please wait... بالإنجليزية |
| عنوان الرسالة واسم المرسل | يعملان بترميز UTF-8 ويظهران صحيحين |
| محتوى الرسالة HTML | يعمل بشرط أن تكتب dir="rtl" وlang="ar" في القالب بنفسك |
| النص البديل Plain Text | عربي سليم، والروابط فيه دون تتبع |
| البيانات في قاعدة البيانات | عربية سليمة، والجداول بترميز utf8mb4 |
| لوحة الإدارة | إنجليزية، فحزمة اللغة العربية الرسمية فيها 24 عبارة مترجمة في أربعة ملفات فقط، بينما حزمة الفرنسية فيها حوالي 6700 سطر |
| الأسماء العربية في الواجهة | تظهر، ولكن الاسم مع الشركة في رأس صفحة جهة الاتصال يختلط ترتيبه لأن الواجهة من اليسار إلى اليمين |
والنتيجة أن Mautic مناسب لحملات عربية، لأن ما يراه العميل تكتبه أنت، أما فريقك فسوف يعمل بواجهة إنجليزية، وهذا مقبول عادة في فريق تقني. وحزم اللغات تأتي من قائمة حزم Mautic ويترجمها متطوعون، فإذا أردت المساهمة في الترجمة العربية فهي هناك.
قبل التشغيل الفعلي
- HTTPS إلزامي: التثبيت نفسه نبه إلى أن المتصفحات لا ترسل ملفات تعريف الارتباط Cookies بين المواقع إلا إذا كانت آمنة، فالتتبع على HTTP لن يعمل عند الزوار من موقعك الحقيقي. ضع Mautic خلف Reverse Proxy بشهادة TLS، وغير
site_urlوعناوين السكربتات في صفحاتك إلى HTTPS. - مزود SMTP حقيقي، مع سجلات SPF وDKIM وDMARC للنطاق الذي ترسل منه، كما شرحنا في شرح SPF وDKIM وDMARC، والفرق بين الإرسال المباشر والترحيل في مقال SMTP والترحيل Relay.
- الموافقة Consent: أضف في النموذج خانة موافقة صريحة على الرسائل التسويقية، لأن المادة السادسة والعشرين من نظام حماية البيانات الشخصية تجيز معالجة البيانات لأغراض تسويقية إذا جمعت من صاحبها مباشرة ووافق على ذلك، ورسالة الترحيب بالحساب التجريبي شيء والنشرة التسويقية شيء آخر.
- قم بإيقاف
track_private_ip_ranges، وتأكد أن الـ Reverse Proxy يمرر عنوان الزائر، وإلا ظهرت كل الزيارات من عنوان واحد. - راجع جدول الـ Cron، وأضف المهام الاختيارية التي تحتاجها من توثيق المهام المجدولة، مثل تنظيف البيانات القديمة.
التحقق من النجاح
docker compose psيظهرhealthyلقاعدة البيانات والواجهة.- الـ API يجيب:
curl -s -u "admin:$MAUTIC_PASSWORD" http://localhost:18790/api/contacts?limit=1يرجع JSON فيهtotal. - زيارة صفحة فيها
mtc.jsتظهر في Contacts كزائر مجهول Visitor، وإذا لم تظهر فراجعcors_valid_domainsوإعداد العناوين الخاصة. - إرسال النموذج ينشئ جهة اتصال بالنقاط الصحيحة في نفس اللحظة، وتظهر في الشريحة.
- رسالة اختبار تصل فعلاً إلى صندوقك، وليس فقط إلى عداد Sent.
النسخ الاحتياطي Backup والاستعادة Restore
البيانات في مكانين: قاعدة البيانات، والـ Volumes الخاصة بالإعدادات والملفات. نسخ قاعدة البيانات:
docker exec mke-mautic-db-1 sh -c 'mysqldump --single-transaction --no-tablespaces -umautic -p"$MYSQL_PASSWORD" mautic' | gzip > mautic-$(date +%F).sql.gzونسخ الإعدادات والملفات المرفوعة:
docker run --rm -v mke-mautic_mautic-config:/c -v mke-mautic_mautic-media:/m -v "$PWD":/backup \
nginx:1.31.6-alpine tar czf /backup/mautic-files-$(date +%F).tar.gz -C / c mوفي تجربتنا كانت قاعدة البيانات 128 جدولاً بحجم 7.7 ميجابايت، والنسخة المضغوطة 35 كيلوبايت فقط، والملفات حوالي 4.7 ميجابايت. ولا تنس الملف local.php داخل الـ Volume المسمى mautic-config، ففيه المفتاح السري secret_key وكلمة مرور قاعدة البيانات، فاحفظ النسخة في مكان محمي. وللاستعادة أعد الـ Volumes من الملف المضغوط، ثم شغل قاعدة البيانات وحدها واستورد النسخة:
gunzip -c mautic-2026-10-25.sql.gz | docker exec -i mke-mautic-db-1 sh -c 'mysql -umautic -p"$MYSQL_PASSWORD" mautic'التحديث Upgrade إلى إصدار أحدث
غير وسم الـ Image في compose.yaml إلى الإصدار الجديد بعد قراءة ملاحظات الإصدار، وخذ نسخة احتياطية قبلها، ثم:
docker compose pull
docker compose up -d
docker exec -u www-data mke-mautic-mautic_web-1 php /var/www/html/bin/console doctrine:migrations:migrate --no-interactionوتفاصيل الترقية بين الإصدارات الرئيسية في دليل التحديث، ولا تقفز بين إصدارين رئيسيين دفعة واحدة دون قراءته.
مشكلات شائعة وحلولها
التثبيت يرفض المنطقة الزمنية
الرسالة Your default timezone is not supported by PHP تظهر مع Asia/Riyadh وغيرها من مناطق الخليج، فاجعل PHP_INI_VALUE_DATE_TIMEZONE=UTC وضع منطقتك في default_timezone داخل Mautic.
الواجهة تقول أرسلت والرسالة لم تصل
راجع سجل الـ Worker في var/logs، فإذا وجدت Connection could not be established بعنوان قديم فأعد تشغيل mautic_worker بعد تعديل إعدادات البريد.
رسالة الترحيب تتأخر ربع ساعة أو أكثر
هذا جدول الـ Cron الافتراضي كما رأينا في تجربة المسار، فأرسل الرسالة الأولى من إجراء في النموذج، أو قم بتقصير الجدول.
الزيارات لا تظهر في Mautic
السبب غالباً واحد من ثلاثة: الموقع ليس في cors_valid_domains، أو الزيارة من عنوان خاص وtrack_private_ip_ranges مغلق، أو الموقع على HTTPS وMautic على HTTP.
النص العربي يحفظ علامات استفهام
إذا كان مصدره الـ API فالسبب ترميز الأمر الذي أرسله، فأرسل الـ JSON من ملف UTF-8 بالخيار --data-binary. وإذا قرأت الجداول بأداة mysql ورأيت علامات استفهام فأضف --default-character-set=utf8mb4 قبل أن تحكم على البيانات.
إزالة التجربة
cd /opt/mautic
docker compose down -v
docker rmi mautic/mautic:7.2.1-apache mysql:8.4.11 axllent/mailpit:v1.31.5 nginx:1.31.6-alpineالخلاصة
وصلنا لنهاية الموضوع، وأهم ما فيه:
- Mautic يعمل بثلاثة أدوار من Docker Image واحدة مع MySQL، ويستهلك حوالي 1.3 جيجابايت من الذاكرة، والتثبيت من سطر الأوامر أخذ ثلاث دقائق.
- اجعل PHP على UTC وإلا رفض المثبت مناطق الخليج، وأعد تشغيل الـ Worker بعد أي تعديل في البريد، لأن الرسائل قد تضيع وعداد Sent يقول إنها أرسلت.
- النموذج والنقاط والحملة عملت كما صممناها: نورة جمعت 35 نقطة فأصبحت MQL ووصل التنبيه للمبيعات، وأحمد بقي عند 20 نقطة.
- جدول الـ Cron الافتراضي كل 15 دقيقة، فأرسل الرسائل العاجلة من النموذج نفسه.
- إلغاء الاشتراك بضغطة واحدة يعمل تلقائياً ويمنع الإرسال بعده، والمحتوى العربي سليم، أما لوحة الإدارة فإنجليزية.
وفي المقال الأخير من السلسلة نضع Mautic مع أداة تحليلات وروابط مختصرة في منظومة واحدة، ونتتبع العميل من رابط في منشور حتى يظهر التحويل Conversion في التقارير. وأقرب المقالات إلى هذا الموضوع على الموقع:
دليلمنظومة تسويق كاملة على سيرفرك: من الرابط إلى العميلنجمع Umami وShlink وMautic في ملف Compose واحد، ونتتبع عميلاً من رابط مختصر في منشور حتى يظهر تحويله ومصدره في التقارير.
دليلتثبيت listmonk لإدارة النشرات البريدية على خادمككيف ترسل نشرتك البريدية Newsletter من سيرفرك باستخدام listmonk، فتبقى قائمة المشتركين عندك ولا يرتفع السعر مع كل مشترك.
دليلشرح SPF وDKIM وDMARC للمبتدئين: كيف تثبت أن رسائلك منك فعلاًلماذا يستطيع أي شخص أن يرسل باسم نطاقك، وكيف يسد كل سجل من هذه السجلات الثلاثة ثغرة تركها الذي قبله.
دليلإرسال بريد سيرفرك عبر Amazon SES دون أن يصل إلى Spamإذا كان مزودك يحجب المنفذ 25 أو تصل رسائلك إلى Spam، فسوف ترسل البريد الصادر عبر Amazon SES ويبقى الاستقبال على سيرفرك.سجل التحديثات
- أكتوبر 2026: كتابة الدليل واختباره على Mautic 7.2.1 (mautic/mautic:7.2.1-apache مع PHP 8.3.36 وApache 2.4.68) وmysql:8.4.11 وaxllent/mailpit:v1.31.5 وnginx:1.31.6-alpine على Docker Engine 29.8.0.