Goemail教程展示了如何使用smtp包在Golang中发送电子邮件。在我们的示例中,我们使用Mailtrap服务。
邮件传输协议
简单邮件传输协议(SMTP)是用于电子邮件传输的互联网标准通信协议。邮件服务器和客户端使用SMTP发送和接收邮件消息。
去smtp
smtp
包实现了简单邮件传输协议。它还支持其他扩展。
注意:Gmail不是测试应用程序的理想选择。我们应该使用Mailtrap或Mailgun等在线服务,或使用网络托管公司提供的SMTP服务器。
发送邮件函数
SendMail
函数是发送电子邮件的高级函数。
func SendMail(addr string, a Auth, from string, to []string, msg []byte) error
它连接到位于addr
的服务器,如果可能的话切换到TLS,如果可能的话使用可选机制a进行身份验证,然后从地址from
发送电子邮件到地址到
,带有消息msg
。
msg
参数应该是一个RFC822风格的电子邮件;这样的电子邮件以标题、空行和邮件正文开头。msg的行应该以CRLF字符结束。
$ go version go version go1.18.1 linux/amd64
我们使用Go版本1.18。
去email简单例子
以下是一个简单的电子邮件示例。
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
函数开始与服务器进行身份验证;它返回一个实现普通认证机制的认证对象。它只会在连接使用TLS或连接到本地主机时发送凭据。
err := smtp.SendMail(addr, auth, from, to, msg)
电子邮件消息是用SendMail
函数发送的。我们将地址、身份验证对象、发件人、收件人和消息传递给该函数。
转到smtpHTML消息
以下示例发送一封包含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
。
去电子邮件抄送/密件抄送
抄送(CC)收件人对所有其他收件人可见,而密件抄送(BCC)收件人对任何人都不可见。CC收件人包含在to
参数和CCmsg
字段中。发送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消息头字段。密件抄送电子邮件不包含在邮件标题中;因此,它们对其他人不可见。
去邮件附件
在下一个示例中,我们将在电子邮件中发送一个附件。电子邮件附件是随电子邮件一起发送的计算机文件。
现代电子邮件系统使用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))
多部分/混合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/decimalpackage。
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中处理电子邮件。
列出所有Go教程。