Go标准库net/smtp不支持附件,需用github.com/go-mail/mail等第三方库;手动构造MIME易出错,关键在boundary一致性、CRLF换行、base64每76字符换行及正确头字段。

Go 标准库不支持直接发带附件的邮件
标准库 net/smtp 只能发纯文本或简单 HTML 邮件,不处理 MIME 多部分结构。想加附件,必须自己构造符合 RFC 2822 / RFC 2045 的原始邮件内容,或者用第三方库补足这一块。
常见错误现象:smtp.SendMail 发出去的邮件附件变成乱码、收件方看不到附件、邮件体被截断、Gmail 直接拒收(报 5.7.1 Message rejected due to spam content)——这些基本都是 MIME 边界(boundary)、编码(base64 / quoted-printable)、头字段顺序写错了。
- 别手写整个 MIME 字符串:容易漏掉换行、空行、
\r\n换行符不一致、boundary 冲突 - 别用
strings.Builder拼接多层 multipart:边界嵌套、Content-Disposition 位置错位会导致解析失败 - 优先选
gopkg.in/gomail.v2或github.com/go-mail/mail(后者是前者的维护分支),它们封装了 MIME 构造逻辑,且默认用 CRLF、随机 boundary、正确 base64 编码
用 github.com/go-mail/mail 添加附件的最小可行代码
这个库是目前最轻量、文档最清晰、仍在维护的选项。它不依赖 cgo,支持 TLS/STARTTLS,附件自动 base64 编码 + 设置 Content-Transfer-Encoding 和 Content-Disposition。
关键点:附件路径必须是本地可读文件;Attach() 内部会打开文件、读取、编码、写入 MIME body,所以大文件要留意内存占用(默认全读进内存)。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
package main
import (
"log"
"net/smtp"
"github.com/go-mail/mail"
)
func main() {
d := mail.NewDialer("smtp.gmail.com", 587, "user@gmail.com", "app-password")
m := mail.NewMessage()
m.SetHeader("From", "user@gmail.com")
m.SetHeader("To", "receiver@example.com")
m.SetHeader("Subject", "带附件测试")
m.SetBody("text/plain", "正文内容")
m.Attach("/path/to/report.pdf") // ← 这行就搞定附件
if err := d.DialAndSend(m); err != nil {
log.Fatal(err)
}
}
-
m.Attach()默认用文件名作为filename,如需自定义显示名,用m.Attach("/path/to/file", mail.WithName("发票_2024.pdf")) - 支持多个
Attach(),按调用顺序写入 multipart,但注意总大小别超邮箱限制(Gmail 是 25MB) - 如果附件是内存数据(比如生成的 PDF bytes),用
m.AttachFile("invoice.pdf", data, mail.WithType("application/pdf"))
手动构造 MIME 附件时最容易崩的三个地方
真要自己造(比如嵌入模板引擎、做流式附件、或审计安全边界),下面三点不检查清楚,90% 概率发出去的邮件被当垃圾邮件或附件丢失。
-
boundary值不能含引号、空格、下划线以外的特殊字符,推荐用fmt.Sprintf("boundary_%d", time.Now().UnixNano())生成,且必须在Content-Type: multipart/mixed; boundary="xxx"和每段分隔行(--xxx)中完全一致 - 每段 MIME part 之间必须有且仅有一个
\r\n空行;最后一段后必须跟--xxx--\r\n(结尾两个短横线) - 附件二进制内容必须用
base64.StdEncoding.EncodeToString()编码,并每 76 字符换行(base64包自带 wrap 功能,别自己切);否则 Outlook 会拒绝解析
发送失败时先盯住 SMTP 错误码和 Content-Type 头
很多“发不出去”其实不是 Go 代码问题,而是邮件服务器对 MIME 结构敏感。Gmail、Outlook、阿里云邮件推送都对 Content-Type、MIME-Version、Date 等头字段有隐性要求。
典型错误信息:554 5.6.0 Unrecognized message header 或 5.7.1 Message rejected due to missing or invalid MIME headers —— 这说明你漏写了 MIME-Version: 1.0,或 Content-Type 缺少 boundary 参数,或用了 multipart/alternative 却塞了附件(只能用 multipart/mixed 或 multipart/related)。
- 用
smtp.SendMail前,把构造好的完整邮件字符串先fmt.Println(string(rawMsg))打出来,粘到在线 MIME 解析器(比如 mimevalidator.net)里看结构是否合法 - 确保所有头字段用
\r\n结尾,正文开头必须有两个\r\n(头与体分离) - 别在附件名里用中文而没加
filename*=UTF-8''...编码 —— 大部分客户端只认这个格式,filename="中文.pdf"会变问号
附件这事看着简单,但 MIME 规范细节多到离谱。边界怎么起、换行用 CR/LF 还是 LF、base64 每行长度、filename 编码方式……任一环节错,收件方就可能看不到那个「回形针」图标。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










