SMTP(Simple Mail Transfer Protocol) 是基于文本的简单邮件协议,默认使用 25 端口。下图展示了 SMTP 的基本操作。

-
Alice 通过 user agent 指定 Bob 的邮件地址以及编辑邮件,然后发送邮件;
-
Alice 的 user agent 先把邮件发送到 Alice 的邮件服务器,邮件服务器用把放到消息队列中;
-
与此同时,Alice 的邮件服务器的客户端(这个邮件服务器既充当服务器也充当客户端)在队列中发现了 Alice 发向 Bob 的邮件,于是就向 Bob 的邮件服务器发起连接;
-
在连接初始化后,Alice 邮件服务器把 Alice 的邮件通过连接发送出去;
-
Bob 的邮件服务器收到了来自 Alice 邮件服务器的邮件,把该邮件放到 Bob 的个人邮箱;
-
Bob 通过 user agent 读取邮件(注意:这里的读取用的并不是 SMTP 协议)。
由此可见,SMTP 不会使用中间邮件服务器发送邮件,即使两个邮件服务器之间在地球的两端。我们可以使用 telnet 进行邮件发送,以下是使用 qq 的 SMTP 服务器发送邮件的完整流程。
-
与 smtp.qq.com 服务器(这个就是发送者的邮件服务器)的 25 端口建立连接。
$ telnet smtp.qq.com 25 Trying 14.18.245.164... Connected to smtp.qq.com Escape character is '^]'. 200 smtp.qq.com Esmtp QQ Mail Server -
Hello。
HELO 163.com 250 smtp.qq.com -
用户登录。
base64(username)和base64(password)指的是分别输入用户名和密码的 base64 编码结果。VXNlcm5hbWU6和UGFzc3dvcmQ6分别是Username:和Password :的 base64 编码结果。一般来说,密码都是授权码,可以在邮件客户端的设置里生成和获取。AUTH LOGIN 334 VXNlcm5hbWU6 base64(username) 334 UGFzc3dvcmQ6 base64(password) 235 Authentication successful -
发送人,要对应 SMTP 服务器,qq 的 SMTP 服务器可以发送 foxmail 的邮件。
MAIL FROM: <username@foxmail.com> 250 Ok -
收件人。
RCPT TO: <username@163.com> 250 Ok -
编辑邮件内容并发送。SUBJECT(主题)之后要空一行,接着写邮件正文。正文以单独占一行的
.为结束。DATA 354 End data with <CR><LF>.<CR><LF> FROM: username@foxmail.com TO: username@163.com SUBJECT: SMTP Mail from Iterm. . 250 Ok: queued as -
退出。
QUIT 221 Bye
与 HTTP 协议相比,HTTP 协议是pull类的协议,从服务器 pull 数据对象;而 SMTP 由是push类的协议,向 SMTP 服务器 push 邮件。
以下是 Go 语言写的 SMTP 用户端。
package main
import (
"fmt"
"log"
"net/smtp"
"strings"
)
func SendMail(user, password, host, to, subject, body, mailType string) error {
hp := strings.Split(host, ":")
auth := smtp.PlainAuth("", user, password, hp[0])
var contentType string
if mailType == "text/html" {
contentType = "Content-Type: " + mailType + ";charset=utf-8"
} else {
contentType = "Content-Type: text/plain" + ";charset=utf-8"
}
sendTo := strings.Split(to, ";")
msg := []byte(
"To: " + to + "\r\n" +
"From: " + user + "\r\n" +
"Subject: " + subject + "\r\n" +
contentType + "\r\n\r\n" +
body)
err := smtp.SendMail(host, auth, user, sendTo, msg)
return err
}
func main() {
user := "xxx@163.com"
password := "***" // 使用邮箱的授权码,一般都不会直接使用登录密码
host := "smtp.163.com:25"
to := "yyy@foxmail.com"
subject := "使用 Go 发送邮件"
body := `
<html>
<body>
<h1>Hello world!</h1>
</body>
</html>
`
fmt.Println("sending...")
err := SendMail(user, password, host, to, subject, body, "text/html")
if err != nil {
log.Fatal(err)
}
fmt.Println("has sent successfully!")
}