一、普通邮件的发送

在golang.org官方页面上,net/smtp模块给出了我们如何直接进行邮件发送和简单的带密码验证的邮件的发送。不过由于不进行tls ssl 安全认证的smtp协议,很容易在网络传输中被抓包获取用户名密码,所以目前各大主流邮箱(QQ、163、gmail)等都不再做为主流(163还在支持),在开始tls加密邮件传输之前,先看下官网的示例(这里用的github上golang项目页的地址):

package main
import (
    "log"
    "net/smtp"
)
func main() {
    // Set up authentication information.
    auth := smtp.PlainAuth("", "user@example.com", "password", "mail.example.com")
    // Connect to the server, authenticate, set the sender and recipient,
    // and send the email all in one step.
    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)
    }
}

二、使用tls加密传输的

使用tls传输的,使用的端口是465,不再是默认的25,其需要使用到crypto/tls模块,具体代码如下:

package main
import (
    "crypto/tls"
    "fmt"
    "log"
    "net"
    "net/smtp"
)
func main() {
    host := "smtp.163.com"
    port := 465
    email := "itybku@163.com"
    password := "password"
    toEmail := "itybku@139.com"
    header := make(map[string]string)
    header["From"] = "test" + ""
    header["To"] = toEmail
    header["Subject"] = "邮件标题"
    header["Content-Type"] = "text/html; charset=UTF-8"
    body := "我是一封电子邮件!golang发出. by:www.361way.com "
    message := ""
    for k, v := range header {
        message += fmt.Sprintf("%s: %s\r\n", k, v)
    }
    message += "\r\n" + body
    auth := smtp.PlainAuth(
        "",
        email,
        password,
        host,
    )
    err := SendMailUsingTLS(
        fmt.Sprintf("%s:%d", host, port),
        auth,
        email,
        []string{toEmail},
        []byte(message),
    )
    if err != nil {
        panic(err)
    }
}
//return a smtp client
func Dial(addr string) (*smtp.Client, error) {
    conn, err := tls.Dial("tcp", addr, nil)
    if err != nil {
        log.Println("Dialing Error:", err)
        return nil, err
    }
    //分解主机端口字符串
    host, _, _ := net.SplitHostPort(addr)
    return smtp.NewClient(conn, host)
}
//参考net/smtp的func SendMail()
//使用net.Dial连接tls(ssl)端口时,smtp.NewClient()会卡住且不提示err
//len(to)>1时,to[1]开始提示是密送
func SendMailUsingTLS(addr string, auth smtp.Auth, from string,
    to []string, msg []byte) (err error) {
    //create smtp client
    c, err := Dial(addr)
    if err != nil {
        log.Println("Create smpt client error:", err)
        return err
    }
    defer c.Close()
    if auth != nil {
        if ok, _ := c.Extension("AUTH"); ok {
            if err = c.Auth(auth); err != nil {
                log.Println("Error during AUTH", err)
                return err
            }
        }
    }
    if err = c.Mail(from); err != nil {
        return err
    }
    for _, addr := range to {
        if err = c.Rcpt(addr); err != nil {
            return err
        }
    }
    w, err := c.Data()
    if err != nil {
        return err
    }
    _, err = w.Write(msg)
    if err != nil {
        return err
    }
    err = w.Close()
    if err != nil {
        return err
    }
    return c.Quit()
}

调用后,邮件发送示例如下:

golang-email

由于上面header里,from里的名称是test,所以上图中可以看出,发件中显示的就是test,就里可以根据自己的需要进行修改。

三、发送html格式的邮件

如果发送的格式,想要比较丰富,就需要使用html格式的body进行发送,具体示例如下:

package main
import (
    "fmt"
    "net/smtp"
    "strings"
)
func SendToMail(user, password, host, to, subject, body, mailtype string) error {
    hp := strings.Split(host, ":")
    auth := smtp.PlainAuth("", user, password, hp[0])
    var content_type string
    if mailtype == "html" {
        content_type = "Content-Type: text/" + mailtype + "; charset=UTF-8"
    } else {
        content_type = "Content-Type: text/plain" + "; charset=UTF-8"
    }
    msg := []byte("To: " + to + "\r\nFrom: " + user + ">\r\nSubject: " + "\r\n" + content_type + "\r\n\r\n" + body)
    send_to := strings.Split(to, ";")
    err := smtp.SendMail(host, auth, user, send_to, msg)
    return err
}
func main() {
    user := "yang**@yun*.com"
    password := "***"
    host := "smtp.exmail.qq.com:25"
    to := "xxxxxx@qq.com"
    subject := "使用Golang发送邮件"
    body := `
        <html>
        <body>
        <h3>
        "Test send to email ,from www.361way.com"
        </h3>
        </body>
        </html>
        `
    fmt.Println("send email")
    err := SendToMail(user, password, host, to, subject, body, "html")
    if err != nil {
        fmt.Println("Send mail error!")
        fmt.Println(err)
    } else {
        fmt.Println("Send mail success!")
    }
}

上面的send_to := strings.Split(to, “;”)是针对多个收件人时做的处理,而msg := []byte 是强制做了格式转换。

四、发送可自定义模板的邮件

一些网站需要通过自动邮件发送时,可以有多个模板可以选择,这里就可以使用”html/template”模块发送格式邮件,该html模板可以自定义修改,要一些变量引用的地方,以类似于jinja2的形式进行引用即可,go代码如下:

package main
import (
    "bytes"
    "fmt"
    "html/template"
    "net/smtp"
)
var auth smtp.Auth
func main() {
    auth = smtp.PlainAuth("", "itybku@gmail.com", "password", "smtp.gmail.com")
    templateData := struct {
        Name string
        URL  string
    }{
        Name: "myblog",
        URL:  "https://www.361way.com",
    }
    r := NewRequest([]string{"junk@junk.com"}, "Hello Junk!", "Hello, World!")
    err := r.ParseTemplate("template.html", templateData)
    if err := r.ParseTemplate("template.html", templateData); err == nil {
        ok, _ := r.SendEmail()
        fmt.Println(ok)
    }
}
//Request struct
type Request struct {
    from    string
    to      []string
    subject string
    body    string
}
func NewRequest(to []string, subject, body string) *Request {
    return &Request{
        to:      to,
        subject: subject,
        body:    body,
    }
}
func (r *Request) SendEmail() (bool, error) {
    mime := "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n"
    subject := "Subject: " + r.subject + "!\n"
    msg := []byte(subject + mime + "\n" + r.body)
    addr := "smtp.gmail.com:587"
    if err := smtp.SendMail(addr, auth, "dhanush@geektrust.in", r.to, msg); err != nil {
        return false, err
    }
    return true, nil
}
func (r *Request) ParseTemplate(templateFileName string, data interface{}) error {
    t, err := template.ParseFiles(templateFileName)
    if err != nil {
        return err
    }
    buf := new(bytes.Buffer)
    if err = t.Execute(buf, data); err != nil {
        return err
    }
    r.body = buf.String()
    return nil
}

对应的模块文件内容如下(template.html):

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
</head>
<body>
<p>
    Hello {{.Name}}
    <a href="{{.URL}}">Confirm you web address</a>
</p>
</body>
</html>

该模块文件使用的就是结构体templateData里的数据。