التسويق والمبيعات الأدلة التقنية

Mautic على سيرفرك: حملة مؤتمتة من النموذج إلى البريد

نشغل Mautic في Docker ونبني نموذجاً عربياً ونقاطاً للتقييم وحملة ترسل الترحيب وتنبه المبيعات، ونختبرها بزائرين تجريبيين.

Mautic على سيرفرك: حملة مؤتمتة من النموذج إلى البريد

لنفرض أن لديك منتجاً برمجياً، وفي موقعه نموذج «جرب مجاناً». يصل كل يوم عدد من الطلبات، فيرسل لها أحد الفريق رسالة ترحيب بيده عندما يتذكر، ولا يعرف أحد من هؤلاء عاد إلى صفحة الأسعار ومن نسي المنتج بعد ساعة. وفي مقال أتمتة التسويق وتقييم العملاء شرحنا الفكرة: نموذج يجمع جهة الاتصال، ونقاط 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، ونرى ما الذي يعمل بالعربية وما الذي يحتاج انتباهك.

والصورة التالية تبين ما سوف نبنيه:

الزائر يفتح صفحة الهبوط على nginx بالمنفذ 18792، وفيها نموذج Mautic وملف التتبع mtc.js يتصلان بالحاوية mautic_web على المنفذ 18790، ومعها mautic_worker وmautic_cron من نفس الـ Docker Image وكلها تتصل بقاعدة البيانات MySQL 8.4، والـ Worker يرسل الرسائل إلى Mailpit، والرسالة فيها رابط يعيد الزائر إلى صفحة الأسعار، ثم يصل تنبيه إلى فريق المبيعات
ست Containers، ثلاث منها من Docker Image واحدة تختلف في الدور فقط

المتطلبات

  • سيرفر 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":

صفحة هبوط عربية عنوانها جرب منصة الفواتير 14 يوماً، وفيها نموذج Mautic بحقول الاسم والبريد الإلكتروني والشركة وحجم الفريق، وقد كتبنا فيها نورة وnoura@example.com وشركة الرياض للبرمجيات Riyadh Soft واخترنا 11-50، وتحتها زر برتقالي مكتوب عليه Please wait أثناء الإرسال
النموذج داخل صفحة عربية، وعبارة Please wait أثناء الإرسال بالإنجليزية

وعند الإرسال يتغير نص الزر إلى 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 email5إشارة ضعيفة لأن بعض البرامج تفتح الرسائل آلياً
زيارة صفحة الأسعارPoints ثم Manage Actions، النوع Visits specific URL10من يسأل عن السعر أقرب إلى الشراء

والعميل الذي يصل إلى 30 نقطة نعتبره جاهزاً للتسويق Marketing Qualified Lead، واختصاراً MQL، وهذا يحدث فقط إذا طلب التجربة ثم عاد وزار صفحة الأسعار.

صفحة Points في Mautic فيها إجراءان: زيارة صفحة الأسعار بعشر نقاط من نوع Visits specific URL، وفتح رسالة بريد بخمس نقاط من نوع Opens an email
إجراءا النقاط: زيارة صفحة الأسعار وفتح رسالة

وفي إجراء زيارة الصفحة كتبنا العنوان مع علامة النجمة 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 هو شريحة «طلبات التجربة»:

محرر الحملات في Mautic: مصدر جهات الاتصال شريحة طلبات التجربة، ثم إجراء Send or schedule email لرسالة الترحيب، ثم قرار Page Hit لزيارة صفحة الأسعار، ثم انتظار دقيقة وشرط Contact points بثلاثين نقطة أو أكثر، ثم إجراءان: Change tags بإضافة الوسم MQL وSend email to user لتنبيه فريق المبيعات
الحملة في محرر Mautic: إجراء، ثم قرار، ثم شرط، ثم إجراءان

وفي الحملة ثلاثة أنواع من العناصر، والفرق بينها مهم:

  • الإجراء 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}:

Mailpit يعرض رسالة من فريق منصة الفواتير إلى نورة بعنوان نورة، حسابك التجريبي جاهز، والمحتوى من اليمين إلى اليسار: أهلاً نورة، ونص الترحيب، وزر برتقالي اطلع على الباقات والأسعار، وفي الأسفل رابط إلغاء الاشتراك، وبجانب المرسل في الأعلى رابط Unsubscribe
رسالة الترحيب: العنوان والمحتوى بالعربية من اليمين إلى اليسار

ثم فتحت نورة الرسالة في نفس المتصفح فحمل بكسل التتبع 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 بثلاثة وعشرين حدثاً، من Contact created وPage hit وForm submitted حتى Campaign action triggered للوسم MQL وتنبيه المبيعات، ثم Do not contact بعد إلغاء الاشتراك، وعلى اليمين الوسم MQL والشركة شركة الرياض للبرمجيات Riyadh Soft والشريحة طلبات التجربة
سجل نورة: 23 حدثاً من أول زيارة حتى إلغاء الاشتراك

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

Mailpit يعرض رسالة إلى sales@example.com بعنوان عميل محتمل جاهز: نورة (35 نقطة)، وفيها قائمة بالاسم والبريد والشركة وحجم الفريق 11-50 الذي ظهر 50-11، والنقاط 35
تنبيه المبيعات، وانتبه إلى «حجم الفريق» الذي ظهر 50-11

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

وفي قائمة جهات الاتصال يظهر الفرق بين العميلين بالأرقام، ومع نورة علامة حمراء بعد أن ألغت اشتراكها كما في القسم التالي:

قائمة Contacts في Mautic فيها أحمد بعشرين نقطة ونورة بخمس وثلاثين نقطة ومعها علامة حمراء تعني أن البريد ممنوع Do Not Contact
أحمد 20 نقطة، ونورة 35 نقطة مع علامة Do Not Contact

إلغاء الاشتراك 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.
Could not load content
نشرة عرب رووت | ArabRoot

معرفة تستحق مكاناً في بريدك.

مقالات مختارة وأدوات مفيدة وأفكار لمشروعك القادم، في رسالة واحدة كل أسبوع.

يمكنك إلغاء الاشتراك متى شئت. الخصوصية

تم استلام طلبك. افتح بريدك واضغط رابط التأكيد لإتمام الاشتراك.