在Golang的生态中,net/mail标准库是处理邮件解析的核心工具,它支持解析符合RFC 5322标准的邮件数据,能够帮助我们快速从原始邮件内容中提取头信息和正文内容,无需依赖第三方库即可完成基础邮件处理工作。

net/mail库基础使用流程
使用net/mail解析邮件的核心步骤是先读取原始邮件数据,再通过库提供的解析方法生成邮件对象,最后从邮件对象中提取所需信息。原始邮件数据可以来自本地文件、网络请求返回的字节流等场景。
基础解析示例
以下是一个从本地eml文件解析邮件的基础示例:
package main
import (
"fmt"
"io/ioutil"
"net/mail"
"strings"
)
func main() {
// 读取本地邮件文件内容
data, err := ioutil.ReadFile("test.eml")
if err != nil {
fmt.Println("读取文件失败:", err)
return
}
// 解析邮件内容
msg, err := mail.ReadMessage(strings.NewReader(string(data)))
if err != nil {
fmt.Println("解析邮件失败:", err)
return
}
fmt.Println("邮件解析成功")
}
获取邮件头信息
邮件头信息存储在mail.Message的Header字段中,Header是一个map结构,键是头字段名称,值是字符串切片,部分头字段可能存在多个值。
常见头字段提取
以下是提取发件人、收件人、主题、发送时间等常见头信息的示例:
package main
import (
"fmt"
"io/ioutil"
"net/mail"
"strings"
"time"
)
func main() {
data, _ := ioutil.ReadFile("test.eml")
msg, _ := mail.ReadMessage(strings.NewReader(string(data)))
header := msg.Header
// 获取发件人 返回*mail.Address类型
from, err := header.AddressList("From")
if err == nil && len(from) > 0 {
fmt.Println("发件人:", from[0].Name, from[0].Address)
}
// 获取收件人 支持To、Cc、Bcc字段
to, err := header.AddressList("To")
if err == nil {
for _, addr := range to {
fmt.Println("收件人:", addr.Name, addr.Address)
}
}
// 获取主题 直接取第一个值即可
subject := header.Get("Subject")
fmt.Println("主题:", subject)
// 获取发送时间 需要解析时间字符串
dateStr := header.Get("Date")
date, err := time.Parse(time.RFC1123Z, dateStr)
if err == nil {
fmt.Println("发送时间:", date.Format("2006-01-02 15:04:05"))
}
}
头信息编码处理
部分邮件头信息会采用RFC 2047编码,比如中文主题会被编码为类似=?UTF-8?B?xxxx==?=的格式,net/mail的Header.Get方法会自动解码这类编码,直接返回可读的字符串,无需额外处理。
获取邮件正文内容
邮件正文存储在mail.Message的Body字段中,是一个io.Reader类型,直接读取即可获取正文内容,但需要注意单部分和多部分邮件的处理差异。
单部分邮件正文提取
如果邮件是单部分结构,没有附件和多段内容,直接读取Body即可:
package main
import (
"fmt"
"io/ioutil"
"io"
"net/mail"
"strings"
)
func main() {
data, _ := ioutil.ReadFile("test.eml")
msg, _ := mail.ReadMessage(strings.NewReader(string(data)))
// 读取正文内容
body, err := ioutil.ReadAll(msg.Body)
if err != nil && err != io.EOF {
fmt.Println("读取正文失败:", err)
return
}
fmt.Println("邮件正文:", string(body))
}
多部分邮件正文提取
如果邮件包含附件、富文本和纯文本多个部分,Body读取的是整个多部分内容,需要借助mime/multipart库拆分各个部分,提取需要的正文:
package main
import (
"fmt"
"io/ioutil"
"io"
"mime"
"mime/multipart"
"net/mail"
"strings"
)
func main() {
data, _ := ioutil.ReadFile("test.eml")
msg, _ := mail.ReadMessage(strings.NewReader(string(data)))
header := msg.Header
// 获取Content-Type头 解析出媒体类型和参数
contentType := header.Get("Content-Type")
mediaType, params, err := mime.ParseMediaType(contentType)
if err != nil {
fmt.Println("解析Content-Type失败:", err)
return
}
// 如果是多部分类型 拆分各个部分
if strings.HasPrefix(mediaType, "multipart/") {
// 获取边界值
boundary, ok := params["boundary"]
if !ok {
fmt.Println("未找到multipart边界")
return
}
// 创建multipart读取器
mr := multipart.NewReader(msg.Body, boundary)
for {
part, err := mr.NextPart()
if err == io.EOF {
break
}
if err != nil {
fmt.Println("读取部分失败:", err)
return
}
// 获取当前部分的Content-Type
partContentType := part.Header.Get("Content-Type")
// 如果是纯文本类型 提取为正文
if strings.HasPrefix(partContentType, "text/plain") {
body, _ := ioutil.ReadAll(part)
fmt.Println("纯文本正文:", string(body))
}
// 如果是富文本类型 可根据需求提取
if strings.HasPrefix(partContentType, "text/html") {
htmlBody, _ := ioutil.ReadAll(part)
fmt.Println("富文本正文:", string(htmlBody))
}
part.Close()
}
} else {
// 单部分邮件直接读取Body
body, _ := ioutil.ReadAll(msg.Body)
fmt.Println("邮件正文:", string(body))
}
}
常见问题与注意事项
- 解析邮件时如果原始数据不符合RFC 5322标准,可能会返回解析错误,需要先校验邮件格式合法性。
- 多部分邮件的边界值需要严格匹配,否则会导致部分内容提取失败。
- 如果邮件正文是base64或者其他编码格式,需要结合
encoding/base64等库进行解码后再使用。 - 处理大邮件时建议采用流式读取方式,避免一次性加载全部内容占用过多内存。
net/mail库主要处理邮件的解析逻辑,如果需要发送邮件或者更复杂的邮件操作,可以结合net/smtp等标准库共同使用。