# Go发送SMTP邮件
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: "to22@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()
来做心跳检测