iOS和macOS上的Message-ID和Mail.app深度链接
如何在iOS上通过电子邮件进行无缝的“无密码”身份验证。
Apple平台上的邮件和日历集成
在macOS和iOS上查看电子邮件时,邮件会在[检测到的日期和时间]下划线 。您可以与他们互动以创建新的日历事件。如果您在“日历”中打开此类活动,则会在其扩展详细信息中看到“在邮件中显示”链接。单击此链接可将您带回到原始电子邮件。此功能可以追溯到iPhone的发布。它将被纳入当年的 [Mac OS X版本(Leopard)中,] 这将标志着许多移动功能中的第一个进入台式机。
如果要将“魔术” URL复制到粘贴板并在文本编辑器中查看,则会看到以下内容:
- "message:%3C1572873882024.NSHIPSTER%40mail.example.com%3E"
经验丰富的iOS开发人员将立即意识到使用 [自定义URL方案]。其中精通网络的用户可以对主机进行百分比解码,并识别出它类似于电子邮件地址,但事实并非如此。
因此,如果没有电子邮件地址,我们在这里看什么?
这是另一个不同的电子邮件字段,称为Message-ID。
另外,如果你想一起进阶,不妨添加一下交流群[1012951431],选择加入一起交流,一起学习。期待你的加入!(进群可领取学习礼包)
消息ID
[RFC 5322§3.6.4]规定,每封电子邮件应该 具有“消息ID:”字段包含单个唯一消息标识符。该标识符的语法本质上是一个带有尖括号(< >)的电子邮件地址。
尽管该规范未包含任何有关生成良好Message-ID的规范性指导,但 1998年的[IETF草案草稿]还是很不错的。
让我们看一下如何在Swift中执行此操作:
生成随机消息ID
前述文档中描述的第一种技术涉及生成带有64位随机数的随机消息ID,该消息ID带有时间戳,以进一步减少冲突的机会。我们可以使用Swift 5内置的随机数生成器API和[String(_:radix:uppercase:)初始化]程序来轻松完成此操作 :
- import Foundation
- let timestamp = String(Int(Date().timeIntervalSince1970 * 1000))
- let nonce = String(UInt64.random(in: 0..<UInt64.max), radix: 36, uppercase: true)
- let domain = "mail.example.com"
- let MessageID = "<\(timestamp).\(nonce)@\(domain)>"
- //"<1572873882024.NSHIPSTER@mail.example.com>"
然后,我们可以将生成的Message-ID与关联的记录一起保存,以便以后链接到它。但是,在许多情况下,一种更简单的选择是使消息ID具有确定性,并且可以从其现有状态进行计算。
生成确定性消息ID
考虑符合[Identifiable协议]且其关联`ID`类型为 [UUID]的记录结构 。您可以这样生成消息ID:
- import Foundation
- func messageID<Value>(for value: Value, domain: String) -> String
- where Value: Identifiable, Value.ID == UUID
- {
- return "<\(value.id.uuidString)@\(domain)>"
- }
如果缺少持久性标识符(或任何其他区别功能),则可以改用消息正文本身的摘要来生成消息ID。这是使用新的[CryptoKit框架]的示例实现 :
- import Foundation
- import CryptoKit
- let body = #"""
- Lorem ipsum dolor sit amet, consectetur adipiscing elit,
- sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
- Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris
- nisi ut aliquip ex ea commodo consequat.
- """#
- let digest = Data(SHA256.hash(data: body.data(using: .utf8)!))
- .map { String($0, radix: 16, uppercase: true) }
- .joined()
- let domain = "ADF"
- "<\(digest)@\(domain)>"
- // "<F52380112175FCE8ECF2731C193EB8A7CC8642E53C68D292CD88531D42F145@mail.example.com>"
移动深层链接
iOS和macOS上的常规Mail客户端都将尝试`message:`通过启动到前台并尝试使用编码的Message-ID字段打开消息来使用自定义方案打开URL 。
生成具有消息ID的邮件深层链接
掌握了Message-ID之后,最后的任务是创建一个深层链接,您可以使用该深层链接将Mail打开到关联的消息。唯一的技巧是 在URL中对消息ID 进行 [百分比编码]。您可以使用[方法]来执行此操作 ,但是我们更愿意将所有操作委托给-这具有进一步的优势,即无需使用[格式字符串]就可以完整构建URL 。[`adding PercentEncoding(withAllowedCharacters:)`][`URLComponents`]
- import Foundation
- var components = URLComponents()
- components.scheme = "message"
- components.host = MessageID
- components.string!
- // "message://%3C1572873882024.NSHIPSTER%40mail.example.com%3E"
据我们所知,自定义`message:`方案后是否存在双斜杠不会对邮件深层链接解析产生任何影响。
打开邮件深层链接
如果您`message:`在iOS上打开URL,并且可以从 其中一个帐户的inbox轻松访问链接的消息,则Mail将立即启动该消息。如果找不到该消息,则该应用程序将启动并在后台异步加载该消息,并在可用时将其打开。相比之下,尝试在macOS上打开到尚未加载的邮件的邮件深层链接会导致显示警报模式。因此,我们建议仅在iOS上使用邮件深层链接。例如, [飞行学校]使用无密码身份验证系统来执行此操作。要访问书籍的电子副本,请输入用于购买书籍的电子邮件地址。提交表单后,iOS上的用户将看到一个深层链接,用于打开指向包含“魔术登录链接”✨的电子邮件的Mail应用程序。
其他系统可能使用消息ID通过[通用链接]简化其本机应用或网站的无密码身份验证 ,或者将其合并为2fa策略的一部分 (因为[出于此目的,不再认为][sms是安全的])。如果您在Web应用程序中使用Rails,则 [ActiveMailer拦截器]提供了一种方便的方式来Message-ID为无密码验证流注入字段。
与苹果平台上如此众多的私人集成(仍然是第一方应用程序的专有领域)不同,“在邮件中显示”的秘密之处在于我们所有人都能参与进来。尽管没有记录,但是由于该功能与系统的深度集成以及植根于基本Web标准中,因此该功能不太可能很快被删除。
从[浏览器供应商], [社交媒体公司]到 [政府](甚至有时甚至是苹果[公司)的]每个人都 试图拆开开放的网络并控制我们可以看到和执行的操作时,很高兴得知电子邮件 [将近50年了],保持互联网自由和分散的能力仍然坚决。
翻译地址: https://nshipster.com/message-id/