
随着电子邮件在监管环境中的使用增加,我决定是时候开始一个新项目,它将所有这些结合在一起,并提供有关如何存储电子邮件正文及其所有相关数据的代码示例。
大约一年前,我写了一篇博客,介绍如何检索电子邮件的副本以进行存档和查看,但我没有涉及电子邮件或相关数据的实际存储。最近,我写了一篇博客,关于存储电子邮件的所有事件数据(例如,电子邮件发送时间、打开、点击、退回、退订等)用于审计,但选择不创建任何支持代码。
随着电子邮件使用量在监管环境中的增加,我决定是时候启动一个新项目,将所有这些结合在一起,用代码示例展示如何存储电子邮件正文及其所有相关数据。在接下来的一年中,我将继续构建这个项目,目标是创建一个有效的存储和查看应用程序,用于存档的电子邮件和由SparkPost生成的所有日志信息。SparkPost 没有存档电子邮件正文的系统,但它使构建存档平台相当容易。
在本博客系列中,我将描述将电子邮件正文存储到 S3(亚马逊简单存储服务)以及将所有相关日志数据存储在 MySQL 中以便于交叉引用的过程。 对于需要强大数据库备份策略的生产存档系统,考虑实现全面的PostgreSQL 备份和恢复流程,以确保您的存档数据得到妥善保护。最终,这是构建一个应用程序的起点,该应用程序将允许对存档邮件进行轻松搜索,然后显示这些邮件以及事件(日志)数据。该项目的代码可以在以下 GitHub 仓库中找到:https://github.com/jeff-goldstein/PHPArchivePlatform
本博客系列的首篇文章将描述挑战并为解决方案架构。其余的博客将详细说明解决方案的各部分以及代码示例。
我流程中的第一步是找出如何获得发送给原始收件人的电子邮件副本。要获得电子邮件正文的副本,您需要:
在发送电子邮件之前捕获电子邮件正文
让邮件服务器存储一个副本
让邮件服务器为您创建一个副本以供存储
如果邮件服务器添加了诸如链接跟踪或打开跟踪等项目,您不能使用方案 #1,因为它不会反映打开/点击跟踪更改。
这意味着服务器必须存储电子邮件,或者以某种方式提供电子邮件的副本供您存储。由于 SparkPost 没有存储电子邮件正文的机制,但它有一种创建电子邮件副本的方法,我们将让 SparkPost 发送给我们一封重复的电子邮件,以便我们将其存储在 S3 中。
这是通过使用 SparkPost 的存档功能完成的。SparkPost 的存档功能使发送者能够告诉 SparkPost 将电子邮件的副本发送给一个或多个邮件地址,并使用与原始邮件相同的跟踪和打开链接。SparkPost 文档如下定义它们的存档功能:
存档列表中的收件人将收到发送给 RCPT TO 地址的邮件的精确副本。特别是,任何针对 RCPT TO 收件人的编码链接在存档邮件中都将相同
与 RCPT TO 邮件的唯一不同之处在于,由于存档邮件的目标地址不同,一些标头将会有所不同,但邮件的正文将是精确的副本!
如果您想要更深入的解释,这里有一个链接到关于创建电子邮件副本(或存档)副本的 SparkPost 文档。
顺便说一句,SparkPost 实际上允许您将邮件发送到 cc、bcc 和存档邮件地址。在这个解决方案中,我们专注于存档地址。
* 注意 * 存档邮件只能在通过 SMTP 注入邮件到 SparkPost 时创建!
现在我们知道如何获得原始邮件的副本,我们需要查看生成的日志数据及其中的一些微妙差异。SparkPost 追踪其服务器上的所有活动,并以消息事件的形式提供这些信息。那些事件在 SparkPost 上存储 10 天,可以通过名为 message-events 的 RESTful API 从服务器检索,或者您可以让 SparkPost 将这些事件推送到您希望的任何收集应用程序。推送机制通过 Webhook 完成,并且是实时的。
目前,可能发生 14 种不同的事件。 以下是当前事件的列表:
退回
点击延迟
投递
生成失败
生成拒绝
初次打开
注入链接退订
列表退订
打开
带外
策略拒绝垃圾邮件投诉
* 请点击此链接获得每个事件的描述及每个事件共享的数据的最新参考指南。
每个事件都有与事件类型匹配的众多字段。 某些字段,如 transmission_id,存在于每个事件中,但其他字段可能更特定于事件;例如,只有打开和点击事件才有地理标记信息。
对于这个项目,一个非常重要的消息事件条目是 transmission_id。原始邮件、存档邮件以及任何 cc 和 bcc 地址的所有消息事件条目将共享相同的 transmission_id。
还有一个称为 message_id 的通用条目,它在原始邮件和存档邮件的每个条目中具有相同的 ID。任何 cc 或 bcc地址在 message_id 条目中将有自己的 ID。
到目前为止,这听起来很棒,而且相当简单,但现在是棘手的部分。记住,为了获得存档邮件,我们将让 SparkPost 将原始邮件的副本发送到另一个您有权访问的收件箱。但为了自动化这个解决方案并存储邮件正文,我将使用 SparkPost 的另一个功能,称为入站邮件中继。它的作用是,将所有发送到特定域的邮件进行处理。处理时,它会解构邮件并创建一个 JSON 结构,然后通过 webhook 传递给一个应用程序。请参见附录 A 了解示例 JSON。
如果您仔细观察,您会注意到入站中继的 JSON 结构缺少一个非常重要的字段;transmission_id。虽然所有的出站邮件都具有 transmission_id,与原始邮件、存档、cc 和 bcc 地址的所有数据的相同条目绑定在一起;SparkPost 无法知道入站过程捕获的邮件与任何出站邮件有关联。入站过程简单地知道发送到特定域的邮件,并解析该邮件而已。它将以相同的方式处理发送到该域的任何邮件,无论是客户回复还是 SparkPost 发送的存档邮件。
所以诀窍是:你如何将出站数据和刚刚抓取存档版本的入站过程粘合在一起?我决定在邮件正文中隐藏一个唯一的 ID。如何做到这一点取决于您,但我只是创建了一个打开隐藏标签的输入字段。
我还将该字段添加到 X-MSYS-API 头的元数据块中,该块在注入时传递给 SparkPost。这个隐藏的 UID 将成为整个过程的粘合剂,并且是该项目的主要组成部分,将在以下博客文章中详细讨论。
现在我们有了将这个项目结合在一起的 UID,并了解其必要性,我可以开始构建总体项目的愿景以及相应的博客文章。
捕获和存储存档邮件以及一个用于搜索/索引的数据库条目
捕获所有消息事件数据
创建一个应用程序以查看邮件及所有相关数据
这是项目的一个简单图示:

第一批代码将涵盖存档过程并将电子邮件存储到 S3 中,而第二批代码将涵盖将所有日志数据从消息事件存储到 MySQL中。预计第一批代码和博客条目将在 2019 年初发布。 如有任何问题或建议,请随时告知。
发送愉快。
– Jeff
附录 A:
