بناء نظام أرشفة البريد الإلكتروني: التحديات وبالطبع الحل – الجزء 1

جيف غولدستين

04‏/02‏/2019

البريد الإلكتروني

1 min read

بناء نظام أرشفة البريد الإلكتروني: التحديات وبالطبع الحل – الجزء 1

النقاط الرئيسية

    • أصبح أرشفة البريد الإلكتروني أمرًا ضروريًا بشكل متزايد في البيئات التنظيمية والامتثال والتدقيق.

    • لا يقوم SparkPost بتخزين محتويات البريد الإلكتروني، ولكن تتيح ميزة الأرشيف للمرسلين تلقي رسائل مكررة تعكس الروابط والرسائل التتبعية.

    • يمكن تخزين محتويات البريد الإلكتروني في Amazon S3، بينما يمكن تخزين بيانات أحداث الرسائل في MySQL للاستعلام والمراجع المتقاطعة.

    • توفر أحداث رسائل SparkPost سجلات نشاط غنية (ارتدادات، عمليات تسليم، نقرات، فتحات، إلغاء اشتراكات، شكاوى والمزيد).

    • يتم إنشاء نسخ الأرشيف فقط عند إرسال البريد الإلكتروني عبر SMTP.

    • تشارك أحداث الرسائل للبريد الأصلي، الأرشيف، CC، و BCC معرف transmission_id مشترك.

    • يمكن لميزة Inbound Email Relay استيعاب الرسائل المؤرشفة ولكنها لا تتضمن transmission_id، مما يخلق تحدي ربط البيانات.

    • يسد تضمين معرف فريد مخفي (UID) في جسد الرسالة تلك الفجوة ويربط المحتوى الوارد بسجلات الخارج.

    • يتيح الجمع بين رسائل البريد المؤرشفة + أحداث الرسائل بناء نظام أرشفة قابل للبحث والتدقيق.

    • يشمل المشروع الطويل المدى إصدارات الأكواد لتخزين رسائل الأرشيف في S3 وتسجيل بيانات الحدث في MySQL.

    • سيسمح التطبيق النهائي بالبحث بسهولة، وعرض، وتسوية محتوى البريد الإلكتروني مع جميع التاريخ المتعلق بالأحداث.

    • مثالي للصناعات التي تتطلب امتثالًا صارمًا وتحتاج إلى رؤية كاملة لكل رسالة مرسلة.

أبرز الأسئلة والأجوبة

  • لماذا تقوم ببناء نظام أرشفة البريد الإلكتروني الخاص بك؟

    غالباً ما تتطلب الصناعات الخاضعة للتنظيم تخزين طويل الأجل لكل من نص البريد الإلكتروني وجميع سجلات الأحداث المرتبطة. لا يقوم SparkPost بتخزين نصوص الرسائل، لذا فإن بناء نظام مخصص يضمن الامتثال والتدقيق والرؤية.

  • كيف يمكنك الحصول على نسخة مطابقة من البريد الإلكتروني الأصلي المرسل؟

    ميزة الأرشيف من SparkPost ترسل نسخة من كل بريد إلكتروني مرسل إلى عناوين الأرشيف المخصصة، مع الحفاظ على جميع الروابط المشفرة وسلوكيات التتبع.

  • لماذا لا يمكنك التقاط نص البريد الإلكتروني قبل الإرسال؟

    الالتقاط المسبق لا يتضمن تعديلات SparkPost (تتبع الفتح، تتبع النقر، ترميز الروابط). استخدام نسخ الأرشيف يضمن أن النسخة المحفوظة لديك تطابق تمامًا ما يتلقاه المستلمون.

  • هل يقوم SparkPost بأرشفة الرسائل تلقائيًا؟

    لا، SparkPost لا يخزن محتويات الرسائل. يجب طلب نسخ الأرشيف عن طريق تحديد عناوين الأرشيف أثناء إدخال SMTP.

  • ما الذي يتم تخزينه وأين في نظام الأرشفة هذا؟

    • محتوى البريد الإلكتروني → Amazon S3

    • سجلات أحداث الرسائل → MySQL
      هذا الفصل يدعم البحث السريع والاستعلامات المنظمة وتخزين الكائنات منخفض التكلفة.

  • إلى متى تحتفظ SparkPost ببيانات الحدث؟

    يقوم SparkPost بتخزين أحداث الرسائل لمدة 10 أيام. بعد ذلك، يجب تناول البيانات عبر ويب هوك أو استعلامها وتخزينها في مكان آخر.

  • ما هي أنواع الرسائل المتاحة؟

    حاليًا، تعرض SparkPost أربعة عشر حدثاً، بما في ذلك عمليات التسليم، وارتدادات البريد، والنقرات، والفتح، والرفض، وقضايا السياسة، وشكاوى الرسائل غير المرغوب فيها، وإلغاء الاشتراك، والمزيد.

  • ما هي المُعرفات التي تربط جميع الأحداث معًا؟

    تشارك جميع الرسائل الصادرة (الأصلية، الأرشيف، CC، BCC) نفس transmission_id. كما تشارك الرسائل الأصلية والأرشيف نفس message_id.

  • لماذا تكون معالجة البيانات الواردة تحديًا؟

    تحويل البريد الإلكتروني الوارد في SparkPost إلى JSON، ولكن هذا JSON لا يحتوي على transmission_id. بدون بيانات إضافية، لا يمكن ربط النسخة الواردة بسجل السجل الخارج.

  • كيف تقوم بربط رسائل الأرشيف الواردة بأحداث الرسائل الصادرة؟

    أضف معرفًا فريدًا (UID) مخفيًا في نص البريد الإلكتروني ومرر نفس UID في البيانات الوصفية. يصبح هذا UID مرجعًا مشتركًا عبر السجلات الواردة والصادرة.

  • كيف تساعد خدمة Inbound Email Relay في أتمتة الأرشفة؟

    يتلقى رسائل الأرشيف المرسلة إلى نطاق الأرشيف الخاص بك، ويحللها إلى JSON منظم، ويرسلها إلى التطبيق الخاص بك عبر webhook—مما يسمح بالاستخلاص والتخزين التلقائي.

  • ما هي الرؤية طويلة المدى للمشروع؟

    تطبيق كامل يقوم ب:

    • تخزين رسائل البريد الإلكتروني المؤرشفة في S3

    • تخزين جميع سجلات الأحداث في MySQL

    • يسمح للمستخدمين بالبحث عن رسائل البريد الإلكتروني

    • عرض البريد الإلكتروني الأصلي وكل حدث مرتبط في واجهة موحدة واحدة

About a year ago I wrote a blog on how to retrieve copies of emails for archival and viewing but I did not broach the actual storing of the email or related data, and recently I wrote a blog on storing all of the event data (i.e. when the email was sent, opens, clicks bounces, unsubscribes, etc) on an email for the purpose of auditing, but chose not to create any supporting code.

With the increase of email usage in regulatory environments, I have decided it is time to start a new project that pulls all of this together with code samples on how to store the email body and all of its associated data. Over the next year, I will continue to build on this project with the aim to create a working storage and viewing application for archived emails and all log information produced by SparkPost. SparkPost does not have a system that archives the email body but it does make building an archival platform fairly easy.

In this blog series, I will describe the process I went through in order to store the email body onto S3 (Amazon’s Simple Store Service) and all relevant log data in MySQL for easy cross-referencing. For production archiving systems that require robust database backup strategies, consider implementing a comprehensive PostgreSQL backup and restore process to ensure your archival data is properly protected. Ultimately, this is the starting point for building an application that will allow for easy searching of archived emails, then displaying those emails along with the event (log) data. The code for this project can be found in the following GitHub repository: PHPArchivePlatform on GitHub

This first entry of the blog series is going to describe the challenge and lay out an architecture for the solution. The rest of the blogs will detail out portions of the solution along with code samples.

The first step in my process was to figure out how I was going to obtain a copy of the email sent to the original recipient. In order to obtain a copy of the email body, you need to either:


Email Body Capture Options

Method

Who creates the copy

Reflects tracking changes

Automation friendly

Used in this solution

Capture before send

Application

❌ No

✅ Yes

Email server stores copy

Mail server

✅ Yes

❌ Limited

SparkPost Archive feature

SparkPost

✅ Yes

✅ Yes


  1. Capture the email body before sending the email

  2. Get the email server to store a copy

  3. Have the email server create a copy for you to store

If the email server is adding items like link tracking or open tracking, you can’t use #1 because it won’t reflect the open/click tracking changes.

That means that either the server has to store the email or somehow offer a copy of that email to you for storage. Since SparkPost does not have a storage mechanism for email bodies but does have a way to create a copy of the email, we will have SparkPost send us a duplicate of the email for us to store in S3.

This is done by using SparkPost’s Archive feature. SparkPost’s Archive feature gives the sender the ability to tell SparkPost to send a duplicate of the email to one or more email addresses and use the same tracking and open links as the original. SparkPost documentation defines their Archive feature in the following manner:

Recipients in the archive list will receive an exact replica of the message that was sent to the RCPT TO address. In particular, any encoded links intended for the RCPT TO recipient will be identical in the archive messages

The only differences from the RCPT TO email are that some of the headers will be different since the target address for the archiving email is different, but the body of the email will be an exact replica!

If you want a deeper explanation here is a link to the SparkPost documentation on creating duplicate (or archive) copies of an email.

As a side note, SparkPost actually allows you to send emails to cc, bcc, and archive email addresses. For this solution, we are focused on the archive addresses.

* Notice * Archived emails can ONLY be created when injecting emails into SparkPost via SMTP!

Now that we know how to obtain a copy of the original email, we need to look at the log data that is produced and some of the subtle nuances within that data. SparkPost tracks everything that happens on its servers and offers that information up to you in the form of message-events. Those events are stored on SparkPost for 10 days and can be pulled from the server via a RESTful API called message-events, or you can have SparkPost push those events to any number of collecting applications that you wish. The push mechanism is done through webhooks and is done in real time.

Currently, there are 14 different events that may happen to an email.  Here is a list of the current events:

  • Bounce

  • ClickDelay

  • Delivery

  • Generation Failure

  • Generation Rejection

  • Initial Open

  • InjectionLink Unsubscribe

  • List Unsubscribe

  • Open

  • Out of Band

  • Policy RejectionSpam Complaint


* Follow this link for an up to date reference guide for a description of each event along with the data that is shared for each event.

Each event has numerous fields that match the event type. Some fields like the transmission_id are found in every event, but other fields may be more event-specific; for example, only open and click events have geotag information.


Identifiers Used in the Archiving System

Identifier

Where it originates

Shared across

Purpose

Limitation

transmission_id

SparkPost outbound

Original, archive, cc, bcc

Correlates all message events

Not available in inbound relay

message_id

SparkPost outbound

Original + archive

Identifies individual messages

Different for cc/bcc

Hidden UID

Injected by sender

Outbound + inbound

Links archived email body to events

Must be custom-implemented


One very important message event entry to this project is the transmission_id. All of the message event entries for the original email, archived email, and any cc and bcc addresses will share the same transmission_id.

There is also a common entry called the message_id that will have the same id for each entry of the original email and the archived email. Any cc or bcc addresses will have their own id for the message_id entry.

So far this sounds great and frankly fairly easy, but now is the challenging part. Remember, in order to get the archive email, we have SparkPost send a duplicate of the original email to another email address which corresponds to some inbox that you have access to. But in order to automate this solution and store the email body, I’m going to use another feature of SparkPost’s called Inbound Email Relaying. What that does, is take all emails sent to a specific domain and process them. By processing them, it rips the email apart and creates a JSON structure which is then delivered to an application via a webhook. See Appendix A for a sample JSON.

If you look real carefully, you will notice that the JSON structure from the inbound relay is missing a very important field; the transmission_id. While all of the outbound emails have the transmission_id  with the same entry which binds all of the data from the original email, archive, cc, and bcc addresses; SparkPost has no way to know that the email captured by the inbound process is connected to any of the outbound emails. The inbound process simply knows that an email was sent to a specific domain and to parse the email. That’s it. It will treat any email sent to that domain the same way, be it a reply from a customer or the archive email send from SparkPost.

So the trick is; how do you glue the outbound data to the inbound process that just grabbed the archived version of the email? What I decided to do is to hide a unique id in the body of the email. How this is done is up to you, but I simply created an input field with the hidden tag turned on.

<input name="ArchiveCode" type="hidden" value="<<UID>>">

I also added that field into the metadata block of the X-MSYS-API header which is passed to SparkPost during injection. This hidden UID will end up being the glue to the whole process, and is a main component of the project and will be discussed in depth in the following blog posts.

Now that we have the UID that will glue this project together and understand why it’s necessary, I can start to build the vision of the overall project and corresponding blog posts.

  1. Capturing and storing the archive email along with a database entry for searching/indexing

  2. Capture all message event data

  3. Create an application to view the email and all corresponding data

Here is a simple diagram of the project:

build an email archiving system - diagram


The first drop of code will cover the archive process and storing the email onto S3, while the second code drop will cover storing all of the log data from message-events into MySQL. You can expect the first two code drops and blog entries sometime in early 2019.  If you have any questions or suggestions, please feel free to pass them along.

Happy Sending.
– Jeff


Appendix A:

JSON file example - email archiving system

About a year ago I wrote a blog on how to retrieve copies of emails for archival and viewing but I did not broach the actual storing of the email or related data, and recently I wrote a blog on storing all of the event data (i.e. when the email was sent, opens, clicks bounces, unsubscribes, etc) on an email for the purpose of auditing, but chose not to create any supporting code.

With the increase of email usage in regulatory environments, I have decided it is time to start a new project that pulls all of this together with code samples on how to store the email body and all of its associated data. Over the next year, I will continue to build on this project with the aim to create a working storage and viewing application for archived emails and all log information produced by SparkPost. SparkPost does not have a system that archives the email body but it does make building an archival platform fairly easy.

In this blog series, I will describe the process I went through in order to store the email body onto S3 (Amazon’s Simple Store Service) and all relevant log data in MySQL for easy cross-referencing. For production archiving systems that require robust database backup strategies, consider implementing a comprehensive PostgreSQL backup and restore process to ensure your archival data is properly protected. Ultimately, this is the starting point for building an application that will allow for easy searching of archived emails, then displaying those emails along with the event (log) data. The code for this project can be found in the following GitHub repository: PHPArchivePlatform on GitHub

This first entry of the blog series is going to describe the challenge and lay out an architecture for the solution. The rest of the blogs will detail out portions of the solution along with code samples.

The first step in my process was to figure out how I was going to obtain a copy of the email sent to the original recipient. In order to obtain a copy of the email body, you need to either:


Email Body Capture Options

Method

Who creates the copy

Reflects tracking changes

Automation friendly

Used in this solution

Capture before send

Application

❌ No

✅ Yes

Email server stores copy

Mail server

✅ Yes

❌ Limited

SparkPost Archive feature

SparkPost

✅ Yes

✅ Yes


  1. Capture the email body before sending the email

  2. Get the email server to store a copy

  3. Have the email server create a copy for you to store

If the email server is adding items like link tracking or open tracking, you can’t use #1 because it won’t reflect the open/click tracking changes.

That means that either the server has to store the email or somehow offer a copy of that email to you for storage. Since SparkPost does not have a storage mechanism for email bodies but does have a way to create a copy of the email, we will have SparkPost send us a duplicate of the email for us to store in S3.

This is done by using SparkPost’s Archive feature. SparkPost’s Archive feature gives the sender the ability to tell SparkPost to send a duplicate of the email to one or more email addresses and use the same tracking and open links as the original. SparkPost documentation defines their Archive feature in the following manner:

Recipients in the archive list will receive an exact replica of the message that was sent to the RCPT TO address. In particular, any encoded links intended for the RCPT TO recipient will be identical in the archive messages

The only differences from the RCPT TO email are that some of the headers will be different since the target address for the archiving email is different, but the body of the email will be an exact replica!

If you want a deeper explanation here is a link to the SparkPost documentation on creating duplicate (or archive) copies of an email.

As a side note, SparkPost actually allows you to send emails to cc, bcc, and archive email addresses. For this solution, we are focused on the archive addresses.

* Notice * Archived emails can ONLY be created when injecting emails into SparkPost via SMTP!

Now that we know how to obtain a copy of the original email, we need to look at the log data that is produced and some of the subtle nuances within that data. SparkPost tracks everything that happens on its servers and offers that information up to you in the form of message-events. Those events are stored on SparkPost for 10 days and can be pulled from the server via a RESTful API called message-events, or you can have SparkPost push those events to any number of collecting applications that you wish. The push mechanism is done through webhooks and is done in real time.

Currently, there are 14 different events that may happen to an email.  Here is a list of the current events:

  • Bounce

  • ClickDelay

  • Delivery

  • Generation Failure

  • Generation Rejection

  • Initial Open

  • InjectionLink Unsubscribe

  • List Unsubscribe

  • Open

  • Out of Band

  • Policy RejectionSpam Complaint


* Follow this link for an up to date reference guide for a description of each event along with the data that is shared for each event.

Each event has numerous fields that match the event type. Some fields like the transmission_id are found in every event, but other fields may be more event-specific; for example, only open and click events have geotag information.


Identifiers Used in the Archiving System

Identifier

Where it originates

Shared across

Purpose

Limitation

transmission_id

SparkPost outbound

Original, archive, cc, bcc

Correlates all message events

Not available in inbound relay

message_id

SparkPost outbound

Original + archive

Identifies individual messages

Different for cc/bcc

Hidden UID

Injected by sender

Outbound + inbound

Links archived email body to events

Must be custom-implemented


One very important message event entry to this project is the transmission_id. All of the message event entries for the original email, archived email, and any cc and bcc addresses will share the same transmission_id.

There is also a common entry called the message_id that will have the same id for each entry of the original email and the archived email. Any cc or bcc addresses will have their own id for the message_id entry.

So far this sounds great and frankly fairly easy, but now is the challenging part. Remember, in order to get the archive email, we have SparkPost send a duplicate of the original email to another email address which corresponds to some inbox that you have access to. But in order to automate this solution and store the email body, I’m going to use another feature of SparkPost’s called Inbound Email Relaying. What that does, is take all emails sent to a specific domain and process them. By processing them, it rips the email apart and creates a JSON structure which is then delivered to an application via a webhook. See Appendix A for a sample JSON.

If you look real carefully, you will notice that the JSON structure from the inbound relay is missing a very important field; the transmission_id. While all of the outbound emails have the transmission_id  with the same entry which binds all of the data from the original email, archive, cc, and bcc addresses; SparkPost has no way to know that the email captured by the inbound process is connected to any of the outbound emails. The inbound process simply knows that an email was sent to a specific domain and to parse the email. That’s it. It will treat any email sent to that domain the same way, be it a reply from a customer or the archive email send from SparkPost.

So the trick is; how do you glue the outbound data to the inbound process that just grabbed the archived version of the email? What I decided to do is to hide a unique id in the body of the email. How this is done is up to you, but I simply created an input field with the hidden tag turned on.

<input name="ArchiveCode" type="hidden" value="<<UID>>">

I also added that field into the metadata block of the X-MSYS-API header which is passed to SparkPost during injection. This hidden UID will end up being the glue to the whole process, and is a main component of the project and will be discussed in depth in the following blog posts.

Now that we have the UID that will glue this project together and understand why it’s necessary, I can start to build the vision of the overall project and corresponding blog posts.

  1. Capturing and storing the archive email along with a database entry for searching/indexing

  2. Capture all message event data

  3. Create an application to view the email and all corresponding data

Here is a simple diagram of the project:

build an email archiving system - diagram


The first drop of code will cover the archive process and storing the email onto S3, while the second code drop will cover storing all of the log data from message-events into MySQL. You can expect the first two code drops and blog entries sometime in early 2019.  If you have any questions or suggestions, please feel free to pass them along.

Happy Sending.
– Jeff


Appendix A:

JSON file example - email archiving system

About a year ago I wrote a blog on how to retrieve copies of emails for archival and viewing but I did not broach the actual storing of the email or related data, and recently I wrote a blog on storing all of the event data (i.e. when the email was sent, opens, clicks bounces, unsubscribes, etc) on an email for the purpose of auditing, but chose not to create any supporting code.

With the increase of email usage in regulatory environments, I have decided it is time to start a new project that pulls all of this together with code samples on how to store the email body and all of its associated data. Over the next year, I will continue to build on this project with the aim to create a working storage and viewing application for archived emails and all log information produced by SparkPost. SparkPost does not have a system that archives the email body but it does make building an archival platform fairly easy.

In this blog series, I will describe the process I went through in order to store the email body onto S3 (Amazon’s Simple Store Service) and all relevant log data in MySQL for easy cross-referencing. For production archiving systems that require robust database backup strategies, consider implementing a comprehensive PostgreSQL backup and restore process to ensure your archival data is properly protected. Ultimately, this is the starting point for building an application that will allow for easy searching of archived emails, then displaying those emails along with the event (log) data. The code for this project can be found in the following GitHub repository: PHPArchivePlatform on GitHub

This first entry of the blog series is going to describe the challenge and lay out an architecture for the solution. The rest of the blogs will detail out portions of the solution along with code samples.

The first step in my process was to figure out how I was going to obtain a copy of the email sent to the original recipient. In order to obtain a copy of the email body, you need to either:


Email Body Capture Options

Method

Who creates the copy

Reflects tracking changes

Automation friendly

Used in this solution

Capture before send

Application

❌ No

✅ Yes

Email server stores copy

Mail server

✅ Yes

❌ Limited

SparkPost Archive feature

SparkPost

✅ Yes

✅ Yes


  1. Capture the email body before sending the email

  2. Get the email server to store a copy

  3. Have the email server create a copy for you to store

If the email server is adding items like link tracking or open tracking, you can’t use #1 because it won’t reflect the open/click tracking changes.

That means that either the server has to store the email or somehow offer a copy of that email to you for storage. Since SparkPost does not have a storage mechanism for email bodies but does have a way to create a copy of the email, we will have SparkPost send us a duplicate of the email for us to store in S3.

This is done by using SparkPost’s Archive feature. SparkPost’s Archive feature gives the sender the ability to tell SparkPost to send a duplicate of the email to one or more email addresses and use the same tracking and open links as the original. SparkPost documentation defines their Archive feature in the following manner:

Recipients in the archive list will receive an exact replica of the message that was sent to the RCPT TO address. In particular, any encoded links intended for the RCPT TO recipient will be identical in the archive messages

The only differences from the RCPT TO email are that some of the headers will be different since the target address for the archiving email is different, but the body of the email will be an exact replica!

If you want a deeper explanation here is a link to the SparkPost documentation on creating duplicate (or archive) copies of an email.

As a side note, SparkPost actually allows you to send emails to cc, bcc, and archive email addresses. For this solution, we are focused on the archive addresses.

* Notice * Archived emails can ONLY be created when injecting emails into SparkPost via SMTP!

Now that we know how to obtain a copy of the original email, we need to look at the log data that is produced and some of the subtle nuances within that data. SparkPost tracks everything that happens on its servers and offers that information up to you in the form of message-events. Those events are stored on SparkPost for 10 days and can be pulled from the server via a RESTful API called message-events, or you can have SparkPost push those events to any number of collecting applications that you wish. The push mechanism is done through webhooks and is done in real time.

Currently, there are 14 different events that may happen to an email.  Here is a list of the current events:

  • Bounce

  • ClickDelay

  • Delivery

  • Generation Failure

  • Generation Rejection

  • Initial Open

  • InjectionLink Unsubscribe

  • List Unsubscribe

  • Open

  • Out of Band

  • Policy RejectionSpam Complaint


* Follow this link for an up to date reference guide for a description of each event along with the data that is shared for each event.

Each event has numerous fields that match the event type. Some fields like the transmission_id are found in every event, but other fields may be more event-specific; for example, only open and click events have geotag information.


Identifiers Used in the Archiving System

Identifier

Where it originates

Shared across

Purpose

Limitation

transmission_id

SparkPost outbound

Original, archive, cc, bcc

Correlates all message events

Not available in inbound relay

message_id

SparkPost outbound

Original + archive

Identifies individual messages

Different for cc/bcc

Hidden UID

Injected by sender

Outbound + inbound

Links archived email body to events

Must be custom-implemented


One very important message event entry to this project is the transmission_id. All of the message event entries for the original email, archived email, and any cc and bcc addresses will share the same transmission_id.

There is also a common entry called the message_id that will have the same id for each entry of the original email and the archived email. Any cc or bcc addresses will have their own id for the message_id entry.

So far this sounds great and frankly fairly easy, but now is the challenging part. Remember, in order to get the archive email, we have SparkPost send a duplicate of the original email to another email address which corresponds to some inbox that you have access to. But in order to automate this solution and store the email body, I’m going to use another feature of SparkPost’s called Inbound Email Relaying. What that does, is take all emails sent to a specific domain and process them. By processing them, it rips the email apart and creates a JSON structure which is then delivered to an application via a webhook. See Appendix A for a sample JSON.

If you look real carefully, you will notice that the JSON structure from the inbound relay is missing a very important field; the transmission_id. While all of the outbound emails have the transmission_id  with the same entry which binds all of the data from the original email, archive, cc, and bcc addresses; SparkPost has no way to know that the email captured by the inbound process is connected to any of the outbound emails. The inbound process simply knows that an email was sent to a specific domain and to parse the email. That’s it. It will treat any email sent to that domain the same way, be it a reply from a customer or the archive email send from SparkPost.

So the trick is; how do you glue the outbound data to the inbound process that just grabbed the archived version of the email? What I decided to do is to hide a unique id in the body of the email. How this is done is up to you, but I simply created an input field with the hidden tag turned on.

<input name="ArchiveCode" type="hidden" value="<<UID>>">

I also added that field into the metadata block of the X-MSYS-API header which is passed to SparkPost during injection. This hidden UID will end up being the glue to the whole process, and is a main component of the project and will be discussed in depth in the following blog posts.

Now that we have the UID that will glue this project together and understand why it’s necessary, I can start to build the vision of the overall project and corresponding blog posts.

  1. Capturing and storing the archive email along with a database entry for searching/indexing

  2. Capture all message event data

  3. Create an application to view the email and all corresponding data

Here is a simple diagram of the project:

build an email archiving system - diagram


The first drop of code will cover the archive process and storing the email onto S3, while the second code drop will cover storing all of the log data from message-events into MySQL. You can expect the first two code drops and blog entries sometime in early 2019.  If you have any questions or suggestions, please feel free to pass them along.

Happy Sending.
– Jeff


Appendix A:

JSON file example - email archiving system

منذ حوالي عام كتبت مدونة حول كيفية استرجاع نسخ من الرسائل الإلكترونية للأرشفة والعرض، لكنني لم أتطرق إلى التخزين الفعلي للبريد الإلكتروني أو البيانات ذات الصلة، ومؤخراً كتبت مدونة حول تخزين جميع بيانات الحدث (أي عندما تم إرسال البريد الإلكتروني، الفتحات، النقرات، الارتدادات، إلغاء الاشتراك، إلخ) على بريد إلكتروني لغرض التدقيق، لكنني اخترت عدم إنشاء أي كود داعم.

مع زيادة استخدام البريد الإلكتروني في البيئات التنظيمية، قررت أنه حان الوقت لبدء مشروع جديد يجمع كل ذلك معاً مع عينة من الأكواد حول كيفية تخزين محتوى البريد الإلكتروني وجميع بياناته المرتبطة. على مدار العام المقبل، سأواصل بناء هذا المشروع بهدف إنشاء تطبيق يعمل لتخزين وعرض رسائل البريد الإلكتروني المؤرشفة وجميع معلومات السجل التي تم إنشاؤها بواسطة SparkPost. SparkPost لا يمتلك نظامًا لأرشفة محتوى البريد الإلكتروني ولكن يجعل من بناء منصة أرشفة أمرًا سهلاً إلى حد ما.

في سلسلة المدونات هذه، سأصف العملية التي مررت بها من أجل تخزين محتوى البريد الإلكتروني على S3 (خدمة التخزين البسيطة من أمازون) وجميع بيانات السجل ذات الصلة في MySQL لسهولة الإحالة المرجعية. لأنظمة الأرشفة الإنتاجية التي تتطلب استراتيجيات نسخ احتياطي قوية لقاعدة البيانات، فكر في تنفيذ عملية نسخ واستعادة PostgreSQL شاملة لضمان حماية بيانات الأرشيف بشكل صحيح. في النهاية، هذه هي نقطة البداية لبناء تطبيق يسمح بالبحث السهل في رسائل البريد الإلكتروني المؤرشفة، ثم عرض تلك الرسائل مع بيانات الحدث (السجل). يمكن العثور على الكود لهذا المشروع في مستودع GitHub التالي: PHPArchivePlatform على GitHub

المدخل الأول لهذه السلسلة من المدونات سيوصف التحدي ويضع هيكلًا للحل. بقية المدونات ستفصل أجزاء من الحل مع عينات من الأكواد.

كانت الخطوة الأولى في عمليتي هي معرفة كيفية الحصول على نسخة من البريد الإلكتروني المرسل إلى المستلم الأصلي. من أجل الحصول على نسخة من محتوى البريد الإلكتروني، تحتاج إلى إما:

  1. التقاط محتوى البريد الإلكتروني قبل إرسال البريد الإلكتروني

  2. جعل خادم البريد الإلكتروني يخزن نسخة

  3. جعل خادم البريد الإلكتروني ينشئ نسخة لك لتخزينها

إذا كان خادم البريد الإلكتروني يضيف عناصر مثل تتبع الروابط أو تتبع الفتحات، لا يمكنك استخدام # 1 لأنه لن يعكس تغييرات تتبع الفتح/النقر.

هذا يعني أن الخادم يجب أن يخزن البريد الإلكتروني أو يقدم لك بطريقة ما نسخة من هذا البريد الإلكتروني للتخزين. نظرًا لأن SparkPost لا يمتلك آلية لتخزين محتويات البريد الإلكتروني ولكن لديه طريقة لإنشاء نسخة من البريد الإلكتروني، سنجعل SparkPost يرسل لنا نسخة مكررة من البريد الإلكتروني لنخزنها في S3.

يتم ذلك باستخدام ميزة أرشيف SparkPost. تتيح ميزة أرشيف SparkPost للمرسل القدرة على إخطار SparkPost لإرسال نسخة مكررة من البريد الإلكتروني إلى عنوان بريد إلكتروني واحد أو أكثر واستخدام نفس الروابط التتبعية والفتح مثل الأصلية. توضح وثائق SparkPost ميزتها الأرشيفية بالطريقة التالية:

المستلمون في قائمة الأرشيف سيتلقون نسخة طبق الأصل من الرسالة المرسلة إلى عنوان RCPT TO. على وجه الخصوص، أي روابط مشفرة مخصصة للمستلم RCPT TO ستكون متطابقة في رسائل الأرشيف

الفروق الوحيدة مع بريد RCPT TO الإلكتروني هي أن بعض الرؤوس ستكون مختلفة لأن العنوان المستهدف لبريد الأرشيف مختلف، لكن محتوى البريد الإلكتروني سيكون نسخة طبق الأصل!

إذا كنت ترغب في شرح أعمق، إليك رابط إلى وثائق SparkPost حول إنشاء نسخ مكررة (أو أرشيفية) من البريد الإلكتروني.

كنقطة جانبية، تتيح لك SparkPost في الواقع إرسال رسائل البريد الإلكتروني إلى عناوين cc و bcc و arجيف البريد الإلكتروني. بالنسبة لهذا الحل، نحن نركز على عناوين الأرشيف.

* تنبيه * لا يمكن إنشاء رسائل البريد الإلكتروني المؤرشفة إلا عند إدخال رسائل البريد الإلكتروني في SparkPost عبر SMTP!

الآن بعد أن نعرف كيفية الحصول على نسخة من البريد الإلكتروني الأصلي، نحتاج إلى النظر في البيانات السجلية التي يتم إنتاجها وبعض الفروق الدقيقة داخل تلك البيانات. تتبع SparkPost كل شيء يحدث على خوادمها وتقدم تلك المعلومات لك في شكل أحداث الرسائل. يتم تخزين تلك الأحداث على SparkPost لمدة 10 أيام ويمكن سحبها من الخادم عبر واجهة برمجة تطبيقات RESTful تسمى message-events، أو يمكنك جعل SparkPost تدفع تلك الأحداث إلى أي عدد من التطبيقات الجمع المناسبة لك. تتقدم آلية الدفع عبر webhooks وتم في الوقت الفعلي.

حاليًا، هناك 14 حدثًا مختلفًا قد يحدث لبريد إلكتروني.  إليك قائمة بالأحداث الحالية:

  • ارتداد

  • تأخير النقر

  • تسليم

  • فشل التوليد

  • رفض التوليد

  • فتح مبكر

  • إلغاء الاشتراك عبر الرابط

  • إلغاء الاشتراك من القائمة

  • فتح

  • خارج النطاق

  • رفض السياسة الشكوى من الرسائل الغير مرغوب فيها


* تابع هذا الرابط للحصول على دليل محدث لوصف كل حدث جنبًا إلى جنب مع البيانات التي يتم مشاركتها لكل حدث.

كل حدث لديه العديد من الحقول التي تتطابق مع نوع الحدث. بعض الحقول مثل transmission_id توجد في كل حدث، ولكن قد تكون الحقول الأخرى أكثر تحديدًا للحدث؛ على سبيل المثال، فقط أحداث الفتح والنقر تحتوي على معلومات تحديد الموقع الجغرافي.

إحدى إدخالات الحدث المهم جدًا لهذا المشروع هو transmission_id. سيتم مشاركة جميع إدخالات أحداث الرسائل للبريد الإلكتروني الأصلي، البريد الإلكتروني المؤرشف، وأية عناوين cc و bcc نفس ال-transmission_id.

هناك أيضًا إدخال مشترك يسمى message_id سيحمل نفس المعرف لكل إدخال للبريد الإلكتروني الأصلي والبريد الإلكتروني المؤرشف. أي عناوين cc أو bcc ستحتوي على معرّف خاص بها لإدخال message_id .

حتى الآن يبدو هذا رائعًا وبصراحة سهل نسبيًا، ولكن الآن تأتي الجزء المثير للتحدي. تذكر، للحصول على البريد الإلكتروني الأرشيفي، نطلب من SparkPost إرسال نسخة مكررة من البريد الإلكتروني الأصلي إلى بريد إلكتروني آخر يتناسب مع بعض صناديق البريد التي لديك حق الوصول إليها. ولكن من أجل أتمتة هذا الحل وتخزين محتوى البريد الإلكتروني، سأستخدم ميزة أخرى من ميزات SparkPost تسمى إعادة توجيه رسائل البريد الإلكتروني الواردة. ما تفعله تلك الميزة هو أخذ جميع الرسائل الإلكترونية المرسلة إلى نطاق محدد ومعالجتها. عند معالجتها، يتم تحليل البريد الإلكتروني وإنشاء بنية JSON والتي يتم تسليمها إلى تطبيق عبر webhook. انظر الملحق أ للحصول على عينة JSON.

إذا نظرت بعناية، ستلاحظ أن بنية JSON من خاصية إعادة التوجيه الواردة تفتقر إلى حقل مهم للغاية؛ وهو الـtransmission_id. في حين أن جميع الرسائل الإلكترونية الصادرة تتضمن الـtransmission_id  مع نفس الإدخال الذي يربط جميع البيانات من البريد الإلكتروني الأصلي والأرشيف وcc وbcc العناوين؛ لا توجد لطريقة لـSparkPost لمعرفة أن البريد الإلكتروني الملتقط بواسطة العملية الواردة مرتبط بأي من الرسائل الإلكترونية الصادرة. تعرف العملية الواردة ببساطة أن البريد الإلكتروني أرسل إلى نطاق محدد وتحليل البريد الإلكتروني. هذا كل ما في الأمر. ستتعامل مع أي بريد إلكتروني يتم إرساله إلى هذا النطاق بنفس الطريقة، سواء كان ردًا من عميل أو البريد الإلكتروني المؤرشف المرسل من SparkPost.

إذاً ما الحيلة؟ كيف تقوم بصق البيانات الصادرة على العملية الواردة التي استحوذت للتو على النسخة الأرشيفية للبريد الإلكتروني؟ ما قررت القيام به هو إخفاء معرّف فريد في محتوى البريد الإلكتروني. كيف يتم ذلك يعود لك، لكنني قمت ببساطة بإنشاء حقل إدخال مع تفعيل علامة الإخفاء.

<input name="ArchiveCode" type="hidden" value="<<UID>>">

كما أضفت ذلك الحقل إلى كتلة البيانات الوصفية في رأس X-MSYS-API الذي يتم تمريره إلى SparkPost خلال الإدخال. سينتهي هذا UID المخفي بأن يكون الغراء لكل العملية، وهو عنصر رئيسي في المشروع وسيتم مناقشته بشكل مفصل في مشاركات المدونات التالية.

الآن بعد أن حصلنا على UID الذي سيربط هذا المشروع معًا وفهمنا لماذا هو ضروري، يمكنني البدء في بناء رؤية للمشروع العام ومشاركات المدونات المتوافقة.

  1. التقاط وتخزين البريد الإلكتروني المؤرشف جنبًا إلى جنب مع إدخال قاعدة بيانات للبحث/الفهرسة

  2. التقاط جميع بيانات أحداث الرسائل

  3. إنشاء تطبيق لعرض البريد الإلكتروني وجميع البيانات المقابلة

إليك رسمًا بسيطًا للمشروع:

build an email archiving system - diagram


الدفعة الأولى من الكود ستغطي عملية الأرشفة وتخزين البريد الإلكتروني على S3، بينما ستغطي الدفعة الثانية من الكود تخزين جميع بيانات السجل من message-events إلى MySQL. يمكنك توقع الحصول على أول دفعتين من الأكواد وإدخالات المدونة في وقت ما في أوائل عام 2019.  إذا كان لديك أي أسئلة أو اقتراحات، لا تتردد في تمريرها.

إرسال سعيد.
– جيف


ملحق أ:

JSON file example - email archiving system

منذ حوالي عام كتبت مدونة حول كيفية استرجاع نسخ من الرسائل الإلكترونية للأرشفة والعرض، لكنني لم أتطرق إلى التخزين الفعلي للبريد الإلكتروني أو البيانات ذات الصلة، ومؤخراً كتبت مدونة حول تخزين جميع بيانات الحدث (أي عندما تم إرسال البريد الإلكتروني، الفتحات، النقرات، الارتدادات، إلغاء الاشتراك، إلخ) على بريد إلكتروني لغرض التدقيق، لكنني اخترت عدم إنشاء أي كود داعم.

مع زيادة استخدام البريد الإلكتروني في البيئات التنظيمية، قررت أنه حان الوقت لبدء مشروع جديد يجمع كل ذلك معاً مع عينة من الأكواد حول كيفية تخزين محتوى البريد الإلكتروني وجميع بياناته المرتبطة. على مدار العام المقبل، سأواصل بناء هذا المشروع بهدف إنشاء تطبيق يعمل لتخزين وعرض رسائل البريد الإلكتروني المؤرشفة وجميع معلومات السجل التي تم إنشاؤها بواسطة SparkPost. SparkPost لا يمتلك نظامًا لأرشفة محتوى البريد الإلكتروني ولكن يجعل من بناء منصة أرشفة أمرًا سهلاً إلى حد ما.

في سلسلة المدونات هذه، سأصف العملية التي مررت بها من أجل تخزين محتوى البريد الإلكتروني على S3 (خدمة التخزين البسيطة من أمازون) وجميع بيانات السجل ذات الصلة في MySQL لسهولة الإحالة المرجعية. لأنظمة الأرشفة الإنتاجية التي تتطلب استراتيجيات نسخ احتياطي قوية لقاعدة البيانات، فكر في تنفيذ عملية نسخ واستعادة PostgreSQL شاملة لضمان حماية بيانات الأرشيف بشكل صحيح. في النهاية، هذه هي نقطة البداية لبناء تطبيق يسمح بالبحث السهل في رسائل البريد الإلكتروني المؤرشفة، ثم عرض تلك الرسائل مع بيانات الحدث (السجل). يمكن العثور على الكود لهذا المشروع في مستودع GitHub التالي: PHPArchivePlatform على GitHub

المدخل الأول لهذه السلسلة من المدونات سيوصف التحدي ويضع هيكلًا للحل. بقية المدونات ستفصل أجزاء من الحل مع عينات من الأكواد.

كانت الخطوة الأولى في عمليتي هي معرفة كيفية الحصول على نسخة من البريد الإلكتروني المرسل إلى المستلم الأصلي. من أجل الحصول على نسخة من محتوى البريد الإلكتروني، تحتاج إلى إما:

  1. التقاط محتوى البريد الإلكتروني قبل إرسال البريد الإلكتروني

  2. جعل خادم البريد الإلكتروني يخزن نسخة

  3. جعل خادم البريد الإلكتروني ينشئ نسخة لك لتخزينها

إذا كان خادم البريد الإلكتروني يضيف عناصر مثل تتبع الروابط أو تتبع الفتحات، لا يمكنك استخدام # 1 لأنه لن يعكس تغييرات تتبع الفتح/النقر.

هذا يعني أن الخادم يجب أن يخزن البريد الإلكتروني أو يقدم لك بطريقة ما نسخة من هذا البريد الإلكتروني للتخزين. نظرًا لأن SparkPost لا يمتلك آلية لتخزين محتويات البريد الإلكتروني ولكن لديه طريقة لإنشاء نسخة من البريد الإلكتروني، سنجعل SparkPost يرسل لنا نسخة مكررة من البريد الإلكتروني لنخزنها في S3.

يتم ذلك باستخدام ميزة أرشيف SparkPost. تتيح ميزة أرشيف SparkPost للمرسل القدرة على إخطار SparkPost لإرسال نسخة مكررة من البريد الإلكتروني إلى عنوان بريد إلكتروني واحد أو أكثر واستخدام نفس الروابط التتبعية والفتح مثل الأصلية. توضح وثائق SparkPost ميزتها الأرشيفية بالطريقة التالية:

المستلمون في قائمة الأرشيف سيتلقون نسخة طبق الأصل من الرسالة المرسلة إلى عنوان RCPT TO. على وجه الخصوص، أي روابط مشفرة مخصصة للمستلم RCPT TO ستكون متطابقة في رسائل الأرشيف

الفروق الوحيدة مع بريد RCPT TO الإلكتروني هي أن بعض الرؤوس ستكون مختلفة لأن العنوان المستهدف لبريد الأرشيف مختلف، لكن محتوى البريد الإلكتروني سيكون نسخة طبق الأصل!

إذا كنت ترغب في شرح أعمق، إليك رابط إلى وثائق SparkPost حول إنشاء نسخ مكررة (أو أرشيفية) من البريد الإلكتروني.

كنقطة جانبية، تتيح لك SparkPost في الواقع إرسال رسائل البريد الإلكتروني إلى عناوين cc و bcc و arجيف البريد الإلكتروني. بالنسبة لهذا الحل، نحن نركز على عناوين الأرشيف.

* تنبيه * لا يمكن إنشاء رسائل البريد الإلكتروني المؤرشفة إلا عند إدخال رسائل البريد الإلكتروني في SparkPost عبر SMTP!

الآن بعد أن نعرف كيفية الحصول على نسخة من البريد الإلكتروني الأصلي، نحتاج إلى النظر في البيانات السجلية التي يتم إنتاجها وبعض الفروق الدقيقة داخل تلك البيانات. تتبع SparkPost كل شيء يحدث على خوادمها وتقدم تلك المعلومات لك في شكل أحداث الرسائل. يتم تخزين تلك الأحداث على SparkPost لمدة 10 أيام ويمكن سحبها من الخادم عبر واجهة برمجة تطبيقات RESTful تسمى message-events، أو يمكنك جعل SparkPost تدفع تلك الأحداث إلى أي عدد من التطبيقات الجمع المناسبة لك. تتقدم آلية الدفع عبر webhooks وتم في الوقت الفعلي.

حاليًا، هناك 14 حدثًا مختلفًا قد يحدث لبريد إلكتروني.  إليك قائمة بالأحداث الحالية:

  • ارتداد

  • تأخير النقر

  • تسليم

  • فشل التوليد

  • رفض التوليد

  • فتح مبكر

  • إلغاء الاشتراك عبر الرابط

  • إلغاء الاشتراك من القائمة

  • فتح

  • خارج النطاق

  • رفض السياسة الشكوى من الرسائل الغير مرغوب فيها


* تابع هذا الرابط للحصول على دليل محدث لوصف كل حدث جنبًا إلى جنب مع البيانات التي يتم مشاركتها لكل حدث.

كل حدث لديه العديد من الحقول التي تتطابق مع نوع الحدث. بعض الحقول مثل transmission_id توجد في كل حدث، ولكن قد تكون الحقول الأخرى أكثر تحديدًا للحدث؛ على سبيل المثال، فقط أحداث الفتح والنقر تحتوي على معلومات تحديد الموقع الجغرافي.

إحدى إدخالات الحدث المهم جدًا لهذا المشروع هو transmission_id. سيتم مشاركة جميع إدخالات أحداث الرسائل للبريد الإلكتروني الأصلي، البريد الإلكتروني المؤرشف، وأية عناوين cc و bcc نفس ال-transmission_id.

هناك أيضًا إدخال مشترك يسمى message_id سيحمل نفس المعرف لكل إدخال للبريد الإلكتروني الأصلي والبريد الإلكتروني المؤرشف. أي عناوين cc أو bcc ستحتوي على معرّف خاص بها لإدخال message_id .

حتى الآن يبدو هذا رائعًا وبصراحة سهل نسبيًا، ولكن الآن تأتي الجزء المثير للتحدي. تذكر، للحصول على البريد الإلكتروني الأرشيفي، نطلب من SparkPost إرسال نسخة مكررة من البريد الإلكتروني الأصلي إلى بريد إلكتروني آخر يتناسب مع بعض صناديق البريد التي لديك حق الوصول إليها. ولكن من أجل أتمتة هذا الحل وتخزين محتوى البريد الإلكتروني، سأستخدم ميزة أخرى من ميزات SparkPost تسمى إعادة توجيه رسائل البريد الإلكتروني الواردة. ما تفعله تلك الميزة هو أخذ جميع الرسائل الإلكترونية المرسلة إلى نطاق محدد ومعالجتها. عند معالجتها، يتم تحليل البريد الإلكتروني وإنشاء بنية JSON والتي يتم تسليمها إلى تطبيق عبر webhook. انظر الملحق أ للحصول على عينة JSON.

إذا نظرت بعناية، ستلاحظ أن بنية JSON من خاصية إعادة التوجيه الواردة تفتقر إلى حقل مهم للغاية؛ وهو الـtransmission_id. في حين أن جميع الرسائل الإلكترونية الصادرة تتضمن الـtransmission_id  مع نفس الإدخال الذي يربط جميع البيانات من البريد الإلكتروني الأصلي والأرشيف وcc وbcc العناوين؛ لا توجد لطريقة لـSparkPost لمعرفة أن البريد الإلكتروني الملتقط بواسطة العملية الواردة مرتبط بأي من الرسائل الإلكترونية الصادرة. تعرف العملية الواردة ببساطة أن البريد الإلكتروني أرسل إلى نطاق محدد وتحليل البريد الإلكتروني. هذا كل ما في الأمر. ستتعامل مع أي بريد إلكتروني يتم إرساله إلى هذا النطاق بنفس الطريقة، سواء كان ردًا من عميل أو البريد الإلكتروني المؤرشف المرسل من SparkPost.

إذاً ما الحيلة؟ كيف تقوم بصق البيانات الصادرة على العملية الواردة التي استحوذت للتو على النسخة الأرشيفية للبريد الإلكتروني؟ ما قررت القيام به هو إخفاء معرّف فريد في محتوى البريد الإلكتروني. كيف يتم ذلك يعود لك، لكنني قمت ببساطة بإنشاء حقل إدخال مع تفعيل علامة الإخفاء.

<input name="ArchiveCode" type="hidden" value="<<UID>>">

كما أضفت ذلك الحقل إلى كتلة البيانات الوصفية في رأس X-MSYS-API الذي يتم تمريره إلى SparkPost خلال الإدخال. سينتهي هذا UID المخفي بأن يكون الغراء لكل العملية، وهو عنصر رئيسي في المشروع وسيتم مناقشته بشكل مفصل في مشاركات المدونات التالية.

الآن بعد أن حصلنا على UID الذي سيربط هذا المشروع معًا وفهمنا لماذا هو ضروري، يمكنني البدء في بناء رؤية للمشروع العام ومشاركات المدونات المتوافقة.

  1. التقاط وتخزين البريد الإلكتروني المؤرشف جنبًا إلى جنب مع إدخال قاعدة بيانات للبحث/الفهرسة

  2. التقاط جميع بيانات أحداث الرسائل

  3. إنشاء تطبيق لعرض البريد الإلكتروني وجميع البيانات المقابلة

إليك رسمًا بسيطًا للمشروع:

build an email archiving system - diagram


الدفعة الأولى من الكود ستغطي عملية الأرشفة وتخزين البريد الإلكتروني على S3، بينما ستغطي الدفعة الثانية من الكود تخزين جميع بيانات السجل من message-events إلى MySQL. يمكنك توقع الحصول على أول دفعتين من الأكواد وإدخالات المدونة في وقت ما في أوائل عام 2019.  إذا كان لديك أي أسئلة أو اقتراحات، لا تتردد في تمريرها.

إرسال سعيد.
– جيف


ملحق أ:

JSON file example - email archiving system

منذ حوالي عام كتبت مدونة حول كيفية استرجاع نسخ من الرسائل الإلكترونية للأرشفة والعرض، لكنني لم أتطرق إلى التخزين الفعلي للبريد الإلكتروني أو البيانات ذات الصلة، ومؤخراً كتبت مدونة حول تخزين جميع بيانات الحدث (أي عندما تم إرسال البريد الإلكتروني، الفتحات، النقرات، الارتدادات، إلغاء الاشتراك، إلخ) على بريد إلكتروني لغرض التدقيق، لكنني اخترت عدم إنشاء أي كود داعم.

مع زيادة استخدام البريد الإلكتروني في البيئات التنظيمية، قررت أنه حان الوقت لبدء مشروع جديد يجمع كل ذلك معاً مع عينة من الأكواد حول كيفية تخزين محتوى البريد الإلكتروني وجميع بياناته المرتبطة. على مدار العام المقبل، سأواصل بناء هذا المشروع بهدف إنشاء تطبيق يعمل لتخزين وعرض رسائل البريد الإلكتروني المؤرشفة وجميع معلومات السجل التي تم إنشاؤها بواسطة SparkPost. SparkPost لا يمتلك نظامًا لأرشفة محتوى البريد الإلكتروني ولكن يجعل من بناء منصة أرشفة أمرًا سهلاً إلى حد ما.

في سلسلة المدونات هذه، سأصف العملية التي مررت بها من أجل تخزين محتوى البريد الإلكتروني على S3 (خدمة التخزين البسيطة من أمازون) وجميع بيانات السجل ذات الصلة في MySQL لسهولة الإحالة المرجعية. لأنظمة الأرشفة الإنتاجية التي تتطلب استراتيجيات نسخ احتياطي قوية لقاعدة البيانات، فكر في تنفيذ عملية نسخ واستعادة PostgreSQL شاملة لضمان حماية بيانات الأرشيف بشكل صحيح. في النهاية، هذه هي نقطة البداية لبناء تطبيق يسمح بالبحث السهل في رسائل البريد الإلكتروني المؤرشفة، ثم عرض تلك الرسائل مع بيانات الحدث (السجل). يمكن العثور على الكود لهذا المشروع في مستودع GitHub التالي: PHPArchivePlatform على GitHub

المدخل الأول لهذه السلسلة من المدونات سيوصف التحدي ويضع هيكلًا للحل. بقية المدونات ستفصل أجزاء من الحل مع عينات من الأكواد.

كانت الخطوة الأولى في عمليتي هي معرفة كيفية الحصول على نسخة من البريد الإلكتروني المرسل إلى المستلم الأصلي. من أجل الحصول على نسخة من محتوى البريد الإلكتروني، تحتاج إلى إما:

  1. التقاط محتوى البريد الإلكتروني قبل إرسال البريد الإلكتروني

  2. جعل خادم البريد الإلكتروني يخزن نسخة

  3. جعل خادم البريد الإلكتروني ينشئ نسخة لك لتخزينها

إذا كان خادم البريد الإلكتروني يضيف عناصر مثل تتبع الروابط أو تتبع الفتحات، لا يمكنك استخدام # 1 لأنه لن يعكس تغييرات تتبع الفتح/النقر.

هذا يعني أن الخادم يجب أن يخزن البريد الإلكتروني أو يقدم لك بطريقة ما نسخة من هذا البريد الإلكتروني للتخزين. نظرًا لأن SparkPost لا يمتلك آلية لتخزين محتويات البريد الإلكتروني ولكن لديه طريقة لإنشاء نسخة من البريد الإلكتروني، سنجعل SparkPost يرسل لنا نسخة مكررة من البريد الإلكتروني لنخزنها في S3.

يتم ذلك باستخدام ميزة أرشيف SparkPost. تتيح ميزة أرشيف SparkPost للمرسل القدرة على إخطار SparkPost لإرسال نسخة مكررة من البريد الإلكتروني إلى عنوان بريد إلكتروني واحد أو أكثر واستخدام نفس الروابط التتبعية والفتح مثل الأصلية. توضح وثائق SparkPost ميزتها الأرشيفية بالطريقة التالية:

المستلمون في قائمة الأرشيف سيتلقون نسخة طبق الأصل من الرسالة المرسلة إلى عنوان RCPT TO. على وجه الخصوص، أي روابط مشفرة مخصصة للمستلم RCPT TO ستكون متطابقة في رسائل الأرشيف

الفروق الوحيدة مع بريد RCPT TO الإلكتروني هي أن بعض الرؤوس ستكون مختلفة لأن العنوان المستهدف لبريد الأرشيف مختلف، لكن محتوى البريد الإلكتروني سيكون نسخة طبق الأصل!

إذا كنت ترغب في شرح أعمق، إليك رابط إلى وثائق SparkPost حول إنشاء نسخ مكررة (أو أرشيفية) من البريد الإلكتروني.

كنقطة جانبية، تتيح لك SparkPost في الواقع إرسال رسائل البريد الإلكتروني إلى عناوين cc و bcc و arجيف البريد الإلكتروني. بالنسبة لهذا الحل، نحن نركز على عناوين الأرشيف.

* تنبيه * لا يمكن إنشاء رسائل البريد الإلكتروني المؤرشفة إلا عند إدخال رسائل البريد الإلكتروني في SparkPost عبر SMTP!

الآن بعد أن نعرف كيفية الحصول على نسخة من البريد الإلكتروني الأصلي، نحتاج إلى النظر في البيانات السجلية التي يتم إنتاجها وبعض الفروق الدقيقة داخل تلك البيانات. تتبع SparkPost كل شيء يحدث على خوادمها وتقدم تلك المعلومات لك في شكل أحداث الرسائل. يتم تخزين تلك الأحداث على SparkPost لمدة 10 أيام ويمكن سحبها من الخادم عبر واجهة برمجة تطبيقات RESTful تسمى message-events، أو يمكنك جعل SparkPost تدفع تلك الأحداث إلى أي عدد من التطبيقات الجمع المناسبة لك. تتقدم آلية الدفع عبر webhooks وتم في الوقت الفعلي.

حاليًا، هناك 14 حدثًا مختلفًا قد يحدث لبريد إلكتروني.  إليك قائمة بالأحداث الحالية:

  • ارتداد

  • تأخير النقر

  • تسليم

  • فشل التوليد

  • رفض التوليد

  • فتح مبكر

  • إلغاء الاشتراك عبر الرابط

  • إلغاء الاشتراك من القائمة

  • فتح

  • خارج النطاق

  • رفض السياسة الشكوى من الرسائل الغير مرغوب فيها


* تابع هذا الرابط للحصول على دليل محدث لوصف كل حدث جنبًا إلى جنب مع البيانات التي يتم مشاركتها لكل حدث.

كل حدث لديه العديد من الحقول التي تتطابق مع نوع الحدث. بعض الحقول مثل transmission_id توجد في كل حدث، ولكن قد تكون الحقول الأخرى أكثر تحديدًا للحدث؛ على سبيل المثال، فقط أحداث الفتح والنقر تحتوي على معلومات تحديد الموقع الجغرافي.

إحدى إدخالات الحدث المهم جدًا لهذا المشروع هو transmission_id. سيتم مشاركة جميع إدخالات أحداث الرسائل للبريد الإلكتروني الأصلي، البريد الإلكتروني المؤرشف، وأية عناوين cc و bcc نفس ال-transmission_id.

هناك أيضًا إدخال مشترك يسمى message_id سيحمل نفس المعرف لكل إدخال للبريد الإلكتروني الأصلي والبريد الإلكتروني المؤرشف. أي عناوين cc أو bcc ستحتوي على معرّف خاص بها لإدخال message_id .

حتى الآن يبدو هذا رائعًا وبصراحة سهل نسبيًا، ولكن الآن تأتي الجزء المثير للتحدي. تذكر، للحصول على البريد الإلكتروني الأرشيفي، نطلب من SparkPost إرسال نسخة مكررة من البريد الإلكتروني الأصلي إلى بريد إلكتروني آخر يتناسب مع بعض صناديق البريد التي لديك حق الوصول إليها. ولكن من أجل أتمتة هذا الحل وتخزين محتوى البريد الإلكتروني، سأستخدم ميزة أخرى من ميزات SparkPost تسمى إعادة توجيه رسائل البريد الإلكتروني الواردة. ما تفعله تلك الميزة هو أخذ جميع الرسائل الإلكترونية المرسلة إلى نطاق محدد ومعالجتها. عند معالجتها، يتم تحليل البريد الإلكتروني وإنشاء بنية JSON والتي يتم تسليمها إلى تطبيق عبر webhook. انظر الملحق أ للحصول على عينة JSON.

إذا نظرت بعناية، ستلاحظ أن بنية JSON من خاصية إعادة التوجيه الواردة تفتقر إلى حقل مهم للغاية؛ وهو الـtransmission_id. في حين أن جميع الرسائل الإلكترونية الصادرة تتضمن الـtransmission_id  مع نفس الإدخال الذي يربط جميع البيانات من البريد الإلكتروني الأصلي والأرشيف وcc وbcc العناوين؛ لا توجد لطريقة لـSparkPost لمعرفة أن البريد الإلكتروني الملتقط بواسطة العملية الواردة مرتبط بأي من الرسائل الإلكترونية الصادرة. تعرف العملية الواردة ببساطة أن البريد الإلكتروني أرسل إلى نطاق محدد وتحليل البريد الإلكتروني. هذا كل ما في الأمر. ستتعامل مع أي بريد إلكتروني يتم إرساله إلى هذا النطاق بنفس الطريقة، سواء كان ردًا من عميل أو البريد الإلكتروني المؤرشف المرسل من SparkPost.

إذاً ما الحيلة؟ كيف تقوم بصق البيانات الصادرة على العملية الواردة التي استحوذت للتو على النسخة الأرشيفية للبريد الإلكتروني؟ ما قررت القيام به هو إخفاء معرّف فريد في محتوى البريد الإلكتروني. كيف يتم ذلك يعود لك، لكنني قمت ببساطة بإنشاء حقل إدخال مع تفعيل علامة الإخفاء.

<input name="ArchiveCode" type="hidden" value="<<UID>>">

كما أضفت ذلك الحقل إلى كتلة البيانات الوصفية في رأس X-MSYS-API الذي يتم تمريره إلى SparkPost خلال الإدخال. سينتهي هذا UID المخفي بأن يكون الغراء لكل العملية، وهو عنصر رئيسي في المشروع وسيتم مناقشته بشكل مفصل في مشاركات المدونات التالية.

الآن بعد أن حصلنا على UID الذي سيربط هذا المشروع معًا وفهمنا لماذا هو ضروري، يمكنني البدء في بناء رؤية للمشروع العام ومشاركات المدونات المتوافقة.

  1. التقاط وتخزين البريد الإلكتروني المؤرشف جنبًا إلى جنب مع إدخال قاعدة بيانات للبحث/الفهرسة

  2. التقاط جميع بيانات أحداث الرسائل

  3. إنشاء تطبيق لعرض البريد الإلكتروني وجميع البيانات المقابلة

إليك رسمًا بسيطًا للمشروع:

build an email archiving system - diagram


الدفعة الأولى من الكود ستغطي عملية الأرشفة وتخزين البريد الإلكتروني على S3، بينما ستغطي الدفعة الثانية من الكود تخزين جميع بيانات السجل من message-events إلى MySQL. يمكنك توقع الحصول على أول دفعتين من الأكواد وإدخالات المدونة في وقت ما في أوائل عام 2019.  إذا كان لديك أي أسئلة أو اقتراحات، لا تتردد في تمريرها.

إرسال سعيد.
– جيف


ملحق أ:

JSON file example - email archiving system

أخبار أخرى

اقرأ المزيد من هذه الفئة

A person is standing at a desk while typing on a laptop.

المنصة الأصلية للذكاء الاصطناعي التي تتوسع مع عملك.

A person is standing at a desk while typing on a laptop.

المنصة الأصلية للذكاء الاصطناعي التي تتوسع مع عملك.

A person is standing at a desk while typing on a laptop.

المنصة الأصلية للذكاء الاصطناعي التي تتوسع مع عملك.