
随着电子邮件在监管环境中的使用增加,我决定是时候开始一个新项目,它将所有这些结合在一起,并提供有关如何存储电子邮件正文及其所有相关数据的代码示例。
大约一年前,我写了一篇博客,讨论如何检索电子邮件的副本以供存档和查看,但我没有涉及实际存储电子邮件或相关数据的问题。最近,我写了一篇博客,讨论如何存储电子邮件的所有事件数据(即何时发送、打开、点击、退回、退订等)以便进行审计,但选择不创建任何支持代码。
随着电子邮件在监管环境中使用的增加,我决定是时候开始一个新项目,将这些数据和如何存储电子邮件正文及其所有相关数据的代码示例结合在一起。在接下来的一年中,我将继续构建这个项目,目标是创建一个可工作的存储和查看应用程序,用于存档的电子邮件及由SparkPost生成的所有日志信息。 SparkPost 没有存档电子邮件正文的系统,但它确实使构建存档平台相当容易。
在这个博客系列中,我将描述我为了将电子邮件正文存储到 S3(亚马逊的简单存储服务)以及将所有相关日志数据存储到 MySQL 中以便于交叉引用的过程。对于需要强大数据库备份策略的生产存档系统,请考虑实施全面的PostgreSQL 备份和恢复过程以确保您的存档数据得到适当保护。最终,这是构建一个允许轻松搜索存档电子邮件的应用程序的起点,然后显示这些电子邮件及其事件(日志)数据。该项目的代码可以在以下 GitHub 存储库中找到:PHPArchivePlatform on GitHub
博客系列的这一第一篇将描述挑战,并为解决方案制定一个体系结构。其余博客将详细介绍解决方案的各个部分以及代码示例。
我的过程的第一步是弄清楚我将如何获得发送给原始收件人的电子邮件副本。为了获取电子邮件正文的副本,您需要:
在发送电子邮件之前捕获其正文
让电子邮件服务器存储副本
让电子邮件服务器为您创建一个副本以进行存储
如果电子邮件服务器正在添加链接跟踪或打开跟踪等项目,您不能使用第 #1 方法,因为它不会反映打开/点击跟踪的更改。
这意味着服务器必须存储邮件或以某种方式向您提供其副本以进行存储。由于 SparkPost 没有用于存储电子邮件正文的机制,但确实有生成电子邮件副本的方法,我们将让 SparkPost 发送给我们一封电子邮件的副本以存储在 S3 中。
这是通过使用 SparkPost 的存档功能完成的。 SparkPost 的存档功能允许发送者告诉 SparkPost 将电子邮件的副本发送到一个或多个电子邮件地址,并使用与原始邮件相同的跟踪和打开链接。 SparkPost 文档这样定义他们的存档功能:
存档列表中的收件人将收到发送至 RCPT TO 地址的邮件的精确副本。特别是,任何编码的链接都将与存档邮件中的 RCPT TO 收件人完全相同
与 RCPT TO 邮件相比,唯一的区别在于某些标题会有所不同,因为用于归档邮件的目标地址不同,但邮件正文则完全相同!
如果您想要更深入的解释,这里是SparkPost 文档链接,关于创建电子邮件副本(或存档)的说明。
顺便说一句,SparkPost 实际上允许您将邮件发送至 cc、bcc 和存档的电子邮件地址。对于这个解决方案,我们专注于存档地址。
* 注意 * 只有通过 SMTP 将邮件注入 SparkPost 时才能创建存档邮件!
现在我们知道如何获取原始电子邮件的副本,接下来需要查看生成的日志数据及其内在的一些微妙区别。 SparkPost 跟踪其服务器上发生的所有事情,并以消息事件的形式向您提供该信息。这些事件在 SparkPost 上存储 10 天,可以通过名为消息事件的 RESTful API 从服务器提取,或者您可以让 SparkPost 将这些事件推送到您希望的多个收集应用程序。推送机制通过 webhook 以实时的方式完成。
目前,电子邮件可能发生 14 种不同的事件。以下是当前事件的列表:
Bounce
ClickDelay
Delivery
Generation Failure
Generation Rejection
Initial Open
InjectionLink Unsubscribe
List Unsubscribe
Open
Out of Band
Policy RejectionSpam Complaint
* 请访问此链接,获取最新的每个事件描述的参考指南及其共享的数据。
每个事件都有多个字段与事件类型相匹配。诸如 transmission_id 的字段在每个事件中都存在,但其他字段可能更具事件特性;例如,只有打开和点击事件具有地理标记信息。
对于这个项目有一个非常重要的消息事件条目是 transmission_id。原始邮件、存档邮件以及任何cc和bcc地址的所有消息事件条目将共享相同的 transmission_id。
还有一个通用条目称为 message_id,它对于原始电子邮件和存档邮件的每个条目将具有相同的 id。任何 cc 或 bcc 地址都会有各自的 message_id 条目的 id。
到目前为止,这一切听起来不错,坦率地说也相对简单,但现在是具有挑战性的部分。请记住,为了获取存档电子邮件,我们让 SparkPost 将原始邮件的副本发送到另一个您可以访问的收件箱。但是,为了自动化这个解决方案并存储电子邮件正文,我将使用 SparkPost 的另一个功能,称为Inbound Email Relaying。这样做的目的是接收发送到特定域的所有邮件并处理它们。通过处理,它将邮件拆分,并创建一个 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:
