Go 语言提供了 net/smtp
来发送邮件,但是这个 API 有点底层,如果要发送一封带附件的 HTML 邮件自己要写好多东西。
得益于 net/smtp
ABI 的稳定,有一个第三方库 gopkg.in/gomail.v2
封装的还不错,用来发送邮件也简单。
发送单封邮件
使用 gopkg.in/gomail.v2
发送邮件一般有 9 个步骤,分别如下
- 首先构建一个 Message 对象,也就是邮件对象
- 填充发件人 From
- 填充收件人 To
- 填充抄送 Cc
- 设置邮件标题 Subject
- 设置邮件正文
- 如果有需要,可以添加附件
- 实例化一个邮件发送器
- 连接到邮件服务器并发送
package main import ( mailer "gopkg.in/gomail.v2" ) func main() { // 1. 首先构建一个 Message 对象,也就是邮件对象 msg := mailer.NewMessage() // 2. 填充 From,注意第一个字母要大写 msg.SetHeader("From", "from_address@example.com") // 3. 填充 To msg.SetHeader("To", "to_address@example.com") // 4. 如果需要可以填充 cc,也就是抄送 msg.SetHeader("Cc", "cc_address@example.com") // 5. 设置邮件标题 msg.SetHeader("Subject", "Go 语言发送邮件") // 6. 设置要发送的邮件正文 // 第一个参数是类型,第二个参数是内容 // 如果是 html,第一个参数则是 `text/html` msg.SetBody("text/html", "<h3>欢迎来到简单教程</h3><p>简单教程,简单编程</p>") // 7. 添加附件,注意,这个附件是完整路径 //msg.Attach("/Users/yufei/Downloads/1.jpg") // 到此,邮件消息构建完毕 // 8. 创建 smtp 实例 // 如果你的阿里云企业邮箱则是密码,否则一般情况下国内国外使用的都是授权码 dialer := mailer.NewDialer("smtp.mxhichina.com", 465, "阿里云企业邮箱账号", "阿里云企业邮箱密码") // 9. 发送邮件,连接邮件服务器,发送完就关闭 if err := dialer.DialAndSend(msg); err != nil { panic(err) } }
发送多封邮件
DialAndSend()
方法是一次性的,也就是连接邮件服务器,发送邮件,然后关闭连接。
如果你有很多封邮件要发,那么正确的方法应该是 创建一个连接器,然后不断的发,准确的说,就是下面这个例子
package main import ( mailer "gopkg.in/gomail.v2" "log" ) type Address struct { Name, Address string } func main() { // 先连接到邮件服务器 dialer := mailer.NewDialer("smtp.mxhichina.com", 465, "阿里云企业邮箱账号", "阿里云企业邮箱密码") sock, err := dialer.Dial() if err != nil { panic(err) } // 收件人 list := []Address{ Address{Name: "简单教程", Address: "to@example.com"}, Address{Name: "简单教程2", Address: "to2@example.com"}, } // 然后发送多封邮件 msg := mailer.NewMessage() for _, r := range list { msg.SetHeader("From", "from_address@example.com") msg.SetHeader("To", msg.FormatAddress(r.Address, r.Name)) msg.SetHeader("Subject", "Go 语言发送邮件") msg.SetBody("text/html", "<h3>欢迎来到简单教程</h3><p>简单教程,简单编程</p>") if err := mailer.Send(sock, msg); err != nil { log.Printf("Could not send email to %q: %v", r.Address, err) } // 千万不要忘记调用这个 msg.Reset() } }
创建一个服务,用来发送邮件
正式环境中,我们一般会用管道 channel
来创建一个邮件发送服务,也就是类似下面这样
package main import ( mailer "gopkg.in/gomail.v2" "log" "time" ) type Address struct { Name, Address string } func main() { // 收件人 list := []Address{ Address{Name: "简单教程", Address: "to@example.com"}, Address{Name: "简单教程2", Address: "to2@example.com"}, } ch := make(chan *mailer.Message) go func() { dialer := mailer.NewDialer("smtp.mxhichina.com", 465, "阿里云企业邮箱账号", "阿里云企业邮箱密码") var sock mailer.SendCloser var err error var lasted int64 = 0 open := false for { select { case msg, ok := <-ch: log.Printf("%v\n", msg) if !ok { return } if !open { if sock, err = dialer.Dial(); err != nil { panic(err) } open = true } if err := mailer.Send(sock, msg); err != nil { lasted = time.Now().Unix() log.Printf("发送错误:%s\n", err.Error()) } // 如果 30s 没有再发送邮件则关闭 case <-time.Tick(30 * time.Second): if open && time.Now().Unix()- lasted > 30 { log.Printf("30 秒没有任何邮件,直接关闭") if err := sock.Close(); err != nil { panic(err) } open = false } } } }() // 然后发送多封邮件 for _, r := range list { msg := mailer.NewMessage() msg.SetHeader("From", "from_address@example.com") msg.SetHeader("To", msg.FormatAddress(r.Address, r.Name)) msg.SetHeader("Subject", "Go 语言发送邮件") msg.SetBody("text/html", "<h3>欢迎来到简单教程</h3><p>简单教程,简单编程</p>") ch <- msg } time.Sleep(120 * time.Second) // 程序接受后关闭管道 close(ch) }
time.Sleep(120 * time.Second)
用来防止命令行退出close(ch)
一定要在Sleep()
之后调用,否则没有效果- 使用
time.Tick()
来做心跳检测
目前尚无回复