Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 - 写入邮件内容的 writer
  • error - 错误

示例:

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 - 连接是否使用 TLS
  • Auth - 服务器支持的认证机制列表

二、函数(按 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认证机制接口
ClientSMTP 客户端
ServerInfoSMTP 服务器信息

Client 方法

方法说明
Auth认证客户端
Close关闭连接
Data发送 DATA 命令
Extension检查扩展支持
Hello发送 HELO/EHLO
Mail发送 MAIL 命令
Noop发送 NOOP 命令
Quit发送 QUIT 命令
Rcpt发送 RCPT 命令
Reset发送 RSET 命令
StartTLS启动 TLS 加密
TLSConnectionState获取 TLS 状态
Verify验证邮件地址

认证机制对比

机制安全性要求
PLAIN低(需 TLS)TLS 或 localhost
CRAM-MD5服务器支持挑战 - 响应

常用端口

端口用途加密
25SMTP通常无
465SMTPSSSL/TLS
587SubmissionSTARTTLS

七、注意事项

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(提供更多功能)