代码地址:https://gitee.com/lymgoforIT/golang-trick/tree/master/30-go-email
一、简介
程序中时常有发送通知的需求。有异常情况了需要通知管理员和负责人,用户下单后可能需要通知订单信息,电商平台、中国移动和联通都有每月账单,这些都可以通过飞书、微信、钉钉、短信或者邮件来推送。之前我们已经介绍过如果发送飞书消息 7. 飞书机器人发送交互式信息,本次我们介绍下如何发送邮件,其实我们平时收到的垃圾邮件大都也是通过这种方式发送的。那么如何在 Go
语言发送邮件?本文我们介绍一下email
库的使用。
二、使用
1、前置准备
首先,你需要在程序中导入库:
import "github.com/jordan-wright/email"
该库文档地址:https://pkg.go.dev/github.com/jordan-wright/email
该库github
地址:https://github.com/darjun/go-daily-lib
可以先贴一下Email
有哪些字段,后面发送邮件就是在设置这些字段的值,然后e.Send(...)
发送
// Email is the type used for email messages
type Email struct {
ReplyTo []string
From string // 发送人
To []string // 接收人,可多个
Bcc []string // 秘密抄送人,可多个
Cc []string // 抄送人,可多个
Subject string // 主题(标题)
Text []byte // Plaintext message (optional) 文本内容,可选
HTML []byte // Html message (optional) HTML内容,可选
Sender string // override From as SMTP envelope sender (optional)
Headers textproto.MIMEHeader
Attachments []*Attachment
ReadReceipt []string
}
进入开发前我们需要做一些工准备工作。我们知道邮箱使用SMTP/POP3/IMAP
等协议从邮件服务器上拉取邮件。邮件并不是直接发送到邮箱的,而是邮箱请求拉取的。 所以,我们需要配置SMTP/POP3/IMAP
服务器。从头搭建固然可行,而且也有现成的开源库,但是比较麻烦。现在一般的邮箱服务商都开放了SMTP/POP3/IMAP
服务器。 我这里拿 QQ 邮箱来举例,使用SMTP服务器。当然,用其他邮箱也可以。
-
首先,登录邮箱;
-
点开顶部的设置,选择POP3/SMTP/IMAP;
-
点击开启
IMAP/SMTP
服务,按照步骤开启即可,有个授权码获取,记住这个授权码,后面有用。
2、简单使用
package main
import (
"github.com/jordan-wright/email"
"log"
"net/smtp"
)
func main() {
e := email.NewEmail()
e.From = "lym <1154041111@qq.com>"
e.To = []string{"15074941111@163.com"}
e.Subject = "【测试】QQ邮箱给163邮箱发送邮件"
e.Text = []byte("测试邮件,收到可以忽略")
err := e.Send("smtp.qq.com:587", smtp.PlainAuth("", "1154041111@qq.com", "yyy", "smtp.qq.com"))
if err != nil {
log.Fatal(err)
}
}
这里为了我的信息安全,我把真实信息都隐藏了。代码中邮箱的后四位我都替换成了1111
,大家可以替换成你的邮箱账号,yyy
替换成上面获取的授权码。
代码步骤比较简单清晰:
- 先调用
NewEmail
创建一封邮件; - 设置
From
发送方,To
接收者,Subject
邮件主题(标题),Text
设置邮件内容; - 然后调用
Send
发送,参数1
是SMTP
服务器的地址,格式为host:port
,参数2
为验证信息,smtp.PlainAuth
函数用于创建一个普通的Auth
(认证)对象,参数为SMTP
服务器要求的身份验证信息。
注意端口号用465如果发送不成功时,换成587
运行程序我的QQ
邮箱将会向我的 163
邮箱发送一封邮件:
有的邮箱会把这种邮件放在垃圾箱中,例如 QQ
。如果收件箱找不到,记得到垃圾箱瞅瞅。
3、抄送
平常我们发邮件的时候可能会抄送给一些人,还有一些人要秘密抄送,即 CC(Carbon Copy)
和 BCC (Blind Carbon Copy)
。 email
我们也可以设置这两个参数:
func testCarbonCopy() {
e := email.NewEmail()
e.From = "lym <1154041111@qq.com>"
e.To = []string{"15074941111@163.com"}
// 主要就是在这里添加了抄送人以及秘密抄送人
e.Cc = []string{"1315381111@qq.com"}
e.Bcc = []string{"1315381111@qq.com"}
e.Subject = "【测试】QQ邮箱发送邮件,并抄送和秘密抄送发送"
e.Text = []byte("测试抄送和密码抄送邮件,收到可以忽略")
err := e.Send("smtp.qq.com:587", smtp.PlainAuth("", "1154041111@qq.com", "yyy", "smtp.qq.com"))
if err != nil {
log.Fatal(err)
}
}
func main() {
testCarbonCopy()
}
同样,代码中后四位我都换成了1
,大家使用时邮箱自己替换后用自己的。
运行程序将会向我的 163
邮件发送一封邮件,同时抄送一封到我另一个 QQ
邮箱:
抄送人也收到了邮件
3、HTML邮件
发送纯文本,邮件不太美观。email
支持发送 HTML
格式的内容。与发送纯文本类似,直接设置对象的HTML
字段:
func testHTMLEmail() {
e := email.NewEmail()
e := email.NewEmail()
e.From = "lym <1154041111@qq.com>"
e.To = []string{"15074941111@163.com"}
e.Cc = []string{"1154041111@qq.com"}
e.Subject = "【测试】发送HTML邮件"
// 主要就是在这里添加HTML字节数组内容
e.HTML = []byte(`
<ul>
<li><a "https://pkg.go.dev/github.com/jordan-wright/email/">Go邮件库github.com/jordan-wright/email文档地址</a></li>
<li><a "https://darjun.github.io/2020/01/10/godailylib/go-flags/">Go邮件库github.com/jordan-wright/email Github地址</a></li>
</ul>
`)
err := e.Send("smtp.qq.com:587", smtp.PlainAuth("", "1154041111@qq.com", "yyy", "smtp.qq.com"))
if err != nil {
log.Fatal(err)
}
}
func main() {
testHTMLEmail()
}
注意, SMTP
服务器检测比较严格,加上 HTML
之后,很容易被识别为垃圾邮件不让发送,这时 抄送
自己就 OK
了。
发送结果:
4、附件
添加附件也很容易,直接调用AttachFile
即可:
func testAttachFile() {
e := email.NewEmail()
e.From = "lym <1154041111@qq.com>"
e.To = []string{"15074941111@163.com"}
e.Subject = "【测试】邮件携带附件"
e.Text = []byte("测试邮件,请查看是否有附件")
// 主要就是在这个位置添加了附件,注意:这里AttachFile是Email结构体的方法而不是字段,所以不是用等于而是括号
e.AttachFile("30-go-email/test.txt")
err := e.Send("smtp.qq.com:587", smtp.PlainAuth("", "1154041111@qq.com", "yyy", "smtp.qq.com"))
if err != nil {
log.Fatal(err)
}
}
func main() {
testAttachFile()
}
发送结果:
5、连接池
实际上每次调用Send
时都会和 SMTP
服务器建立一次连接,如果发送邮件很多很频繁的话可能会有性能问题。email
提供了连接池,可以复用网络连接:
func testEmailPool() {
ch := make(chan *email.Email, 10)
// 创建连接池,这里的参数和Send方法的参数几乎一致,不过多了一个连接池数量的参数
p, err := email.NewPool(
"smtp.qq.com:587",
4,
smtp.PlainAuth("", "1154041111@qq.com", "yyy", "smtp.qq.com"),
)
if err != nil {
log.Fatal("failed to create pool:", err)
}
// 开启四个协程充当消费者,发送邮件
var wg sync.WaitGroup
wg.Add(4)
for i := 0; i < 4; i++ {
go func() {
defer wg.Done()
for e := range ch {
// 这里的Send是连接池对象的方法,第一个参数是Email对象,第二个参数表示超时时间,这个时间内没有发送成功会报错
err := p.Send(e, 10*time.Second)
if err != nil {
fmt.Fprintf(os.Stderr, "email:%v sent error:%v\n", e, err)
}
}
}()
}
for i := 0; i < 10; i++ {
e := email.NewEmail()
e.From = "lym <1154041111@qq.com>"
e.To = []string{"15074941111@163.com"}
e.Subject = fmt.Sprintf("测试连接池:%d", i+1)
e.Text = []byte(fmt.Sprintf("测试连接池:%d", i+1))
ch <- e
}
close(ch) // 发送完毕后,关闭通道
wg.Wait() // 通道关闭后,ch中消费完后,4个消费者会退出协程,从而放行
}
func main() {
testEmailPool()
}
上面程序中,我们创建 4 goroutine
共用一个连接池发送邮件,发送 10
封邮件后程序退出。为了等邮件都发送完成或失败,程序才退出,我们使用了sync.WaitGroup
。
发送结果,邮箱被轰炸了:
由于使用了多个 goroutine
消费同一个channel
,邮件顺序不能保证。