Go 电子邮件
最后修改时间 2024 年 4 月 11 日
在本文中,我们将展示如何使用 Golang 的 smtp 包发送电子邮件。在我们的示例中,我们使用 Mailtrap 服务。
SMTP
简单邮件传输协议 (SMTP) 是一种用于电子邮件传输的互联网标准通信协议。邮件服务器和客户端使用 SMTP 发送和接收邮件。
Go smtp
smtp 包实现了简单邮件传输协议。它还支持其他扩展。
注意: Gmail 不适合测试应用程序。我们应该使用 Mailtrap 或 Mailgun 等在线服务,或者使用网络托管公司提供的 SMTP 服务器。
SendMail 函数
SendMail 函数是一个用于发送电子邮件的高级函数。
func SendMail(addr string, a Auth, from string, to []string, msg []byte) error
它连接到 addr 的服务器,如果可能则切换到 TLS,如果可能则使用可选机制 a 进行身份验证,然后从地址 from 发送电子邮件到地址 to,消息为 msg。
msg 参数应该是符合 RFC 822 格式的电子邮件;这样的电子邮件以标题、一个空行,然后是消息正文开头。msg 的行应该以 CRLF 字符终止。
Go 电子邮件简单示例
下面是一个简单的电子邮件示例。
package main
import (
"fmt"
"log"
"net/smtp"
)
func main() {
from := "john.doe@example.com"
user := "9c1d45eaf7af5b"
password := "ad62926fa75d0f"
to := []string{
"roger.roe@example.com",
}
addr := "smtp.mailtrap.io:2525"
host := "smtp.mailtrap.io"
msg := []byte("From: john.doe@example.com\r\n" +
"To: roger.roe@example.com\r\n" +
"Subject: Test mail\r\n\r\n" +
"Email body\r\n")
auth := smtp.PlainAuth("", user, password, host)
err := smtp.SendMail(addr, auth, from, to, msg)
if err != nil {
log.Fatal(err)
}
fmt.Println("Email sent successfully")
}
我们向 Mailtrap 服务发送一封简单的电子邮件。
import (
"fmt"
"log"
"net/smtp"
)
我们导入 net/smtp 包。
from := "john.doe@example.com"
这是电子邮件发件人。
user := "9c1d45eaf7af5b" password := "ad62926fa75d0f"
我们从 Mailtrap 帐户获取用户名和密码。
to := []string{
"roger.roe@example.com",
}
我们将收件人存储在 to 切片中。
addr := "smtp.mailtrap.io:2525" host := "smtp.mailtrap.io"
地址是主机名和端口。Mailtrap 监听端口 2525。
msg := []byte("From: john.doe@example.com\r\n" +
"To: roger.roe@example.com\r\n" +
"Subject: Test mail\r\n\r\n" +
"Email body\r\n")
我们构建电子邮件消息。消息行以 CRLF 字符分隔。
auth := smtp.PlainAuth("", user, password, host)
PlainAuth 函数开始与服务器进行身份验证;它返回一个实现 PLAIN 身份验证机制的身份验证对象。它仅在连接使用 TLS 或连接到 localhost 时才发送凭据。
err := smtp.SendMail(addr, auth, from, to, msg)
电子邮件通过 SendMail 函数发送。我们将地址、身份验证对象、发件人、收件人和消息传递给该函数。
Go smtp HTML 消息
以下示例发送一封正文消息为 HTML 的电子邮件。
package main
import (
"fmt"
"log"
"net/smtp"
"strings"
)
type Mail struct {
Sender string
To []string
Subject string
Body string
}
func main() {
sender := "john.doe@example.com"
to := []string{
"roger.roe@example.com",
}
user := "9c1d45eaf7af5b"
password := "ad62926fa75d0f"
subject := "Simple HTML mail"
body := `<p>An old <b>falcon</b> in the sky.</p>`
request := Mail{
Sender: sender,
To: to,
Subject: subject,
Body: body,
}
addr := "smtp.mailtrap.io:2525"
host := "smtp.mailtrap.io"
msg := BuildMessage(request)
auth := smtp.PlainAuth("", user, password, host)
err := smtp.SendMail(addr, auth, sender, to, []byte(msg))
if err != nil {
log.Fatal(err)
}
fmt.Println("Email sent successfully")
}
func BuildMessage(mail Mail) string {
msg := "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\r\n"
msg += fmt.Sprintf("From: %s\r\n", mail.Sender)
msg += fmt.Sprintf("To: %s\r\n", strings.Join(mail.To, ";"))
msg += fmt.Sprintf("Subject: %s\r\n", mail.Subject)
msg += fmt.Sprintf("\r\n%s\r\n", mail.Body)
return msg
}
在消息正文中,我们使用 HTML 标签。消息的内容类型设置为 text/html。
Go 电子邮件抄送/密送
抄送 (CC) 收件人对所有其他收件人可见,而密送 (BCC) 收件人对任何人均不可见。CC 收件人包含在 to 参数和 CC msg 字段中。发送 BCC 消息是通过在 to 参数中包含电子邮件地址,但不在 msg 标题中包含它来完成的。
package main
import (
"fmt"
"log"
"net/smtp"
"strings"
)
type Mail struct {
Sender string
To []string
Cc []string
Bcc []string
Subject string
Body string
}
func main() {
sender := "john.doe@example.com"
to := []string{
"roger.roe@example.com",
"adam.smith@example.com",
"thomas.wayne@example.com",
"oliver.holmes@example.com",
}
cc := []string{
"adam.smith@example.com",
"thomas.wayne@example.com",
}
// not used
bcc := []string{
"oliver.holmes@example.com",
}
user := "9c1d45eaf7af5b"
password := "ad62926fa75d0f"
subject := "simple testing mail"
body := "email body message"
request := Mail{
Sender: sender,
To: to,
Cc: cc,
Subject: subject,
Body: body,
}
addr := "smtp.mailtrap.io:2525"
host := "smtp.mailtrap.io"
msg := BuildMessage(request)
auth := smtp.PlainAuth("", user, password, host)
err := smtp.SendMail(addr, auth, sender, to, []byte(msg))
if err != nil {
log.Fatal(err)
}
fmt.Println("Emails sent successfully")
}
func BuildMessage(mail Mail) string {
msg := ""
msg += fmt.Sprintf("From: %s\r\n", mail.Sender)
if len(mail.To) > 0 {
msg += fmt.Sprintf("To: %s\r\n", mail.To[0])
}
if len(mail.Cc) > 0 {
msg += fmt.Sprintf("Cc: %s\r\n", strings.Join(mail.Cc, ";"))
}
msg += fmt.Sprintf("Subject: %s\r\n", mail.Subject)
msg += fmt.Sprintf("\r\n%s\r\n", mail.Body)
return msg
}
在示例中,我们向多个收件人发送电子邮件。其中一些是 CC 和 BCC 收件人。
to := []string{
"roger.roe@example.com",
"adam.smith@example.com",
"thomas.wayne@example.com",
"oliver.holmes@example.com",
}
电子邮件发送给所有这些电子邮件。
cc := []string{
"adam.smith@example.com",
"thomas.wayne@example.com",
}
这两封电子邮件将被抄送;也就是说,它们的电子邮件地址将对任何人可见。
if len(mail.To) > 0 {
msg += fmt.Sprintf("To: %s\r\n", mail.To[0])
}
第一个电子邮件地址显示在“收件人”字段中。
if len(mail.Cc) > 0 {
msg += fmt.Sprintf("Cc: %s\r\n", strings.Join(mail.Cc, ";"))
}
在这里我们构建 Cc 消息标题字段。Bcc 电子邮件不包含在消息标题中;因此,它们对他人不可见。
Go 电子邮件附件
在下一个示例中,我们随电子邮件发送附件。电子邮件附件是随电子邮件消息一起发送的计算机文件。
现代电子邮件系统使用 MIME 标准;消息及其所有附件都封装在单个多部分消息中,使用 base64 编码将二进制转换为 7 位 ASCII 文本。
sky blud rock water poem
我们将此文本文件作为附件发送。
package main
import (
"bytes"
"encoding/base64"
"fmt"
"io/ioutil"
"log"
"net/smtp"
"strings"
)
type Mail struct {
Sender string
To []string
Subject string
Body string
}
func main() {
sender := "john.doe@example.com"
to := []string{
"roger.roe@example.com",
}
user := "9c1d45eaf7af5b"
password := "ad62926fa75d0f"
subject := "testing mail with attachment"
body := "email body message"
request := Mail{
Sender: sender,
To: to,
Subject: subject,
Body: body,
}
addr := "smtp.mailtrap.io:2525"
host := "smtp.mailtrap.io"
data := BuildMail(request)
auth := smtp.PlainAuth("", user, password, host)
err := smtp.SendMail(addr, auth, sender, to, data)
if err != nil {
log.Fatal(err)
}
fmt.Println("Email sent successfully")
}
func BuildMail(mail Mail) []byte {
var buf bytes.Buffer
buf.WriteString(fmt.Sprintf("From: %s\r\n", mail.Sender))
buf.WriteString(fmt.Sprintf("To: %s\r\n", strings.Join(mail.To, ";")))
buf.WriteString(fmt.Sprintf("Subject: %s\r\n", mail.Subject))
boundary := "my-boundary-779"
buf.WriteString("MIME-Version: 1.0\r\n")
buf.WriteString(fmt.Sprintf("Content-Type: multipart/mixed; boundary=%s\n",
boundary))
buf.WriteString(fmt.Sprintf("\r\n--%s\r\n", boundary))
buf.WriteString("Content-Type: text/plain; charset=\"utf-8\"\r\n")
buf.WriteString(fmt.Sprintf("\r\n%s", mail.Body))
buf.WriteString(fmt.Sprintf("\r\n--%s\r\n", boundary))
buf.WriteString("Content-Type: text/plain; charset=\"utf-8\"\r\n")
buf.WriteString("Content-Transfer-Encoding: base64\r\n")
buf.WriteString("Content-Disposition: attachment; filename=words.txt\r\n")
buf.WriteString("Content-ID: <words.txt>\r\n\r\n")
data := readFile("words.txt")
b := make([]byte, base64.StdEncoding.EncodedLen(len(data)))
base64.StdEncoding.Encode(b, data)
buf.Write(b)
buf.WriteString(fmt.Sprintf("\r\n--%s", boundary))
buf.WriteString("--")
return buf.Bytes()
}
func readFile(fileName string) []byte {
data, err := ioutil.ReadFile(fileName)
if err != nil {
log.Fatal(err)
}
return data
}
在代码示例中,我们将一个文本文件附加到电子邮件。
boundary := "my-boundary-779"
buf.WriteString("MIME-Version: 1.0\r\n")
buf.WriteString(fmt.Sprintf("Content-Type: multipart/mixed; boundary=%s\n",
boundary))
multipart/mixed MIME 消息由不同数据类型的混合组成。每个正文部分都由一个边界分隔。边界参数是一个文本字符串,用于分隔消息正文的一个部分与另一个部分。
buf.WriteString(fmt.Sprintf("\r\n--%s\r\n", boundary))
buf.WriteString("Content-Type: text/plain; charset=\"utf-8\"\r\n")
buf.WriteString(fmt.Sprintf("\r\n%s", mail.Body))
在这里我们定义正文部分,它是纯文本。
buf.WriteString(fmt.Sprintf("\r\n--%s\r\n", boundary))
buf.WriteString("Content-Type: text/plain; charset=\"utf-8\"\r\n")
buf.WriteString("Content-Transfer-Encoding: base64\r\n")
buf.WriteString("Content-Disposition: attachment; filename=words.txt\r\n")
buf.WriteString("Content-ID: <words.txt>\r\n\r\n")
这是文本文件附件的一部分。内容以 base64 编码。
data := readFile("words.txt")
我们从 words.txt 文件读取数据。
b := make([]byte, base64.StdEncoding.EncodedLen(len(data)))
base64.StdEncoding.Encode(b, data)
buf.Write(b)
buf.WriteString(fmt.Sprintf("\r\n--%s", boundary))
buf.WriteString("--")
我们将 base64 编码的数据写入缓冲区。最后一个边界以两个破折号结束。
From: john.doe@example.com To: roger.roe@example.com Subject: testing mail with attachment MIME-Version: 1.0 Content-Type: multipart/mixed; boundary=my-boundary-779 --my-boundary-779 Content-Type: text/plain; charset="utf-8" email body message --my-boundary-779 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename=words.txt Content-ID: <words.txt> c2t5CmJsdWQKcm9jawp3YXRlcgpwb2VtCg== --my-boundary-779--
原始电子邮件的样子是这样的。
$ echo c2t5CmJsdWQKcm9jawp3YXRlcgpwb2VtCg== | base64 -d sky blud rock water poem
我们可以使用 base64 命令解码附件。
Go 电子邮件模板
在以下示例中,我们使用电子邮件模板为多个用户生成电子邮件。
$ mkdir template $ cd template $ go mod init com/zetcode.TemplateEmail $ go get github.com/shopspring/decimal
我们初始化项目并添加外部 github.com/shopspring/decimal 包。
package main
import (
"bytes"
"fmt"
"log"
"net/smtp"
"text/template"
"github.com/shopspring/decimal"
)
type Mail struct {
Sender string
To string
Subject string
Body bytes.Buffer
}
type User struct {
Name string
Email string
Debt decimal.Decimal
}
func main() {
sender := "john.doe@example.com"
var users = []User{
{"Roger Roe", "roger.roe@example.com", decimal.NewFromFloat(890.50)},
{"Peter Smith", "peter.smith@example.com", decimal.NewFromFloat(350)},
{"Lucia Green", "lucia.green@example.com", decimal.NewFromFloat(120.80)},
}
my_user := "9c1d45eaf7af5b"
my_password := "ad62926fa75d0f"
addr := "smtp.mailtrap.io:2525"
host := "smtp.mailtrap.io"
subject := "Amount due"
var template_data = `
Dear {{ .Name }}, your debt amount is ${{ .Debt }}.`
for _, user := range users {
t := template.Must(template.New("template_data").Parse(template_data))
var body bytes.Buffer
err := t.Execute(&body, user)
if err != nil {
log.Fatal(err)
}
request := Mail{
Sender: sender,
To: user.Email,
Subject: subject,
Body: body,
}
msg := BuildMessage(request)
auth := smtp.PlainAuth("", my_user, my_password, host)
err2 := smtp.SendMail(addr, auth, sender, []string{user.Email}, []byte(msg))
if err2 != nil {
log.Fatal(err)
}
}
fmt.Println("Emails sent successfully")
}
func BuildMessage(mail Mail) string {
msg := ""
msg += fmt.Sprintf("From: %s\r\n", mail.Sender)
msg += fmt.Sprintf("To: %s\r\n", mail.To)
msg += fmt.Sprintf("Subject: %s\r\n", mail.Subject)
msg += fmt.Sprintf("\r\n%s\r\n", mail.Body.String())
return msg
}
该示例向多个用户发送电子邮件,提醒他们还款。text/template 包用于创建电子邮件模板。
var users = []User{
{"Roger Roe", "roger.roe@example.com", decimal.NewFromFloat(890.50)},
{"Peter Smith", "peter.smith@example.com", decimal.NewFromFloat(350)},
{"Lucia Green", "lucia.green@example.com", decimal.NewFromFloat(120.80)},
}
这些是借款人。
var template_data = `
Dear {{ .Name }}, your debt amount is ${{ .Debt }}.`
这是模板;它包含一个通用消息,其中 .Name 和 .Debt 占位符被替换为实际值。
for _, user := range users {
t := template.Must(template.New("template_data").Parse(template_data))
var body bytes.Buffer
err := t.Execute(&body, user)
if err != nil {
log.Fatal(err)
}
request := Mail{
Sender: sender,
To: user.Email,
Subject: subject,
Body: body,
}
msg := BuildMessage(request)
auth := smtp.PlainAuth("", my_user, my_password, host)
err2 := smtp.SendMail(addr, auth, sender, []string{user.Email}, []byte(msg))
if err2 != nil {
log.Fatal(err)
}
}
我们遍历借款人,为每个人生成电子邮件消息。Execute 函数将解析后的模板应用于指定的数据对象。生成消息后,使用 SendMail 发送。
来源
在本文中,我们使用 smtp 包在 Go 中处理电子邮件。