如何使用smtp包在Golang中发送电子邮件

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教程。

赞(0) 打赏

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏