Go net/smtp 包详解
概述
net/smtp 包实现了 RFC 5321 中定义的简单邮件传输协议(SMTP)。它还支持以下扩展:
- 8BITMIME RFC 1652
- AUTH RFC 2554
- STARTTLS RFC 3207
该包提供了发送邮件的完整功能,包括连接 SMTP 服务器、身份验证、发送邮件等。
重要说明:
- ✓ 实现 RFC 5321 SMTP 协议
- ✓ 支持 8BITMIME 扩展(RFC 1652)
- ✓ 支持 AUTH 认证扩展(RFC 2554)
- ✓ 支持 STARTTLS 加密扩展(RFC 3207)
- ✓ Go 1.0+ 引入,已冻结(不再接受新特性)
- ✓ 低级别机制,不支持 DKIM、MIME 附件等
- ✓ 需要手动构造 RFC 822 格式的邮件内容
包已冻结: net/smtp 包已冻结,不再添加新功能。某些外部包提供更多功能。
包导入
import (
"net/smtp"
)
基本使用
1. 使用 SendMail 发送邮件
package main
import (
"fmt"
"log"
"net/smtp"
"strings"
)
func main() {
// SMTP 服务器配置
smtpHost := "smtp.example.com"
smtpPort := "587"
// 发件人和密码
from := "your_email@example.com"
password := "your_password"
// 收件人
to := []string{"recipient@example.com"}
// 邮件内容
subject := "测试邮件"
body := "这是一封通过 Go 发送的测试邮件"
msg := []byte("From: " + from + "\r\n" +
"To: " + strings.Join(to, ",") + "\r\n" +
"Subject: " + subject + "\r\n" +
"MIME-version: 1.0;\r\n" +
"Content-Type: text/plain; charset=\"UTF-8\"\r\n" +
"\r\n" +
body)
// 身份验证
auth := smtp.PlainAuth("", from, password, smtpHost)
// 发送邮件
err := smtp.SendMail(smtpHost+":"+smtpPort, auth, from, to, msg)
if err != nil {
log.Fatal(err)
}
fmt.Println("邮件发送成功!")
}
2. 使用 Client 发送(更灵活)
package main
import (
"fmt"
"log"
"net/smtp"
)
func main() {
// 连接到 SMTP 服务器
c, err := smtp.Dial("mail.example.com:25")
if err != nil {
log.Fatal(err)
}
defer c.Close()
// 设置发件人
if err := c.Mail("sender@example.org"); err != nil {
log.Fatal(err)
}
// 设置收件人
if err := c.Rcpt("recipient@example.net"); err != nil {
log.Fatal(err)
}
// 写入邮件内容
wc, err := c.Data()
if err != nil {
log.Fatal(err)
}
_, err = fmt.Fprintf(wc, "Subject: Test Email\r\n\r\nThis is the email body")
if err != nil {
log.Fatal(err)
}
err = wc.Close()
if err != nil {
log.Fatal(err)
}
// 退出
err = c.Quit()
if err != nil {
log.Fatal(err)
}
}
一、类型(按 a-z 排序)
Auth
Auth 接口由 SMTP 认证机制实现。
type Auth interface {
// Start 开始与服务器的认证
// 返回认证协议名称和可选的初始 AUTH 消息数据
// 可以返回 proto == "" 表示跳过认证
// 如果返回非 nil 错误,SMTP 客户端中止认证并关闭连接
Start(server *ServerInfo) (proto string, toServer []byte, err error)
// Next 继续认证。服务器刚发送了 fromServer 数据
// 如果 more 为 true,服务器期望响应,Next 应返回 toServer
// 否则 Next 应返回 toServer == nil
// 如果 Next 返回非 nil 错误,SMTP 客户端中止认证并关闭连接
Next(fromServer []byte, more bool) (toServer []byte, err error)
}
说明:
Start- 开始认证,返回协议名称和初始数据Next- 继续认证过程,处理挑战 - 响应
Client
Client 表示到 SMTP 服务器的客户端连接。
type Client struct {
// Text 是客户端使用的 textproto.Conn
// 导出以允许客户端添加扩展
Text *textproto.Conn
// 包含隐藏或未导出的字段
}
Client.Auth
func (c *Client) Auth(a Auth) error
Auth 使用提供的认证机制认证客户端。失败的认证会关闭连接。只有通告 AUTH 扩展的服务器才支持此函数。
参数:
a- Auth 认证机制
返回值:
error- 认证错误
示例:
auth := smtp.PlainAuth("", "user@example.com", "password", "mail.example.com")
err := c.Auth(auth)
if err != nil {
log.Fatal(err)
}
Client.Close
func (c *Client) Close() error
Close 关闭连接。
示例:
defer c.Close()
Client.Data
func (c *Client) Data() (io.WriteCloser, error)
Data 向服务器发送 DATA 命令,并返回一个 writer 用于写入邮件头部和正文。调用者应在调用 c 的任何其他方法之前关闭 writer。调用 Data 之前必须调用一次或多次 Client.Rcpt。
返回值:
io.WriteCloser- 写入邮件内容的 writererror- 错误
示例:
wc, err := c.Data()
if err != nil {
log.Fatal(err)
}
defer wc.Close()
_, err = fmt.Fprintf(wc, "Subject: Test\r\n\r\nBody")
Client.Extension
func (c *Client) Extension(ext string) (bool, string)
Extension 报告服务器是否支持某个扩展。扩展名不区分大小写。如果支持扩展,Extension 还返回服务器为该扩展指定的任何参数。
参数:
ext- 扩展名称(如 “AUTH”, “STARTTLS”)
返回值:
bool- 是否支持string- 扩展参数
示例:
if ok, _ := c.Extension("AUTH"); ok {
fmt.Println("服务器支持 AUTH 扩展")
}
Client.Hello
func (c *Client) Hello(localName string) error
Hello 向服务器发送 HELO 或 EHLO 命令,使用给定的主机名。只有在客户端需要控制使用的主机名时才需要调用此方法。否则客户端会自动介绍为“localhost“。如果调用 Hello,必须在任何其他方法之前调用。
参数:
localName- 本地主机名
示例:
err := c.Hello("myhost.example.com")
if err != nil {
log.Fatal(err)
}
Client.Mail
func (c *Client) Mail(from string) error
Mail 向服务器发送 MAIL 命令,使用提供的电子邮件地址。如果服务器支持 8BITMIME 扩展,Mail 添加 BODY=8BITMIME 参数。如果服务器支持 SMTPUTF8 扩展,Mail 添加 SMTPUTF8 参数。这启动一个邮件事务,后跟一个或多个 Client.Rcpt 调用。
参数:
from- 发件人地址
示例:
err := c.Mail("sender@example.org")
if err != nil {
log.Fatal(err)
}
Client.Noop
func (c *Client) Noop() error
Noop 向服务器发送 NOOP 命令。它什么都不做,只是检查与服务器的连接是否正常。
示例:
err := c.Noop()
if err != nil {
log.Fatal("连接可能已断开")
}
Client.Quit
func (c *Client) Quit() error
Quit 发送 QUIT 命令并关闭与服务器的连接。
示例:
err := c.Quit()
if err != nil {
log.Fatal(err)
}
Client.Rcpt
func (c *Client) Rcpt(to string) error
Rcpt 向服务器发送 RCPT 命令,使用提供的电子邮件地址。调用 Rcpt 之前必须调用 Client.Mail,之后可以跟 Client.Data 调用或另一个 Rcpt 调用。
参数:
to- 收件人地址
示例:
err := c.Mail("sender@example.org")
if err != nil {
log.Fatal(err)
}
err = c.Rcpt("recipient1@example.net")
if err != nil {
log.Fatal(err)
}
err = c.Rcpt("recipient2@example.net")
if err != nil {
log.Fatal(err)
}
Client.Reset
func (c *Client) Reset() error
Reset 向服务器发送 RSET 命令,中止当前的邮件事务。
示例:
// 如果邮件发送失败,可以重置
err := c.Reset()
if err != nil {
log.Fatal(err)
}
Client.StartTLS
func (c *Client) StartTLS(config *tls.Config) error
StartTLS 发送 STARTTLS 命令并加密所有后续通信。只有通告 STARTTLS 扩展的服务器才支持此函数。
参数:
config- TLS 配置
示例:
import "crypto/tls"
config := &tls.Config{ServerName: "mail.example.com"}
err := c.StartTLS(config)
if err != nil {
log.Fatal(err)
}
Client.TLSConnectionState
func (c *Client) TLSConnectionState() (state tls.ConnectionState, ok bool)
TLSConnectionState 返回客户端的 TLS 连接状态。如果 Client.StartTLS 未成功,返回值为其零值。
返回值:
tls.ConnectionState- TLS 连接状态bool- 是否成功获取
示例:
if state, ok := c.TLSConnectionState(); ok {
fmt.Printf("TLS 版本:%x\n", state.Version)
}
Client.Verify
func (c *Client) Verify(addr string) error
Verify 检查服务器上电子邮件地址的有效性。如果 Verify 返回 nil,地址有效。非 nil 返回不一定表示地址无效。许多服务器出于安全原因不会验证地址。
参数:
addr- 要验证的电子邮件地址
示例:
err := c.Verify("user@example.com")
if err == nil {
fmt.Println("地址有效")
} else {
fmt.Println("地址可能无效或服务器不支持验证")
}
ServerInfo
ServerInfo 记录有关 SMTP 服务器的信息。
type ServerInfo struct {
Name string // 服务器名称
TLS bool // 是否使用 TLS
Auth []string // 支持的认证机制
}
字段说明:
Name- SMTP 服务器名称TLS- 连接是否使用 TLSAuth- 服务器支持的认证机制列表
二、函数(按 a-z 排序)
CRAMMD5Auth
func CRAMMD5Auth(username, secret string) Auth
CRAMMD5Auth 返回一个 Auth,实现 RFC 2195 中定义的 CRAM-MD5 认证机制。返回的 Auth 使用给定的用户名和密钥通过挑战 - 响应机制向服务器认证。
参数:
username- 用户名secret- 密钥/密码
返回值:
Auth- CRAM-MD5 认证机制
示例:
auth := smtp.CRAMMD5Auth("user@example.com", "secret")
err := smtp.SendMail("mail.example.com:25", auth, "from@example.com",
[]string{"to@example.com"}, []byte("Subject: Test\r\n\r\nBody"))
注意:
- CRAM-MD5 是挑战 - 响应认证机制
- 比 PLAIN 更安全,因为密码不在网络上传输
- 但需要服务器存储明文密码(或可逆加密)
Dial
func Dial(addr string) (*Client, error)
Dial 返回一个新的 Client,连接到 addr 指定的 SMTP 服务器。addr 必须包含端口,如“mail.example.com:smtp“。
参数:
addr- SMTP 服务器地址(包含端口)
返回值:
*Client- SMTP 客户端error- 连接错误
示例:
c, err := smtp.Dial("mail.example.com:25")
if err != nil {
log.Fatal(err)
}
defer c.Close()
NewClient
func NewClient(conn net.Conn, host string) (*Client, error)
NewClient 返回一个新的 Client,使用现有连接和 host 作为认证时使用的服务器名称。
参数:
conn- 现有网络连接host- 服务器名称(用于认证)
返回值:
*Client- SMTP 客户端error- 错误
示例:
conn, err := net.Dial("tcp", "mail.example.com:25")
if err != nil {
log.Fatal(err)
}
defer conn.Close()
c, err := smtp.NewClient(conn, "mail.example.com")
if err != nil {
log.Fatal(err)
}
PlainAuth
func PlainAuth(identity, username, password, host string) Auth
PlainAuth 返回一个 Auth,实现 RFC 4616 中定义的 PLAIN 认证机制。返回的 Auth 使用给定的用户名和密码向 host 认证,并作为 identity 行事。通常 identity 应为空字符串,作为 username 使用。
参数:
identity- 身份(通常为空)username- 用户名password- 密码host- SMTP 服务器主机名
返回值:
Auth- PLAIN 认证机制
示例:
auth := smtp.PlainAuth("", "user@example.com", "password", "mail.example.com")
重要说明:
- PlainAuth 仅在连接使用 TLS 或连接到 localhost 时才会发送凭据
- 否则认证会失败并返回错误,不发送凭据
- 这是为了防止凭据在明文连接中传输
SendMail
func SendMail(addr string, a Auth, from string, to []string, msg []byte) error
SendMail 连接到 addr 指定的服务器,如果可能则切换到 TLS,使用机制 a 进行认证(如果可能),然后发送从地址 from 到地址 to 的邮件,消息为 msg。
参数:
addr- SMTP 服务器地址(必须包含端口,如“mail.example.com:smtp“)a- 认证机制(可为 nil)from- 发件人地址to- 收件人地址列表(SMTP RCPT 地址)msg- RFC 822 格式的邮件内容(头部 + 空行 + 正文)
返回值:
error- 发送错误
示例:
auth := smtp.PlainAuth("", "user@example.com", "password", "mail.example.com")
to := []string{"recipient@example.net"}
msg := []byte("To: recipient@example.net\r\n" +
"Subject: discount Gophers!\r\n" +
"\r\n" +
"This is the email body.\r\n")
err := smtp.SendMail("mail.example.com:25", auth, "sender@example.org", to, msg)
if err != nil {
log.Fatal(err)
}
重要说明:
addr必须包含端口to参数中的地址是 SMTP RCPT 地址msg应该是 RFC 822 格式的邮件,头部在前,空行,然后是消息正文msg的行应以 CRLF 结尾msg头部通常应包括“From“、“To”、“Subject“和“Cc“等字段- 发送“Bcc“邮件的方法是在
to参数中包含电子邮件地址,但不在msg头部中包含它
限制: SendMail 函数和 net/smtp 包是低级别机制,不提供以下支持:
- DKIM 签名
- MIME 附件(参见 mime/multipart 包)
- 其他邮件功能
三、典型示例
示例 1:发送简单文本邮件
package main
import (
"fmt"
"log"
"net/smtp"
"strings"
)
func main() {
smtpHost := "smtp.example.com"
smtpPort := "587"
from := "sender@example.com"
password := "password"
to := []string{"recipient@example.com"}
msg := []byte("From: " + from + "\r\n" +
"To: " + strings.Join(to, ",") + "\r\n" +
"Subject: 测试邮件\r\n" +
"Content-Type: text/plain; charset=UTF-8\r\n" +
"\r\n" +
"这是一封测试邮件")
auth := smtp.PlainAuth("", from, password, smtpHost)
err := smtp.SendMail(smtpHost+":"+smtpPort, auth, from, to, msg)
if err != nil {
log.Fatal(err)
}
fmt.Println("邮件发送成功!")
}
示例 2:发送 HTML 邮件
package main
import (
"log"
"net/smtp"
"strings"
)
func main() {
smtpHost := "smtp.example.com"
smtpPort := "587"
from := "sender@example.com"
password := "password"
to := []string{"recipient@example.com"}
htmlBody := `<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; }
h2 { color: #336699; }
p { color: #333333; }
</style>
</head>
<body>
<h2>你好,这是一封 HTML 邮件!</h2>
<p>这封邮件<b>内容丰富</b>,包含了一些<i>格式化文本</i>。</p>
</body>
</html>`
msg := []byte("From: " + from + "\r\n" +
"To: " + strings.Join(to, ",") + "\r\n" +
"Subject: HTML 邮件\r\n" +
"MIME-version: 1.0\r\n" +
"Content-Type: text/html; charset=UTF-8\r\n" +
"\r\n" +
htmlBody)
auth := smtp.PlainAuth("", from, password, smtpHost)
err := smtp.SendMail(smtpHost+":"+smtpPort, auth, from, to, msg)
if err != nil {
log.Fatal(err)
}
}
示例 3:发送给多个收件人
package main
import (
"log"
"net/smtp"
"strings"
)
func main() {
smtpHost := "smtp.example.com"
smtpPort := "587"
from := "sender@example.com"
password := "password"
// 收件人列表
to := []string{
"recipient1@example.com",
"recipient2@example.com",
"recipient3@example.com",
}
msg := []byte("From: " + from + "\r\n" +
"To: " + strings.Join(to, ",") + "\r\n" +
"Subject: 群发邮件\r\n" +
"Content-Type: text/plain; charset=UTF-8\r\n" +
"\r\n" +
"这是一封群发邮件")
auth := smtp.PlainAuth("", from, password, smtpHost)
err := smtp.SendMail(smtpHost+":"+smtpPort, auth, from, to, msg)
if err != nil {
log.Fatal(err)
}
}
示例 4:发送带抄送的邮件
package main
import (
"log"
"net/smtp"
"strings"
)
func main() {
smtpHost := "smtp.example.com"
smtpPort := "587"
from := "sender@example.com"
password := "password"
to := []string{"recipient@example.com"}
cc := []string{"cc1@example.com", "cc2@example.com"}
// 合并 to 和 cc 用于 SMTP 发送
allRecipients := append(to, cc...)
msg := []byte("From: " + from + "\r\n" +
"To: " + strings.Join(to, ",") + "\r\n" +
"Cc: " + strings.Join(cc, ",") + "\r\n" +
"Subject: 带抄送的邮件\r\n" +
"Content-Type: text/plain; charset=UTF-8\r\n" +
"\r\n" +
"这是一封带抄送的邮件")
auth := smtp.PlainAuth("", from, password, smtpHost)
err := smtp.SendMail(smtpHost+":"+smtpPort, auth, from, allRecipients, msg)
if err != nil {
log.Fatal(err)
}
}
示例 5:发送带密送的邮件
package main
import (
"log"
"net/smtp"
"strings"
)
func main() {
smtpHost := "smtp.example.com"
smtpPort := "587"
from := "sender@example.com"
password := "password"
to := []string{"recipient@example.com"}
bcc := []string{"bcc1@example.com", "bcc2@example.com"}
// BCC 地址只在 SMTP RCPT 中,不在邮件头中
allRecipients := append(to, bcc...)
// 邮件头中不包含 BCC
msg := []byte("From: " + from + "\r\n" +
"To: " + strings.Join(to, ",") + "\r\n" +
"Subject: 带密送的邮件\r\n" +
"Content-Type: text/plain; charset=UTF-8\r\n" +
"\r\n" +
"这是一封带密送的邮件,BCC 收件人不会显示在邮件头中")
auth := smtp.PlainAuth("", from, password, smtpHost)
err := smtp.SendMail(smtpHost+":"+smtpPort, auth, from, allRecipients, msg)
if err != nil {
log.Fatal(err)
}
}
示例 6:使用 Client 发送(更灵活的控制)
package main
import (
"fmt"
"log"
"net/smtp"
)
func main() {
// 连接到服务器
c, err := smtp.Dial("mail.example.com:25")
if err != nil {
log.Fatal(err)
}
defer c.Close()
// 检查是否支持 STARTTLS
if ok, _ := c.Extension("STARTTLS"); ok {
fmt.Println("支持 STARTTLS")
// 可以调用 StartTLS 加密
}
// 检查是否支持 AUTH
if ok, param := c.Extension("AUTH"); ok {
fmt.Printf("支持 AUTH: %s\n", param)
}
// 设置发件人
if err := c.Mail("sender@example.org"); err != nil {
log.Fatal(err)
}
// 设置多个收件人
recipients := []string{"recipient1@example.net", "recipient2@example.net"}
for _, rcpt := range recipients {
if err := c.Rcpt(rcpt); err != nil {
log.Fatal(err)
}
}
// 写入邮件
wc, err := c.Data()
if err != nil {
log.Fatal(err)
}
_, err = fmt.Fprintf(wc, "To: recipient1@example.net, recipient2@example.net\r\n"+
"Subject: Test Email\r\n"+
"Content-Type: text/plain; charset=UTF-8\r\n"+
"\r\n"+
"This is the email body")
if err != nil {
log.Fatal(err)
}
err = wc.Close()
if err != nil {
log.Fatal(err)
}
// 退出
err = c.Quit()
if err != nil {
log.Fatal(err)
}
}
示例 7:使用 TLS 加密连接
package main
import (
"crypto/tls"
"log"
"net"
"net/smtp"
"strings"
)
func main() {
smtpHost := "smtp.example.com"
smtpPort := "587"
from := "sender@example.com"
password := "password"
to := []string{"recipient@example.com"}
// 建立连接
conn, err := net.Dial("tcp", smtpHost+":"+smtpPort)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
// 创建客户端
c, err := smtp.NewClient(conn, smtpHost)
if err != nil {
log.Fatal(err)
}
// 启动 TLS
tlsConfig := &tls.Config{
ServerName: smtpHost,
}
if err := c.StartTLS(tlsConfig); err != nil {
log.Fatal(err)
}
// 认证
auth := smtp.PlainAuth("", from, password, smtpHost)
if err := c.Auth(auth); err != nil {
log.Fatal(err)
}
// 发送邮件
msg := []byte("From: " + from + "\r\n" +
"To: " + strings.Join(to, ",") + "\r\n" +
"Subject: TLS 加密邮件\r\n" +
"Content-Type: text/plain; charset=UTF-8\r\n" +
"\r\n" +
"这是一封通过 TLS 加密发送的邮件")
if err := c.Mail(from); err != nil {
log.Fatal(err)
}
if err := c.Rcpt(to[0]); err != nil {
log.Fatal(err)
}
wc, err := c.Data()
if err != nil {
log.Fatal(err)
}
_, err = wc.Write(msg)
if err != nil {
log.Fatal(err)
}
err = wc.Close()
if err != nil {
log.Fatal(err)
}
err = c.Quit()
if err != nil {
log.Fatal(err)
}
}
示例 8:验证电子邮件地址
package main
import (
"fmt"
"log"
"net/smtp"
)
func main() {
c, err := smtp.Dial("mail.example.com:25")
if err != nil {
log.Fatal(err)
}
defer c.Close()
// 尝试验证地址
addresses := []string{
"user1@example.com",
"user2@example.com",
"invalid@example.com",
}
for _, addr := range addresses {
err := c.Verify(addr)
if err == nil {
fmt.Printf("%s: 有效\n", addr)
} else {
fmt.Printf("%s: 无法验证或无效 (%v)\n", addr, err)
}
}
}
四、最佳实践
1. 使用授权码而非密码
// ✓ 推荐 - 使用授权码
password := getAppSpecificPassword() // 应用专用授权码
// ✗ 不推荐 - 使用主密码
password := "main_password"
2. 始终使用 TLS 加密
// ✓ 推荐 - 使用端口 587 + STARTTLS
smtpPort := "587"
auth := smtp.PlainAuth("", from, password, smtpHost)
smtp.SendMail(smtpHost+":"+smtpPort, auth, from, to, msg)
// ✗ 不推荐 - 明文连接
smtpPort := "25" // 通常不加密
3. 正确构造 MIME 格式
// ✓ 正确 - CRLF 结尾,正确格式
msg := []byte("From: sender@example.com\r\n" +
"To: recipient@example.com\r\n" +
"Subject: 测试\r\n" +
"Content-Type: text/plain; charset=UTF-8\r\n" +
"\r\n" +
"邮件正文")
// ✗ 错误 - 使用 LF 而非 CRLF
msg := []byte("From: sender@example.com\n" +
"To: recipient@example.com\n" +
"\n" +
"邮件正文")
4. 使用 defer 关闭连接
c, err := smtp.Dial("mail.example.com:25")
if err != nil {
log.Fatal(err)
}
defer c.Close() // 确保连接关闭
5. 错误处理
err := smtp.SendMail(addr, auth, from, to, msg)
if err != nil {
// 详细错误处理
log.Printf("发送邮件失败:%v", err)
// 可以重试或通知用户
}
6. 批量发送优化
// ✓ 推荐 - 复用连接批量发送
c, err := smtp.Dial("mail.example.com:25")
if err != nil {
log.Fatal(err)
}
defer c.Close()
for _, email := range emailList {
// 发送邮件
c.Mail(from)
c.Rcpt(email)
// ... 发送
c.Reset() // 重置事务
}
c.Quit()
五、与其他包配合
1. 与 mime/multipart 配合发送附件
import (
"bytes"
"mime/multipart"
"net/smtp"
)
// 创建 multipart 消息
buf := new(bytes.Buffer)
w := multipart.NewWriter(buf)
// 添加文本部分
w.WritePart([]byte("这是邮件正文"))
// 添加附件
part, err := w.CreateFormFile("attachment", "file.txt")
if err != nil {
log.Fatal(err)
}
part.Write([]byte("附件内容"))
w.Close()
// 发送邮件
msg := []byte("From: sender@example.com\r\n" +
"To: recipient@example.com\r\n" +
"Subject: 带附件的邮件\r\n" +
"Content-Type: multipart/mixed; boundary=" + w.Boundary() + "\r\n" +
"\r\n" +
buf.Bytes())
smtp.SendMail("smtp.example.com:587", auth, from, to, msg)
2. 与 crypto/tls 配合使用加密
import (
"crypto/tls"
"net/smtp"
)
config := &tls.Config{
ServerName: "smtp.example.com",
MinVersion: tls.VersionTLS12,
}
c, err := smtp.Dial("smtp.example.com:587")
if err != nil {
log.Fatal(err)
}
defer c.Close()
if err := c.StartTLS(config); err != nil {
log.Fatal(err)
}
3. 与 bufio 配合提高效率
import (
"bufio"
"net/smtp"
)
c, err := smtp.Dial("smtp.example.com:25")
if err != nil {
log.Fatal(err)
}
defer c.Close()
wc, err := c.Data()
if err != nil {
log.Fatal(err)
}
defer wc.Close()
// 使用缓冲写入
bw := bufio.NewWriter(wc)
bw.WriteString("Subject: Test\r\n")
bw.WriteString("\r\n")
bw.WriteString("Body")
bw.Flush()
六、快速参考
函数总览
| 函数 | 说明 |
|---|---|
| CRAMMD5Auth | 创建 CRAM-MD5 认证机制 |
| Dial | 连接到 SMTP 服务器 |
| NewClient | 从现有连接创建客户端 |
| PlainAuth | 创建 PLAIN 认证机制 |
| SendMail | 发送邮件(一站式) |
类型总览
| 类型 | 说明 |
|---|---|
| Auth | 认证机制接口 |
| Client | SMTP 客户端 |
| ServerInfo | SMTP 服务器信息 |
Client 方法
| 方法 | 说明 |
|---|---|
| Auth | 认证客户端 |
| Close | 关闭连接 |
| Data | 发送 DATA 命令 |
| Extension | 检查扩展支持 |
| Hello | 发送 HELO/EHLO |
| 发送 MAIL 命令 | |
| Noop | 发送 NOOP 命令 |
| Quit | 发送 QUIT 命令 |
| Rcpt | 发送 RCPT 命令 |
| Reset | 发送 RSET 命令 |
| StartTLS | 启动 TLS 加密 |
| TLSConnectionState | 获取 TLS 状态 |
| Verify | 验证邮件地址 |
认证机制对比
| 机制 | 安全性 | 要求 |
|---|---|---|
| PLAIN | 低(需 TLS) | TLS 或 localhost |
| CRAM-MD5 | 中 | 服务器支持挑战 - 响应 |
常用端口
| 端口 | 用途 | 加密 |
|---|---|---|
| 25 | SMTP | 通常无 |
| 465 | SMTPS | SSL/TLS |
| 587 | Submission | STARTTLS |
七、注意事项
1. 包已冻结
// net/smtp 包已冻结,不再接受新特性
// 对于更复杂的需求,考虑使用第三方包
// 如:github.com/emersion/go-smtp
2. PlainAuth 安全限制
// PlainAuth 仅在以下情况发送凭据:
// 1. 连接使用 TLS
// 2. 连接到 localhost
// ✓ 安全 - 配合 STARTTLS 使用
c.StartTLS(config)
c.Auth(smtp.PlainAuth(...))
// ✗ 错误 - 明文连接会失败
c.Auth(smtp.PlainAuth(...)) // 返回错误
3. 邮件格式要求
// ✓ 正确 - RFC 822 格式,CRLF 结尾
msg := []byte("From: sender@example.com\r\n" +
"To: recipient@example.com\r\n" +
"Subject: Test\r\n" +
"\r\n" +
"Body")
// ✗ 错误 - 使用 LF
msg := []byte("From: sender@example.com\n" +
"To: recipient@example.com\n" +
"\n" +
"Body")
4. BCC 发送方式
// BCC 地址只在 to 参数中,不在邮件头中
to := []string{"to@example.com", "bcc@example.com"}
msg := []byte("From: sender@example.com\r\n" +
"To: to@example.com\r\n" + // 不包含 BCC
"Subject: Test\r\n" +
"\r\n" +
"Body")
smtp.SendMail(addr, auth, from, to, msg)
5. 不支持的功能
// net/smtp 不支持:
// - DKIM 签名
// - MIME 附件(需使用 mime/multipart)
// - HTML 邮件构建(需手动构造)
// - 邮件模板
// 需要使用第三方包
6. 错误类型
err := smtp.SendMail(...)
if err != nil {
// 错误可能包含 SMTP 响应码
// 可以解析错误信息判断具体原因
log.Printf("发送失败:%v", err)
}
7. 连接复用
// ✓ 推荐 - 复用连接发送多封邮件
c, _ := smtp.Dial("smtp.example.com:587")
defer c.Close()
for _, email := range emails {
c.Mail(from)
c.Rcpt(email)
// ... 发送
c.Reset()
}
c.Quit()
8. Gmail 特殊配置
// Gmail 需要:
// 1. 启用两步验证
// 2. 生成应用专用密码
// 3. 使用授权码而非主密码
smtpHost := "smtp.gmail.com"
smtpPort := "587"
password := "app_specific_password" // 应用专用密码
最后更新: 2026-04-05
Go 版本: Go 1.0+(包已冻结)
包文档: https://pkg.go.dev/net/smtp
相关 RFC: RFC 5321 (SMTP), RFC 2554 (AUTH), RFC 3207 (STARTTLS)
替代方案: github.com/emersion/go-smtp(提供更多功能)