构建电子邮件归档系统:存储电子邮件正文

2019年3月4日

电子邮件

1 min read

构建电子邮件归档系统:存储电子邮件正文

关键要点

    • 目的: 本文概述了使用 SparkPostAmazon S3MySQL 构建电子邮件存档系统的第一阶段。它解释了如何复制、捕获和存储电子邮件以实现长期访问和合规性。

    • 核心理念: 系统自动将原始邮件正文(rfc822 格式)存储在 S3 中,并将元数据(主题、发件人、时间戳等)记录到 MySQL 中,以实现快速搜索和检索。

    • 涵盖要点:

      1. 创建存档副本: 使用 SparkPost 的 Archive feature 向指定存档地址发送外发邮件的相同副本,确保正文和追踪链接保持相同。

      2. 通过 UID 进行数据绑定: 将唯一标识符 (UID) 嵌入到邮件正文和 X-MSYS-API 元数据中,以链接原始和存档消息。

      3. 入站处理: 在 SparkPost 中配置入站域和 webhook,通过应用程序采集器接收存档邮件的 JSON 负载。

      4. 在 S3 中存储电子邮件: 将解析后的 rfc822 正文上传到 S3 存储桶,使用生命周期规则(例如一年后转换到 Glacier)降低存储成本。

      5. 在 MySQL 中记录元数据: 保存关键字段,例如 RCPT_TO、FROM、SUBJECT 和 S3 文件名,以用于搜索索引和将来检索。

      6. 性能考虑: 高效的代码和最小的日志记录确保采集器能够以最小的延迟每分钟处理数百个请求。

    • 大局观: 这个基础支持未来的增强——比如日志事件存储、故障警报和 UI 可视化——为可扩展、可审计的电子邮件存档解决方案奠定基础。

Q&A 精华

  • 这个项目的目标是什么?

    创建一个自动化电子邮件存档系统,将消息正文存储在Amazon S3中,同时在MySQL数据库中维护可搜索的元数据。

  • 为什么使用 SparkPost 的 Archive 功能?

    它允许您生成外发邮件的精确副本,保留其结构和跟踪数据以进行合规和审查。

  • 每封存档电子邮件如何链接到其原始消息?

    一个唯一的UID嵌入到电子邮件正文和元数据中,使原始副本和归档副本之间的准确交叉引用成为可能。

  • 为什么使用S3进行存储?

    S3 提供可扩展的存储和生命周期管理选项(如 Glacier),使得长期电子邮件保留变得具有成本效益。

  • MySQL数据库存储什么?

    它存储可搜索的元数据字段—如subject line、sender、timestamps和S3 filename—允许高效的查询和检索。

  • 接下来的开发步骤是什么?

    添加日志事件追踪、自动错误报告、简化的收集器,以及用于查看或重新发送归档电子邮件的用户界面。

在这篇博文中,我将描述我如何将电子邮件的正文存储到S3(亚马逊的Simple Store Service)以及辅助数据存储到MySQL表中,以便于交叉引用。最终,这将是代码库的起点,该代码库将包括一个应用程序,允许轻松搜索存档的邮件,然后显示这些邮件以及事件(日志)数据。这个项目的代码可以在以下GitHub代码库中找到:https://github.com/jeff-goldstein/PHPArchivePlatform

虽然我将在这个项目中利用S3和MySQL,但绝不是仅有的可以用于构建存档平台的技术,鉴于它们的普及性,我认为它们是这个项目的不错选择。在一个全规模高容量系统中,我会使用比MySQL性能更高的数据库,但对于这个示例项目,MySQL非常合适。对于考虑选择PostgreSQL作为存档数据库的组织,实施适当的备份和还原程序对于维护生产系统中的数据完整性是至关重要的。

在这个项目的第一阶段中,我详细介绍了我采取的步骤:

  1. 创建用于存档的重复电子邮件

  2. 使用SparkPost的Archiving和Inbound Relay功能,将原始电子邮件的副本发送回SparkPost进行处理,转换为JSON结构,然后发送到webhook收集器(应用程序)

  3. 拆解JSON结构以获取必要的组件

  4. 将电子邮件正文发送到S3进行存储

  5. 在MySQL中为每封电子邮件记录一个条目以供交叉引用

在这篇博文中,我将描述我如何将电子邮件的正文存储到S3(亚马逊的Simple Store Service)以及辅助数据存储到MySQL表中,以便于交叉引用。最终,这将是代码库的起点,该代码库将包括一个应用程序,允许轻松搜索存档的邮件,然后显示这些邮件以及事件(日志)数据。这个项目的代码可以在以下GitHub代码库中找到:https://github.com/jeff-goldstein/PHPArchivePlatform

虽然我将在这个项目中利用S3和MySQL,但绝不是仅有的可以用于构建存档平台的技术,鉴于它们的普及性,我认为它们是这个项目的不错选择。在一个全规模高容量系统中,我会使用比MySQL性能更高的数据库,但对于这个示例项目,MySQL非常合适。对于考虑选择PostgreSQL作为存档数据库的组织,实施适当的备份和还原程序对于维护生产系统中的数据完整性是至关重要的。

在这个项目的第一阶段中,我详细介绍了我采取的步骤:

  1. 创建用于存档的重复电子邮件

  2. 使用SparkPost的Archiving和Inbound Relay功能,将原始电子邮件的副本发送回SparkPost进行处理,转换为JSON结构,然后发送到webhook收集器(应用程序)

  3. 拆解JSON结构以获取必要的组件

  4. 将电子邮件正文发送到S3进行存储

  5. 在MySQL中为每封电子邮件记录一个条目以供交叉引用

在这篇博文中,我将描述我如何将电子邮件的正文存储到S3(亚马逊的Simple Store Service)以及辅助数据存储到MySQL表中,以便于交叉引用。最终,这将是代码库的起点,该代码库将包括一个应用程序,允许轻松搜索存档的邮件,然后显示这些邮件以及事件(日志)数据。这个项目的代码可以在以下GitHub代码库中找到:https://github.com/jeff-goldstein/PHPArchivePlatform

虽然我将在这个项目中利用S3和MySQL,但绝不是仅有的可以用于构建存档平台的技术,鉴于它们的普及性,我认为它们是这个项目的不错选择。在一个全规模高容量系统中,我会使用比MySQL性能更高的数据库,但对于这个示例项目,MySQL非常合适。对于考虑选择PostgreSQL作为存档数据库的组织,实施适当的备份和还原程序对于维护生产系统中的数据完整性是至关重要的。

在这个项目的第一阶段中,我详细介绍了我采取的步骤:

  1. 创建用于存档的重复电子邮件

  2. 使用SparkPost的Archiving和Inbound Relay功能,将原始电子邮件的副本发送回SparkPost进行处理,转换为JSON结构,然后发送到webhook收集器(应用程序)

  3. 拆解JSON结构以获取必要的组件

  4. 将电子邮件正文发送到S3进行存储

  5. 在MySQL中为每封电子邮件记录一个条目以供交叉引用

创建邮件的副本

在 SparkPost 中,归档电子邮件的最佳方式是创建一个专门为归档目的设计的电子邮件的相同副本。这是通过使用 SparkPost 的 Archive 功能来实现的。SparkPost 的 Archive 功能使发送者能够将电子邮件的副本发送到一个或多个电子邮件地址。这个副本使用与原始邮件相同的跟踪和打开链接。SparkPost 文档如下定义了 Archive 功能:

归档列表中的收件人将收到发送给 RCPT TO 地址的邮件的完整副本。特别是,针对 RCPT TO 收件人的任何编码链接在归档邮件中将是相同的。

此归档副本与原始 RCPT TO 邮件之间的唯一区别是某些标头会有所不同,因为归档电子邮件的目标地址不同,但电子邮件的主体将是一个完全相同的副本!

如果您想要更深入的解释,请参阅此 链接,了解有关创建电子邮件副本(或归档)的 SparkPost 文档。此项目的示例 X-MSYS-API 标头将在此博客中稍后显示。

这种方法有一个警告;虽然原始电子邮件中的所有事件信息都通过 transmission_id 和 message_id 连接在一起,但在入站中继事件中(获取和分发存档电子邮件的机制),没有用于连接回这两个 id 的信息,即没有原始电子邮件的信息。这意味着我们需要在电子邮件正文和原始电子邮件的标头中放置数据,以便将所有来自原始电子邮件和 Archive 邮件的 SparkPost 数据连接在一起。

为了创建放置在电子邮件正文中的代码,我在电子邮件创建应用程序中使用了以下流程。

  1. 在电子邮件正文的某个地方,我放置了以下输入条目:<input name="ArchiveCode" type="hidden" value="<<UID>>">

  2. 然后我创建了一个唯一代码并替换了 <<UID>> 字段:$uid = md5(uniqid(rand(), true)); $emailBody = str_replace(“<<UID>>,$uid,$emailBody);

    以下是示例输出:

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

  3. 接下来,我确保将 $UID 添加到 X-MSYS-API 标头的 meta_data 块中。此步骤确保 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>"
  }
}

现在我们有了一种方法,将所有来自原始电子邮件的数据绑定到存档的电子邮件正文中。

在 SparkPost 中,归档电子邮件的最佳方式是创建一个专门为归档目的设计的电子邮件的相同副本。这是通过使用 SparkPost 的 Archive 功能来实现的。SparkPost 的 Archive 功能使发送者能够将电子邮件的副本发送到一个或多个电子邮件地址。这个副本使用与原始邮件相同的跟踪和打开链接。SparkPost 文档如下定义了 Archive 功能:

归档列表中的收件人将收到发送给 RCPT TO 地址的邮件的完整副本。特别是,针对 RCPT TO 收件人的任何编码链接在归档邮件中将是相同的。

此归档副本与原始 RCPT TO 邮件之间的唯一区别是某些标头会有所不同,因为归档电子邮件的目标地址不同,但电子邮件的主体将是一个完全相同的副本!

如果您想要更深入的解释,请参阅此 链接,了解有关创建电子邮件副本(或归档)的 SparkPost 文档。此项目的示例 X-MSYS-API 标头将在此博客中稍后显示。

这种方法有一个警告;虽然原始电子邮件中的所有事件信息都通过 transmission_id 和 message_id 连接在一起,但在入站中继事件中(获取和分发存档电子邮件的机制),没有用于连接回这两个 id 的信息,即没有原始电子邮件的信息。这意味着我们需要在电子邮件正文和原始电子邮件的标头中放置数据,以便将所有来自原始电子邮件和 Archive 邮件的 SparkPost 数据连接在一起。

为了创建放置在电子邮件正文中的代码,我在电子邮件创建应用程序中使用了以下流程。

  1. 在电子邮件正文的某个地方,我放置了以下输入条目:<input name="ArchiveCode" type="hidden" value="<<UID>>">

  2. 然后我创建了一个唯一代码并替换了 <<UID>> 字段:$uid = md5(uniqid(rand(), true)); $emailBody = str_replace(“<<UID>>,$uid,$emailBody);

    以下是示例输出:

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

  3. 接下来,我确保将 $UID 添加到 X-MSYS-API 标头的 meta_data 块中。此步骤确保 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>"
  }
}

现在我们有了一种方法,将所有来自原始电子邮件的数据绑定到存档的电子邮件正文中。

在 SparkPost 中,归档电子邮件的最佳方式是创建一个专门为归档目的设计的电子邮件的相同副本。这是通过使用 SparkPost 的 Archive 功能来实现的。SparkPost 的 Archive 功能使发送者能够将电子邮件的副本发送到一个或多个电子邮件地址。这个副本使用与原始邮件相同的跟踪和打开链接。SparkPost 文档如下定义了 Archive 功能:

归档列表中的收件人将收到发送给 RCPT TO 地址的邮件的完整副本。特别是,针对 RCPT TO 收件人的任何编码链接在归档邮件中将是相同的。

此归档副本与原始 RCPT TO 邮件之间的唯一区别是某些标头会有所不同,因为归档电子邮件的目标地址不同,但电子邮件的主体将是一个完全相同的副本!

如果您想要更深入的解释,请参阅此 链接,了解有关创建电子邮件副本(或归档)的 SparkPost 文档。此项目的示例 X-MSYS-API 标头将在此博客中稍后显示。

这种方法有一个警告;虽然原始电子邮件中的所有事件信息都通过 transmission_id 和 message_id 连接在一起,但在入站中继事件中(获取和分发存档电子邮件的机制),没有用于连接回这两个 id 的信息,即没有原始电子邮件的信息。这意味着我们需要在电子邮件正文和原始电子邮件的标头中放置数据,以便将所有来自原始电子邮件和 Archive 邮件的 SparkPost 数据连接在一起。

为了创建放置在电子邮件正文中的代码,我在电子邮件创建应用程序中使用了以下流程。

  1. 在电子邮件正文的某个地方,我放置了以下输入条目:<input name="ArchiveCode" type="hidden" value="<<UID>>">

  2. 然后我创建了一个唯一代码并替换了 <<UID>> 字段:$uid = md5(uniqid(rand(), true)); $emailBody = str_replace(“<<UID>>,$uid,$emailBody);

    以下是示例输出:

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

  3. 接下来,我确保将 $UID 添加到 X-MSYS-API 标头的 meta_data 块中。此步骤确保 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>"
  }
}

现在我们有了一种方法,将所有来自原始电子邮件的数据绑定到存档的电子邮件正文中。

获取 Archive 版本

为了获取一份电子邮件的存档副本,您需要采取以下步骤:

  1. 创建一个子域,您将把所有存档(重复)的电子邮件发送到该子域

  2. 设置适当的DNS记录,将所有发送到该子域的电子邮件转发到SparkPost

  3. 在SparkPost中创建一个入站域

  4. 在SparkPost中创建一个入站Webhook

  5. 创建一个应用程序(收集器)来接收SparkPost的Webhook数据流

可以使用以下两个链接来帮助您指导此过程:

  1. SparkPost技术文档:启用入站电子邮件中继和中继Webhooks

  2. 另外,我去年写的博客,存档电子邮件:跟踪已发送邮件的指南,将引导您通过SparkPost创建入站中继

* 注意:截至2018年10月,存档功能仅在使用SMTP连接到SparkPost发送电子邮件时有效,RESTful API不支持此功能。这可能不是问题,因为需要此级别审计控制的大多数电子邮件往往是由后端应用程序完全构建的个性化电子邮件,在需要发送之前。

为了获取一份电子邮件的存档副本,您需要采取以下步骤:

  1. 创建一个子域,您将把所有存档(重复)的电子邮件发送到该子域

  2. 设置适当的DNS记录,将所有发送到该子域的电子邮件转发到SparkPost

  3. 在SparkPost中创建一个入站域

  4. 在SparkPost中创建一个入站Webhook

  5. 创建一个应用程序(收集器)来接收SparkPost的Webhook数据流

可以使用以下两个链接来帮助您指导此过程:

  1. SparkPost技术文档:启用入站电子邮件中继和中继Webhooks

  2. 另外,我去年写的博客,存档电子邮件:跟踪已发送邮件的指南,将引导您通过SparkPost创建入站中继

* 注意:截至2018年10月,存档功能仅在使用SMTP连接到SparkPost发送电子邮件时有效,RESTful API不支持此功能。这可能不是问题,因为需要此级别审计控制的大多数电子邮件往往是由后端应用程序完全构建的个性化电子邮件,在需要发送之前。

为了获取一份电子邮件的存档副本,您需要采取以下步骤:

  1. 创建一个子域,您将把所有存档(重复)的电子邮件发送到该子域

  2. 设置适当的DNS记录,将所有发送到该子域的电子邮件转发到SparkPost

  3. 在SparkPost中创建一个入站域

  4. 在SparkPost中创建一个入站Webhook

  5. 创建一个应用程序(收集器)来接收SparkPost的Webhook数据流

可以使用以下两个链接来帮助您指导此过程:

  1. SparkPost技术文档:启用入站电子邮件中继和中继Webhooks

  2. 另外,我去年写的博客,存档电子邮件:跟踪已发送邮件的指南,将引导您通过SparkPost创建入站中继

* 注意:截至2018年10月,存档功能仅在使用SMTP连接到SparkPost发送电子邮件时有效,RESTful API不支持此功能。这可能不是问题,因为需要此级别审计控制的大多数电子邮件往往是由后端应用程序完全构建的个性化电子邮件,在需要发送之前。

在JSON结构中获取重复的电子邮件

在这个项目的第一阶段,我所存储的仅仅是 S3 中的 rfc822 邮件格式以及一些用于搜索的 SQL 表的高级描述字段。 由于 SparkPost 将通过 webhook 数据流以 JSON 结构将电子邮件数据发送到我的归档平台,我构建了一个应用程序(通常称为collector)来接受 Relay_Webhook 数据流。

每个来自 SparkPost Relay_Webhook 的数据包将包含一个重复电子邮件的信息,因此将 JSON 结构分解为该项目的目标组件相对简单。 在我的 PHP 代码中,获取 rfc822 格式的电子邮件就像下面的几行代码一样简单:

if ($_SERVER['REQUEST_METHOD'] === '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 $headerGroup) {
        foreach ($headerGroup as $key => $value) {
            if ($key === 'To') {
                $original_to = $value;
            } elseif ($key === 'Date') {
                $headerDate = $value;
            } elseif ($key === 'Subject') {
                $subject = $value;
            } elseif ($key === 'From') {
                $from = $value;
            }
        }
    }
}

现在我已经有了数据,我准备将正文存储到 S3。

在这个项目的第一阶段,我所存储的仅仅是 S3 中的 rfc822 邮件格式以及一些用于搜索的 SQL 表的高级描述字段。 由于 SparkPost 将通过 webhook 数据流以 JSON 结构将电子邮件数据发送到我的归档平台,我构建了一个应用程序(通常称为collector)来接受 Relay_Webhook 数据流。

每个来自 SparkPost Relay_Webhook 的数据包将包含一个重复电子邮件的信息,因此将 JSON 结构分解为该项目的目标组件相对简单。 在我的 PHP 代码中,获取 rfc822 格式的电子邮件就像下面的几行代码一样简单:

if ($_SERVER['REQUEST_METHOD'] === '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 $headerGroup) {
        foreach ($headerGroup as $key => $value) {
            if ($key === 'To') {
                $original_to = $value;
            } elseif ($key === 'Date') {
                $headerDate = $value;
            } elseif ($key === 'Subject') {
                $subject = $value;
            } elseif ($key === 'From') {
                $from = $value;
            }
        }
    }
}

现在我已经有了数据,我准备将正文存储到 S3。

在这个项目的第一阶段,我所存储的仅仅是 S3 中的 rfc822 邮件格式以及一些用于搜索的 SQL 表的高级描述字段。 由于 SparkPost 将通过 webhook 数据流以 JSON 结构将电子邮件数据发送到我的归档平台,我构建了一个应用程序(通常称为collector)来接受 Relay_Webhook 数据流。

每个来自 SparkPost Relay_Webhook 的数据包将包含一个重复电子邮件的信息,因此将 JSON 结构分解为该项目的目标组件相对简单。 在我的 PHP 代码中,获取 rfc822 格式的电子邮件就像下面的几行代码一样简单:

if ($_SERVER['REQUEST_METHOD'] === '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 $headerGroup) {
        foreach ($headerGroup as $key => $value) {
            if ($key === 'To') {
                $original_to = $value;
            } elseif ($key === 'Date') {
                $headerDate = $value;
            } elseif ($key === 'Subject') {
                $subject = $value;
            } elseif ($key === 'From') {
                $from = $value;
            }
        }
    }
}

现在我已经有了数据,我准备将正文存储到 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/

我将要做的是指出我选择的一些与此类项目相关的设置。

  1. 访问控制。  您不仅需要为存储桶设置安全性,还需要为项目本身设置权限。 在我的项目中,我使用非常开放的公共读取政策,因为示例数据不是个人数据,我希望能够轻松访问数据。 您可能需要一组严格得多的 ACL 策略。这里有一篇关于 ACL 设置的不错文章: https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html

  2. 归档归档。 在 S3 中,有一种称为生命周期管理的功能。 这允许您将数据从一种 S3 存储类移动到另一种。 不同的存储类代表您访问存储数据所需的访问量,访问较少的存储具有较低的成本。对不同类别及其过渡的详细说明可以在 AWS 指南中找到,称为 Transitioning Objects。就我而言,我选择创建一个生命周期,在一年后将每个对象从标准存储移动到 Glacier。Glacier 访问比标准 S3 归档便宜得多,会为我节省存储成本。

一旦我创建了 S3 存储桶并设置了我的设置,S3 就准备好让我上传从 SparkPost Relay Webhook 数据流获得的 rfc822 兼容电子邮件。但在将 rfc822 电子邮件负载上传到 S3 之前,我需要创建一个用于存储该电子邮件的唯一文件名。

对于唯一文件名,我将搜索电子邮件正文中发送应用程序插入的隐藏 id,并使用该 id 作为文件名。有更优雅的方法可以从 HTML 正文中提取 connectorId,但为了简单和清晰,我将使用以下代码:

$start = strpos($htmlbody, $inputField);
if ($start !== false) {
    $start = strpos($htmlbody, 'value="', $start);
    if ($start !== false) {
        $start += 7; // Move past 'value="'
        $end = strpos($htmlbody, '"', $start);
        if ($end !== false) {
            $length = $end - $start;
            $UID = substr($htmlbody, $start, $length);
        }
    }
}

* 我们假设 $inputField 保存了值 "ArchiveCode",并在我的 config.php 文件中找到了。

利用 UID,我们可以制作将在 S3 中使用的文件名:

$fileName = $ArchiveDirectory . '/' . $UID . '.eml';

现在我可以打开到 S3 的连接并上传文件。如果您查看 GitHub 存储库中的 s3.php 文件,您会看到上传文件所需的代码非常少。

我的最后一步是将此条目记录到 MYSQL 表中。

很抱歉让你失望,但我不会提供有关创建 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/

我将要做的是指出我选择的一些与此类项目相关的设置。

  1. 访问控制。  您不仅需要为存储桶设置安全性,还需要为项目本身设置权限。 在我的项目中,我使用非常开放的公共读取政策,因为示例数据不是个人数据,我希望能够轻松访问数据。 您可能需要一组严格得多的 ACL 策略。这里有一篇关于 ACL 设置的不错文章: https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html

  2. 归档归档。 在 S3 中,有一种称为生命周期管理的功能。 这允许您将数据从一种 S3 存储类移动到另一种。 不同的存储类代表您访问存储数据所需的访问量,访问较少的存储具有较低的成本。对不同类别及其过渡的详细说明可以在 AWS 指南中找到,称为 Transitioning Objects。就我而言,我选择创建一个生命周期,在一年后将每个对象从标准存储移动到 Glacier。Glacier 访问比标准 S3 归档便宜得多,会为我节省存储成本。

一旦我创建了 S3 存储桶并设置了我的设置,S3 就准备好让我上传从 SparkPost Relay Webhook 数据流获得的 rfc822 兼容电子邮件。但在将 rfc822 电子邮件负载上传到 S3 之前,我需要创建一个用于存储该电子邮件的唯一文件名。

对于唯一文件名,我将搜索电子邮件正文中发送应用程序插入的隐藏 id,并使用该 id 作为文件名。有更优雅的方法可以从 HTML 正文中提取 connectorId,但为了简单和清晰,我将使用以下代码:

$start = strpos($htmlbody, $inputField);
if ($start !== false) {
    $start = strpos($htmlbody, 'value="', $start);
    if ($start !== false) {
        $start += 7; // Move past 'value="'
        $end = strpos($htmlbody, '"', $start);
        if ($end !== false) {
            $length = $end - $start;
            $UID = substr($htmlbody, $start, $length);
        }
    }
}

* 我们假设 $inputField 保存了值 "ArchiveCode",并在我的 config.php 文件中找到了。

利用 UID,我们可以制作将在 S3 中使用的文件名:

$fileName = $ArchiveDirectory . '/' . $UID . '.eml';

现在我可以打开到 S3 的连接并上传文件。如果您查看 GitHub 存储库中的 s3.php 文件,您会看到上传文件所需的代码非常少。

我的最后一步是将此条目记录到 MYSQL 表中。

很抱歉让你失望,但我不会提供有关创建 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/

我将要做的是指出我选择的一些与此类项目相关的设置。

  1. 访问控制。  您不仅需要为存储桶设置安全性,还需要为项目本身设置权限。 在我的项目中,我使用非常开放的公共读取政策,因为示例数据不是个人数据,我希望能够轻松访问数据。 您可能需要一组严格得多的 ACL 策略。这里有一篇关于 ACL 设置的不错文章: https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html

  2. 归档归档。 在 S3 中,有一种称为生命周期管理的功能。 这允许您将数据从一种 S3 存储类移动到另一种。 不同的存储类代表您访问存储数据所需的访问量,访问较少的存储具有较低的成本。对不同类别及其过渡的详细说明可以在 AWS 指南中找到,称为 Transitioning Objects。就我而言,我选择创建一个生命周期,在一年后将每个对象从标准存储移动到 Glacier。Glacier 访问比标准 S3 归档便宜得多,会为我节省存储成本。

一旦我创建了 S3 存储桶并设置了我的设置,S3 就准备好让我上传从 SparkPost Relay Webhook 数据流获得的 rfc822 兼容电子邮件。但在将 rfc822 电子邮件负载上传到 S3 之前,我需要创建一个用于存储该电子邮件的唯一文件名。

对于唯一文件名,我将搜索电子邮件正文中发送应用程序插入的隐藏 id,并使用该 id 作为文件名。有更优雅的方法可以从 HTML 正文中提取 connectorId,但为了简单和清晰,我将使用以下代码:

$start = strpos($htmlbody, $inputField);
if ($start !== false) {
    $start = strpos($htmlbody, 'value="', $start);
    if ($start !== false) {
        $start += 7; // Move past 'value="'
        $end = strpos($htmlbody, '"', $start);
        if ($end !== false) {
            $length = $end - $start;
            $UID = substr($htmlbody, $start, $length);
        }
    }
}

* 我们假设 $inputField 保存了值 "ArchiveCode",并在我的 config.php 文件中找到了。

利用 UID,我们可以制作将在 S3 中使用的文件名:

$fileName = $ArchiveDirectory . '/' . $UID . '.eml';

现在我可以打开到 S3 的连接并上传文件。如果您查看 GitHub 存储库中的 s3.php 文件,您会看到上传文件所需的代码非常少。

我的最后一步是将此条目记录到 MYSQL 表中。

在 MySQL 中存储 Meta Data

我们在以前的步骤中获取了所有必要的数据,所以存储的步骤很简单。 在这个第一阶段,我选择建立一个包含以下字段的表:

MySQL Metadata Fields

Field

Purpose

Date/time (auto)

记录该条目时的时间戳

RCPT_TO address

存档消息的目标电子邮件地址

DATE header timestamp

原始电子邮件的发送时间

SUBJECT header

用于索引和搜索的主题行

FROM header

用于查找的发件人标识符

S3 directory

S3 存储桶中的目录路径

S3 filename

存储在 S3 中的唯一 .eml 文件

在 upload.php 应用程序文件中名为 MySQLLog 的函数经历了必要的步骤以打开 MySQL 的链接,插入新行,测试结果并关闭链接。为了更好的考虑,我确实添加了另外一步,那就是将这些数据记录到文本文件中。我是否需要进行更多的错误日志记录?是的。但我确实希望保持这段代码的简洁,以便让它运行得非常快。有时这段代码会每分钟调用数百次,需要尽可能高效。在未来的更新中,我将添加辅助代码来处理失败并将这些失败通过电子邮件发送给管理员进行监控。

我们在以前的步骤中获取了所有必要的数据,所以存储的步骤很简单。 在这个第一阶段,我选择建立一个包含以下字段的表:

MySQL Metadata Fields

Field

Purpose

Date/time (auto)

记录该条目时的时间戳

RCPT_TO address

存档消息的目标电子邮件地址

DATE header timestamp

原始电子邮件的发送时间

SUBJECT header

用于索引和搜索的主题行

FROM header

用于查找的发件人标识符

S3 directory

S3 存储桶中的目录路径

S3 filename

存储在 S3 中的唯一 .eml 文件

在 upload.php 应用程序文件中名为 MySQLLog 的函数经历了必要的步骤以打开 MySQL 的链接,插入新行,测试结果并关闭链接。为了更好的考虑,我确实添加了另外一步,那就是将这些数据记录到文本文件中。我是否需要进行更多的错误日志记录?是的。但我确实希望保持这段代码的简洁,以便让它运行得非常快。有时这段代码会每分钟调用数百次,需要尽可能高效。在未来的更新中,我将添加辅助代码来处理失败并将这些失败通过电子邮件发送给管理员进行监控。

我们在以前的步骤中获取了所有必要的数据,所以存储的步骤很简单。 在这个第一阶段,我选择建立一个包含以下字段的表:

MySQL Metadata Fields

Field

Purpose

Date/time (auto)

记录该条目时的时间戳

RCPT_TO address

存档消息的目标电子邮件地址

DATE header timestamp

原始电子邮件的发送时间

SUBJECT header

用于索引和搜索的主题行

FROM header

用于查找的发件人标识符

S3 directory

S3 存储桶中的目录路径

S3 filename

存储在 S3 中的唯一 .eml 文件

在 upload.php 应用程序文件中名为 MySQLLog 的函数经历了必要的步骤以打开 MySQL 的链接,插入新行,测试结果并关闭链接。为了更好的考虑,我确实添加了另外一步,那就是将这些数据记录到文本文件中。我是否需要进行更多的错误日志记录?是的。但我确实希望保持这段代码的简洁,以便让它运行得非常快。有时这段代码会每分钟调用数百次,需要尽可能高效。在未来的更新中,我将添加辅助代码来处理失败并将这些失败通过电子邮件发送给管理员进行监控。

总结

在几个相当简单的步骤中,我们能够通过建立一个强大的电子邮件归档系统的第一阶段,将电子邮件副本存储在S3中,并在MySQL表中进行数据交叉引用。这将为项目的其余部分奠定基础,未来几个帖子中将继续讨论。

在此项目的未来修订中,我预计会:

  1. 存储所有原始电子邮件的日志事件

  2. 在上传或记录失败时,将存储错误发送给管理员

  3. 简化收集器的复杂性。

  4. 添加一个用于查看所有数据的UI

  5. 支持重新发送电子邮件的功能

与此同时,我希望这个项目对您有趣且有帮助;祝发送愉快。

在几个相当简单的步骤中,我们能够通过建立一个强大的电子邮件归档系统的第一阶段,将电子邮件副本存储在S3中,并在MySQL表中进行数据交叉引用。这将为项目的其余部分奠定基础,未来几个帖子中将继续讨论。

在此项目的未来修订中,我预计会:

  1. 存储所有原始电子邮件的日志事件

  2. 在上传或记录失败时,将存储错误发送给管理员

  3. 简化收集器的复杂性。

  4. 添加一个用于查看所有数据的UI

  5. 支持重新发送电子邮件的功能

与此同时,我希望这个项目对您有趣且有帮助;祝发送愉快。

在几个相当简单的步骤中,我们能够通过建立一个强大的电子邮件归档系统的第一阶段,将电子邮件副本存储在S3中,并在MySQL表中进行数据交叉引用。这将为项目的其余部分奠定基础,未来几个帖子中将继续讨论。

在此项目的未来修订中,我预计会:

  1. 存储所有原始电子邮件的日志事件

  2. 在上传或记录失败时,将存储错误发送给管理员

  3. 简化收集器的复杂性。

  4. 添加一个用于查看所有数据的UI

  5. 支持重新发送电子邮件的功能

与此同时,我希望这个项目对您有趣且有帮助;祝发送愉快。

其他新闻

阅读更多来自此类别的内容

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

完整的AI原生平台,可与您的业务一起扩展。

© 2025 Bird

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

完整的AI原生平台,可与您的业务一起扩展。

© 2025 Bird