
随着电子邮件在监管环境中的使用增加,我决定是时候开始一个新项目,它将所有这些结合在一起,并提供有关如何存储电子邮件正文及其所有相关数据的代码示例。
大约一年前,我写了一篇博客,介绍了如何检索电子邮件的副本以进行归档和查看,但没有涉及实际存储电子邮件或相关数据的内容。最近,我写了一篇博客,讲述了如何存储所有关于事件的数据(即发送时间、打开、点击、退回、退订等)以进行审计,但选择不创建任何支持代码。
随着电子邮件在监管环境中的使用增加,我决定是时候开始一个新项目,将这一切整合在一起,并提供如何存储电子邮件正文及所有相关数据的代码示例。在接下来的一年里,我将继续构建此项目,目标是创建一个有效的存储和查看应用程序,用于存档电子邮件和所有由SparkPost生成的日志信息。SparkPost并没有一个归档电子邮件正文的系统,但确实使得搭建一个归档平台相对容易。
在这个博客系列中,我将描述我为将电子邮件正文存储到S3(Amazon的Simple Store Service)以及所有相关日志数据存储在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天,可以通过称为消息事件的RESTful API从服务器拉取,或者您可以让SparkPost将这些事件推送到您希望的任何数量的收集应用中。这种推送机制是通过webhooks实时完成的。
目前,可能会发生在电子邮件上的事件有14种。以下是当前事件的列表:
退回(Bounce)
点击延迟(ClickDelay)
交付(Delivery)
生成失败(Generation Failure)
生成拒绝(Generation Rejection)
初次打开(Initial Open)
注入链接退订(InjectionLink Unsubscribe)
名单退订(List Unsubscribe)
打开(Open)
带外(Out of Band)
策略拒绝(Policy Rejection)垃圾邮件投诉(Spam 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。如何做到这一点由您决定,但我只是简单地创建了一个隐藏标签开启的输入字段。
我还将该字段添加到注入过程中传递给SparkPost的X-MSYS-API标头的元数据块中。这个隐藏的UID将成为整个过程的粘合剂,是项目的一个主要组件,并将在接下来的博客文章中深入讨论。
现在我们有了将这个项目粘合在一起并理解其必要性的UID,我可以开始构建整个项目的愿景和相应的博客文章。
捕获并存储归档电子邮件及搜索/索引的数据库条目
捕获所有消息事件数据
创建一个应用程序以查看电子邮件及所有对应的数据
以下是该项目的简单图示:

第一批代码将涵盖归档过程并将电子邮件存储到S3中,而第二批代码将涵盖从消息事件中存储所有日志数据到MySQL中。您可以期待在2019年初某个时间看到前两批代码和博客条目。如果您有任何问题或建议,请随时传递给我们。
发送愉快。
– Jeff
附录A:
