全面解读邮件协议

系统性解读邮件相关协议的内容却很少。前段时间进行了邮件系统的开发,这个过程不可避免的设计到SMTP、IMAP、MIME等种种协议,因此在这里做个系统性的、简单全面的总结,希望对邮件开发感兴趣或者需要因工作需要接触这块知识的朋友提供一些帮助。

1. 区分邮件的网络协议与文本协议

相信绝大部分同学对邮件协议的记忆都是停留在计算机网络教材角落中的SMTP、POP3和IMAP 等协议。正如教材的名字,这些都是网络应用层协议,负责邮件在网络中的传输。

除此之外,对于邮件开发而言,还有一部分同样重要的协议,邮件文本协议 ,这些协议约束了邮件的文本格式,基于这些约定俗称的格式,我们发送的邮件才能在世界上各种邮件商之间相互流转的过程中,准确稳定的以人类可读的形式展示给我们,比如 RFC 5322(Internet Message Format )协议规定了邮件结构、头字段、邮件主题等,MIME协议允许邮件携带附件、多媒体内容等非ASCII字符的文件内容。

2. 邮件网络协议

2.1. SMTP协议

2.1.1. SMTP协议介绍

SMTP(Simple Mail Transfer Protocal) 是最广为人知的邮件网络协议,SMTP是一个"推"协议,用于将邮件从发送方传输到接收方的邮件服务器。

和HTTP协议一样,SMTP协议也是TCP/IP协议簇的一员,因此,在进入正式的SMTP交互前,需要进行TCP三次握手完成TCP通道的建联。但是,对比HTTP与SMTP,我们还是能发现明显的不同:

协议 Server-Client交互规则 链接持久性 状态 是否双工
HTTP Client主动向Server请求数据 一般为短链接 无状态 一般为单向
SMTP Client主动向Server推送数据 长链接 有会话状态 双工通道

SMTP的内核是在一次长链接中,从client向server推送数据,具体的交互链路参考如下:

2.1.2. SMTP Client的常用命令

  • HELO @server.com <CRLF>:请求与service建立SMTP通道
  • RCPT TO [email protected] <CRLF>: 邮件接收者的地址
  • DATA <CRLF>: 邮件数据,以 结尾
  • QUIT:结束SMTP

2.1.3. SMTP service的常用响应码

  • 220 [email protected]:标识服务器准备就绪
  • 250: 操作成功
  • 221:结束SMTP
  • 354:开始邮件内容输入
  • 421:服务器未就绪,关闭通道
  • 452:系统存储不足
  • 450:邮箱不可用
  • 451 - 请求的动作未完成,错误引起暂时失败
  • 452 - 请求的动作未完成,系统存储分配尽
  • 500 - 语法错误,命令无法识别
  • 501 - 语法错误,参数不正确
  • 502 - 命令不可实现
  • 503 - 命令序列错误
  • 504 - 命令参数不支持
  • 550 - 用户不存在
  • 551 - 用户非本地,请尝试
  • 552 - 询问超出存储分配
  • 553 - 请求操作未完成,邮箱名不合法
  • 554 - 操作未执行,邮箱名不存在

2.2. POP3与IMAP协议

SMTP协议就已经可以完成client与server之间的邮件通信了,那么为什么还需要POP3与IMAP协议呢?

SMTP协议要求client与server两者均在线。而在现代的邮件系统中,保持服务实时在线是不现实的,因此,邮件代理服务器就很有必要了。我们常用的gmali、qq、163等都是邮件代理服务器。而POP3与IMAP协议承载的就是用户从邮件服务器拉取邮件的职责。

2.2.1. POP3协议

协议支持离线邮件处理,当邮件发送到服务器后,电子邮件客户端会调用邮件客户端程序,下载所有未阅读的电子邮件(这种离线访问模式是一种存储转发服务)。当邮件从邮件服务器发送到个人计算机上,同时邮件服务器会删除该邮件。

POP3客户端通常采用"off-line"离线方式访问邮件服务器,会定时的访问邮件服务器,下载邮件到客户的电脑上,然后和服务器断开。

  • POP3协议的常用指令
    • 连接和身份验证: 邮件客户端通过TCP/IP连接到邮件服务器的POP3端口(通常是110)。客户端首先发送USER命令提供用户名,然后发送PASS命令提供密码以进行身份验证。
    • 查看邮件状态: 客户端可以使用STAT命令来查看邮箱中的邮件状态,包括邮件数量和总字节数。
    • 查看邮件列表: 使用LIST命令,客户端可以查看每封邮件的索引号和大小。如果提供邮件索引号,服务器将返回特定邮件的大小。
    • 检索邮件: 使用RETR命令,客户端可以检索特定邮件的内容,包括邮件头和正文。邮件被下载到客户端设备上。
    • 删除邮件: 使用DELE命令,客户端可以标记要删除的邮件。邮件在标记为删除后不会立即从服务器上删除,直到客户端执行QUIT命令。
    • 退出: 使用QUIT命令,客户端结束POP3会话并断开连接。在断开连接之前,标记为删除的邮件将从服务器上删除。

2.2.2. IMAP协议

IMAP(Internet Message Access Protocol)是一种用于接收邮件的标准协议。除POP3的基础能力外,IMAP允许用户通过邮件客户端从邮件服务器上获取邮件,管理邮件夹,以及搜索和筛选邮件。

AP协议的主要特点包括:

  1. 服务器存储:IMAP允许用户将邮件存储在邮件服务器上,而不是像POP3协议那样将邮件下载到本地设备,这样用户可以从任何设备上访问并同步邮件。
  2. 多邮件夹支持:IMAP支持用户在邮件服务器上创建多个邮件夹,方便用户管理和组织邮件。
  3. 邮件状态同步:IMAP能够同步邮件的读取状态、标记状态等,这意味着用户在不同设备上查看邮件时,邮件的状态会得到同步。
  4. 搜索和筛选:IMAP支持在邮件服务器上进行搜索和筛选,用户可以通过关键词、发件人、收件人等进行邮件搜索。

当我们在多个设备上访问同一个邮箱时,IMAP能同步所有设备对邮箱邮件的操作:

可以看到,在使用上,IMAP协议和我们用HTTP维护的一般Web服务就没啥大的区别了,数据存储在服务端,用户在客户端通过指令或者接口操作服务端的数据。

  • IMAP协议的常用命令
    • NOOP:检查新邮件,防止超时。
    • LOGIN:用户登录。
    • LIST:邮箱名称列表。
    • APPEND:将邮件添加到指定邮箱。
    • SELECT:选择一个邮箱,并进入该邮箱的工作状态。
    • FETCH:从当前选定的邮箱中获取邮件的内容。
    • STORE:修改邮件的标志或属性。
    • SEARCH:搜索符合特定条件的邮件。
    • EXPUNGE:永久删除标记为删除的邮件。

3. 邮件文本协议

现在绝大部分系统的邮件系统都是基于邮件服务商提供的服务来做的,因此,其实上一章节提到的各种网络协议以及交互服务商都替我们做掉了。和我们打交道最多的,其实更有可能是邮件的文本协议。可以说,邮件文本协议才是我们视觉效果上一封邮件的原本样貌。

3.1. IMF协议

邮件 IMF(Internet Message Format)协议是电子邮件系统中用于定义电子邮件消息格式的标准规范。是电子邮件最重要、最基础的文本协议。

IMF协议规定电子邮件必须包括邮件头(Header)与邮件体(Body)两部分,以及各部分的标准格式,以确保电子邮件可以在各个邮件服务器中都能完美兼容。

3.1.1. 邮件头

为了方便理解,这里把邮件协议头的字段分为两大类(非官方分类),一类是有展示语义的,例如发件人,收件人,抄送人等等,另一类是无具体展示语义的,例如mMeaasge-ID等等。

  1. 有展示的邮件头字段

这部分字段是最容易理解的,当我们在邮箱中收到一封邮件,邮件本身就会展示很多邮件正文内容以外的信息(发件人、抄送人等等),这些信息都是储存在邮件协议头中,经由邮件服务应用展示给我们的。

  • 发件人(From):指明发送邮件的邮箱地址和姓名。
  • 收件人(To):指明接收邮件的邮箱地址和姓名。
  • 抄送(Cc):指明同时抄送邮件的邮箱地址和姓名。
  • 密送(Bcc):指明同时密送邮件的邮箱地址和姓名,收件人无法看到这部分信息。
  • 主题(Subject):指明邮件的主题或标题。
  • 日期(Date):指明邮件的发送日期和时间。
  • 回复地址(Reply-To):指明回复邮件的邮箱地址,如果与发件人不同的话。
  1. 无具体展示语义的邮件头字段

这部分字段并不会直接在UI上展示给我们,但是在我们做邮件系统开发时,往往会利用这其中的很多字段进行功能设计与邮件定位。

  • 消息ID(Message-ID):唯一标识邮件消息的ID。
  • In-Reply-To:邮件回复的邮件的消息ID。
  • 内容类型(Content-Type):指明邮件内容的格式和编码方式,如文本、HTML、附件等。
  • 报告回执(Return-Receipt-To):指明发送邮件后是否要求接收回执。
  • 附件(Attachments):指明邮件中是否包含附件。
  • 传输路径(Received):邮件传输的路线,可用于分析邮件的来源。

这其中最关键的就是Message-ID,他是电子邮件在互联网中的唯一ID,用于标识邮件身份。Message-ID的格式为<uniqe_id@domain>例如<[email protected]>。Message-ID的格式为中的uniqe_id由邮件发送方自己保证唯一,domain是邮件服务的域名,通过这种形式就保证了每个邮件的Message-ID都是唯一的。

那么这有什么作用呢?比如说我们需要具体统计某个邮件的回复或者转发链路,或者需要针对具体的回复邮件做功能处理,我们就可以定制自己的Message-ID,也可以在其中埋入轻量级的信息,有用户回复我们发出的邮件时,就会在In-Reply-To中带上我们自己的Message-ID,之后我们就可以根据Message-ID及其关联的信息,设计我们自己的业务逻辑了。

3.1.2. 邮件体

在IMAP的上下文中,邮件的BODY是指邮件的主体内容,通常包括文本、图像、附件等。BODY部分可以根据MIME(Multipurpose Internet Mail Extensions)协议进行分解,支持多媒体和多部分邮件。

具体的邮件体格式就是下面的MIME协议

3.2. MIME协议

MIME(Multipurpose Internet Mail Extensions,多用途互联网邮件扩展)是一种互联网标准,用于扩展电子邮件的功能,使其能够支持多种类型的内容。MIME协议最初是为了在电子邮件中发送非ASCII字符以及各种媒体类型而设计的。邮件的原始协议只能传输ASCII文本,因此需要MIME来提供更多类型的支持。

  1. MIME的主要特性
  • 多媒体支持: MIME允许电子邮件包含文本、图像、音频、视频等多种形式的内容。
  • 字符编码: 支持多种字符集与编码方法,比如UTF-8,允许发送各种语言的文字。
  • 复杂邮件结构: 支持多部分邮件,可以将邮件分成多个部分,每个部分可以有不同的内容类型和编码。
  1. MIME协议格式

以下是一个MIME协议的示例:

bash 复制代码
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="boundary_string"

--boundary_string
Content-Type: text/plain; charset="UTF-8"
Content-Transfer-Encoding: 7bit

这是邮件的文本部分。

--boundary_string
Content-Type: image/jpeg
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="example.jpg"

(此处是经过base64编码的JPEG图像数据)

--boundary_string--

MIME协议是由分割线分割而成的块状的数据结构,每个数据块中以数据块的属性字段开头,之后跟随具体的数据内容,其中,具体的数据内容也可以是MIME数据块的形式:

  1. MIME协议中的字段:
  • Content-Type:指定邮件消息的内容类型,如文本、图像、音频、视频等。
  • Content-Disposition:指定邮件消息中附件的处理方式,如内联显示或作为附件保存。
  • Content-Transfer-Encoding:指定邮件消息内容的编码方式,如base64、quoted-printable等。
  • Content-ID:指定邮件消息中某个部分的唯一标识符,通常用于引用内联内容或附件。
  • Content-Description:对邮件消息内容的简短描述。

MIME不仅用于电子邮件,还广泛应用于HTTP(如在网络上传输多媒体文件)以及其他协议中,使其能够处理复杂的内容类型。MIME协议极大地扩展了电子邮件的功能,使用户能够在邮件中发送丰富的内容形式,提升了通信的灵活性和多样性。

3.3. HTML

MIME协议可以支持多种类型的内容。但是我们现在的很多业务邮件,往往都有着自己的排版以及格式设计,这写格式与排版,都是基于HTML实现的。

MIME的Content-Type可以指定为HTML,通过这种形式支持HTML格式的数据渲染。

具体的HTML协议网络上的资料和教程都很多,这里就不做展开了。HTML很好的支持了复杂形势的邮件模板,因此,各个业务系统发出的邮件,绝大部分都是HTML的形势。但是,复杂的格式就给邮件内容的解析带来了很大的挑战。这里举一个例子:

邮件服务商在用户回复邮件时都会自动填充一些默认信息与用户手动输入的信息一道发送给收件人,这些默认信息与用户输入信息往往就是通过HTML的形势编排在一起的。

如果我们的业务系统需要解析回复邮件的内容,那么面对的问题就是如何在复杂的HTML结构中甄别出哪些是用户真正自己输入的内容。由于这部分内容并没有官方协议进行统一,各邮件服务商都是各自定义自己的HTML格式,这就会给我们带来很大的挑战。

我们想到的做法是根据各服务商邮件模板中默认填充信息的元素名称,将对应的HTML元素排除掉,以下是我们统计的各邮件服务商的默认信息HTML元素名:

css 复制代码
[  "div.businessCard_name",  "div#divRplyFwdMsg",  "div.yahoo_quoted",  "div[class~=(yahoo_quoted|quoted|quote)]",  "div.xm_mail_oringinal_describe",  "div.quote",  "div.hidden",  "div.gmail_quote_container",  "div.gmail_quote",  "div.yahoo_quoted",  "div.Apple-Mail-quoted-text",  "div.mcnImageGroupBlock",  "div.email_quote",  "div.ntes-mailmaster-quote",  "div.accio_mail_notice_container",  "blockquote",]

当然,这种方法并不通用,因为我们没办法枚举世界上所有的邮件服务商,另外,只要邮件供应商在自己的服务器修改了发送出的HTML元素名称,这种方式就过期了。不过,由于MIME中的HTML并没有通用的协议约定,目前,也没有想到有什么更好的办法解决这个问题。

相关推荐
天天摸鱼的java工程师8 分钟前
高考放榜夜,系统别崩!聊聊查分系统怎么设计,三张表足以?
java·后端·mysql
天天摸鱼的java工程师16 分钟前
深入理解 Spring 核心:IOC 与 AOP 的原理与实践
java·后端
编程轨迹29 分钟前
使用 Playwright Java 框架创建自定义的请求和响应日志记录器
后端
Moonbit1 小时前
征文开启|写一篇能跑的文档,赢 MoonBit 周边 & 成为官方示例
后端
我爱Jack1 小时前
Spring Boot统一功能处理深度解析
java·spring boot·后端
惜鸟1 小时前
# LLM统一网关:LiteLLM 详细介绍(实践篇)
后端·openai
RainbowJie12 小时前
Spring Boot 使用 SLF4J 实现控制台输出与分类日志文件管理
spring boot·后端·单元测试
suke2 小时前
MinIO社区版"挥刀自宫":Web管理功能全砍,社区信任岌岌可危
后端·程序员·开源
美团技术团队2 小时前
可信实验白皮书系列04:随机轮转实验
后端
想用offer打牌2 小时前
面试回答喜欢用构造器注入,面试官很满意😎...
后端·spring·面试