
في هذه المدونة، سأصف العملية التي مررت بها لتخزين محتوى البريد الإلكتروني على S3 (خدمة التخزين البسيطة من Amazon) والبيانات المساعدة في جدول MySQL لسهولة الرجوع المتبادل.
في هذه المدونة، سأصف العملية التي مررت بها لتخزين جسد البريد الإلكتروني على S3 (خدمة التخزين البسيطة من أمازون) والبيانات المساعدة في جدول MySQL لسهولة الرجوع إليها. في النهاية، هذه هي نقطة البداية لقاعدة الكود التي ستتضمن تطبيق يسمح بالبحث السهل في رسائل البريد الإلكتروني المحفوظة، ثم عرض تلك الرسائل إلى جانب بيانات الحدث (السجل). يمكن العثور على الكود لهذا المشروع في مستودع GitHub التالي: https://github.com/jeff-goldstein/PHPArchivePlatform.
بينما سأستخدم S3 وMySQL في هذا المشروع، ليس بالضرورة أن تكون هذه هي التقنيات الوحيدة التي يمكن استخدامها لبناء منصة أرشفة، ولكن بالنظر إلى شيوعها، اعتبرت أنها خيار جيد لهذا المشروع. في نظام ذو حجم عالي كامل، سأستخدم قاعدة بيانات ذات أداء أعلى من MySQL، ولكن لهذا المشروع النموذجي، MySQL مناسبة. بالنسبة للمنظمات التي تفكر في استخدام PostgreSQL كخيار لأرشفة قاعدة البيانات، فإن إجراء إجراءات النسخ الاحتياطي والاستعادة الصحيحة أمر ضروري للحفاظ على سلامة البيانات في أنظمة الإنتاج.
لقد قمت بتفصيل الخطوات التي اتخذتها في المرحلة الأولى من المشروع:
إنشاء البريد الإلكتروني المكرر للأرشفة
استخدام ميزات الأرشفة والتوجيه الوارد الخاص بSparkPost لإرسال نسخة من البريد الإلكتروني الأصلي مرة أخرى إلى SparkPost لمعالجته إلى هيكل JSON، ثم إرساله إلى جامع webhook (التطبيق)
تفكيك هيكل JSON للحصول على المكونات اللازمة
إرسال جسد البريد الإلكتروني إلى S3 للتخزين
تسجيل إدخال في MySQL لكل بريد إلكتروني للرجوع إليه
إنشاء نسخة من البريد الإلكتروني
في منصة SparkPost، أفضل طريقة لأرشفة بريد إلكتروني هي إنشاء نسخة مطابقة من البريد الإلكتروني مصممة خصيصًا لأغراض الأرشفة. يتم ذلك باستخدام ميزة الأرشفة في SparkPost. تمنح ميزة الأرشفة في SparkPost المُرسل القدرة على إرسال نسخة مكررة من البريد الإلكتروني إلى عنوان بريد إلكتروني واحد أو أكثر. تستخدم هذه النسخة المكررة نفس روابط التتبع والفتح كالأصلية. يتم تعريف ميزة الأرشفة في وثائق SparkPost بالطريقة التالية:
سيتلقى المستلمون في قائمة الأرشيف نسخة طبق الأصل من الرسالة التي تم إرسالها إلى عنوان RCPT TO. وبصفة خاصة، ستكون أي روابط مشفرة مخصصة للمستلم في RCPT TO متطابقة في رسائل الأرشيف.
الفرق الوحيد بين نسخة الأرشيف هذه و البريد الإلكتروني الأصلي المرسل إلى RCPT TO هو أن بعض الرؤوس ستكون مختلفة نظرًا لاختلاف العنوان المستهدف للبريد الإلكتروني المؤرشف، لكن جسم البريد الإلكتروني سيكون نسخة متطابقة!
إذا كنت ترغب في الحصول على تفسير أعمق، فإليك رابط لوثائق SparkPost حول إنشاء نسخ مكررة (أو أرشيفية) من البريد الإلكتروني. سيتم عرض عينات رؤوس X-MSYS-API لهذا المشروع في وقت لاحق في هذه المدونة.
هناك ملاحظة واحدة على هذا النهج؛ بينما ترتبط جميع معلومات الحدث في البريد الإلكتروني الأصلي معًا بواسطتي transmission_id و message_id، لا توجد معلومات في حدث ترحيل البريد الوارد (الآلية للحصول على وتوزيع البريد الإلكتروني المؤرشف) للبريد الإلكتروني المكرر تربط بواحد من تلك المُعرِّفين ومن ثم المعلومات للبريد الإلكتروني الأصلي. وهذا يعني أننا بحاجة إلى وضع البيانات في جسم البريد الإلكتروني ورأس البريد الإلكتروني الأصلي كوسيلة لربط جميع بيانات SparkPost من البريد الإلكتروني الأصلي والأرشيفي.
من أجل إنشاء الكود الذي يتم وضعه في جسم البريد الإلكتروني، استخدمت العملية التالية في تطبيق إنشاء البريد الإلكتروني.
في مكان ما في جسم البريد الإلكتروني، وضعت مدخل الإدخال التالي:<input name="ArchiveCode" type="hidden" value="<<UID>>">
ثم أنشأت رمزًا فريدًا واستبدلت الحقل <<UID>>:$uid = md5(uniqid(rand(), true)); $emailBody = str_replace(“<<UID>>,$uid,$emailBody);
هنا مثال على المخرج:
<input name="ArchiveCode" type="hidden" value="00006365263145">
بعدها، تأكدت من إضافة الـ UID إلى كتلة meta_data في رأس X-MSYS-API. تضمن هذه الخطوة أن يتم تضمين UID في كل مُخرج حدث للبريد الإلكتروني الأصلي:
X-MSYS-API:{ "campaign_id":"<my_campaign>", "metadata":{ "UID":"<UID>" }, "archive":[ { "email":"archive@geekwithapersonality.com" } ], "options":{ "open_tracking":false, "click_tracking":false, "transactional":false, "ip_pool":"<my_ip_pool>" } }
الآن لدينا طريقة لربط جميع البيانات من البريد الإلكتروني الأصلي إلى جسم البريد الإلكتروني المؤرشف.
الحصول على النسخة الأرشيفية
الحصول على البريد الإلكتروني المكرر في بنية JSON
في المرحلة الأولى من هذا المشروع، كل ما أقوم بتخزينه الآن هو تنسيق البريد الإلكتروني rfc822 في S3 وبعض حقول الوصف عالية المستوى في جدول SQL للبحث. بما أن SparkPost سترسل بيانات البريد الإلكتروني في بنية JSON إلى منصة الأرشفة الخاصة بي عبر تدفقات بيانات webhook، فقد بنيت تطبيقًا (غالبًا ما يُشار إليه بـ جامع) يقبل تدفق بيانات Relay_Webhook.
سيحتوي كل حزمة من SparkPost Relay_Webhook على معلومات نسخة مكررة واحدة في كل مرة، لذا فإن تقسيم بنية JSON إلى المكونات المستهدفة لهذا المشروع هو بالأحرى بسيط. في كود PHP الخاص بي، الحصول على البريد الإلكتروني بتنسيق rfc822 كان سهلاً مثل السطور القليلة التالية من الكود:
if ($verb == "POST") { $body = file_get_contents("php://input"); $fields = json_decode($body, true); $rfc822body = $fields['0']['msys']['relay_message']['content']['email_rfc822']; $htmlbody = $fields['0']['msys']['relay_message']['content'][html'] $headers = $fields['0']['msys']['relay_message']['content']['headers'];}
بعض المعلومات التي أرغب في تخزينها في جدول SQL الخاص بي تقع في مصفوفة حقول الرؤوس. لذلك كتبت دالة صغيرة تقبل مصفوفة الرؤوس وتقوم بالتكرار عليها للحصول على البيانات التي كنت مهتمًا بتخزينها:
function get_important_headers($headers, &$original_to, &$headerDate, &$subject, &$from) { foreach ($headers as $key => $value) { foreach ($value as $key_sub => $value_sub) { if ($key_sub == 'To') $original_to = $value_sub; if ($key_sub == 'Date') $headerDate = $value_sub; if ($key_sub == 'Subject') $subject = $value_sub; if ($key_sub == 'From') $from = $value_sub; } } }
الآن بعد أن حصلت على البيانات، أنا جاهز لتخزين المحتوى في S3.
تخزين البريد الإلكتروني المكرر في S3
أعتذر عن خيبة أملك ولكنني لن أقدم لك برنامجا تعليما خطوة بخطوة لإنشاء مستودع S3 لتخزين البريد الإلكتروني ولا أعتزم وصف كيفية إنشاء مفتاح الوصول الضروري الذي ستحتاجه في تطبيقك لرفع المحتوى إلى المستودع الخاص بك؛ هناك دروس أكثر جودة حول هذا الموضوع مما يمكنني كتابته. إليك بعض المقالات التي قد تساعدك:
https://docs.aws.amazon.com/quickstarts/latest/s3backup/step-1-create-bucket.html
https://aws.amazon.com/blogs/security/wheres-my-secret-access-key/
ما سأفعله هو الإشارة إلى بعض الإعدادات التي اخترتها والتي تتعلق بمشروع مثل هذا.
التحكم في الوصول. لا تحتاج فقط إلى تحديد الأمان للمستودع، بل تحتاج إلى تعيين الأذونات للعناصر نفسها. في مشروعي، أستخدم سياسة مفتوحة جدا للقراءة العامة لأن البيانات التجريبية ليست شخصية وأردت الوصول السهل إلى البيانات. من المحتمل أنك ستحتاج إلى مجموعة أكثر صرامة من سياسات ACL. هنا مقالة لطيفة عن إعدادات ACL: https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html
أرشفة الأرشيف. في S3 هناك شيء يسمى إدارة دورة الحياة. هذا يسمح لك بنقل البيانات من نوع من فئة تخزين S3 إلى أخرى. تمثل الفئات المختلفة من التخزين مقدار الوصول الذي تحتاجه للبيانات المخزنة مع تقليل التكاليف المرتبطة بالتخزين الذي تصل إليه بشكل أقل. يمكن العثور على كتابة جيدة للفئات المختلفة والانتقال بينها في دليل AWS يسمى، Transitioning Objects. في حالتي، اخترت إنشاء دورة حياة تنقل كل عنصر من Standard إلى Glacier بعد عام واحد. الوصول إلى Glacier أرخص بكثير من أرشيف S3 القياسي وسيوفر لي المال في تكاليف التخزين.
بمجرد إنشاء مستودع S3 ووضع إعداداتي في مكانها، أصبح S3 جاهزا لي لتحميل البريد الإلكتروني المتوافق مع rfc822 الذي حصلت عليه من تدفق البيانات SparkPost Relay Webhook. ولكن قبل تحميل بريد rfc822 إلى S3 أحتاج إلى إنشاء اسم ملف فريد سأستخدمه لتخزين ذلك البريد.
للحصول على اسم الملف الفريد، سأتبحث في جسم البريد الإلكتروني عن المعرف المخفي الذي وضعه التطبيق المرسل في البريد الإلكتروني واستخدام ذلك المعرف كاسم للملف. هناك طرق أكثر أناقة لاستخراج connectorId من جسم html، ولكن من أجل البساطة والوضوح سأستخدم الكود التالي:
$start = strpos($htmlbody, $inputField); $start = strpos($htmlbody, "value=", $start) + 7; $end = strpos($htmlbody, ">", $start) - 1; $length = $end - $start; $UID = substr($html, $start, $length);
* نحن نفترض أن $inputField يحتوي على القيمة "ArchiveCode" وتم العثور عليه في ملف config.php الخاص بي.
مع UID، يمكننا بعد ذلك إنشاء اسم الملف الذي سيتم استخدامه في S3:
$fileName = $ArchiveDirectory . '/' . $UID . '.eml';
الآن أستطيع فتح الاتصال الخاص بي إلى S3 وتحميل الملف. إذا نظرت إلى ملف s3.php في مستودع GitHub سترى أنه يتطلب القليل من الشيفرة لتحميل الملف.
خطوتي الأخيرة هي تسجيل هذا الإدخال في جدول MYSQL.
تخزين البيانات الوصفية في MySQL
لقد حصلنا على جميع البيانات اللازمة في خطوة سابقة، لذلك فإن خطوة التخزين سهلة. في هذه المرحلة الأولى اخترت بناء جدول مع الحقول التالية:
إدخال حقل تلقائي للتاريخ/الوقت
عنوان البريد الإلكتروني المستهدف (RCPT_TO)
التوقيت الزمني من رأس تاريخ البريد الإلكتروني
رأس الموضوع
رأس عنوان البريد الإلكتروني الوارد
الدليل المستخدم في حاوية S3
اسم الملف في S3 للبريد الإلكتروني المؤرشف
الوظيفة المسماة، MySQLLog داخل ملف التطبيق upload.php تمر بالخطوات اللازمة لفتح الرابط إلى MySQL، حقن الصف الجديد، اختبار النتائج وإغلاق الرابط. أضف خطوة أخرى للاحتياط وهي تسجيل هذه البيانات في ملف نصي. هل يجب علي القيام بالكثير من التسجيل للأخطاء؟ نعم. لكنني أريد أن أبقي هذه الشيفرة خفيفة بحيث تعمل بسرعة فائقة. في بعض الأحيان، سيتم استدعاء هذه الشيفرة مئات المرات في الدقيقة وتحتاج إلى أن تكون فعالة قدر الإمكان. في التحديثات المستقبلية، سأضيف شيفرة مساعدة لمعالجة الإخفاقات وإرسال تلك الإخفاقات إلى مدير للمراقبة.
ختامًا
لذلك في بعض الخطوات السهلة نسبيًا ، تمكنا من اجتياز المرحلة الأولى من بناء نظام قوي لأرشفة البريد الإلكتروني الذي يحتفظ بنسخة البريد الإلكتروني المكررة في S3 ومقارنة البيانات في جدول MySQL. سيمنحنا هذا أساسًا لبقية المشروع الذي سيتم التعامل معه في العديد من المشاركات المستقبلية.
في النسخ المستقبلية من هذا المشروع أتوقع أن:
تخزين جميع أحداث السجل للبريد الإلكتروني الأصلي
إرسال أخطاء التخزين إلى المسؤول عند حدوث فشل في التحميل أو التسجيل
تقليل تعقيد المجمع.
إضافة واجهة مستخدم لعرض جميع البيانات
دعم القدرة على إعادة إرسال البريد الإلكتروني
في الوقت الحالي، آمل أن يكون هذا المشروع قد كان مثيرًا للاهتمام ومفيدًا لك؛ إرسال سعيد.