[TOC]
0x00 前言简述 描述: 在某些系统中往往需要实时的监控应用的健康信息以及关键操作信息的发送,若要使用Go语言实现上述报警信息的发送,通常会在企业中使用邮件的形式或者Webhook钩子的形式进行预警(例如,钉钉、企业微信)推荐,当然你也可以使用openwechat 项目实现个人微信推送以及go-cqhttp 项目实现QQ推送,不论你使用何种方式实现信息发送都是可以请根据自身的实际情况进行选择。
0x01 常用模块 Gomail 模块 - 邮件发送模块 描述: Gomail是一个简单高效的发送电子邮件go语言的模块包,其使用SMTP服务器发送电子邮件含附件,请注意它需要 Go 1.2 或更高版本。 项目地址: https://github.com/go-gomail/gomail 文档地址: https://pkg.go.dev/gopkg.in/gomail.v2 支持特性:
附件发送
嵌入的图像
网页和文本模板
特殊字符的自动编码
SSL 和 TLS
使用相同的 SMTP 连接发送多封电子邮件
示例演示 示例1.gomail快速上手实践 描述: 使用 gopkg.in/gomail.v2
发送邮件一般有 9 个步骤,分别如下
[TOC]
0x00 前言简述 描述: 在某些系统中往往需要实时的监控应用的健康信息以及关键操作信息的发送,若要使用Go语言实现上述报警信息的发送,通常会在企业中使用邮件的形式或者Webhook钩子的形式进行预警(例如,钉钉、企业微信)推荐,当然你也可以使用openwechat 项目实现个人微信推送以及go-cqhttp 项目实现QQ推送,不论你使用何种方式实现信息发送都是可以请根据自身的实际情况进行选择。
0x01 常用模块 Gomail 模块 - 邮件发送模块 描述: Gomail是一个简单高效的发送电子邮件go语言的模块包,其使用SMTP服务器发送电子邮件含附件,请注意它需要 Go 1.2 或更高版本。 项目地址: https://github.com/go-gomail/gomail 文档地址: https://pkg.go.dev/gopkg.in/gomail.v2 支持特性:
附件发送
嵌入的图像
网页和文本模板
特殊字符的自动编码
SSL 和 TLS
使用相同的 SMTP 连接发送多封电子邮件
示例演示 示例1.gomail快速上手实践 描述: 使用 gopkg.in/gomail.v2
发送邮件一般有 9 个步骤,分别如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package mainimport ( mailer "gopkg.in/gomail.v2" ) func main () { msg := mailer.NewMessage() msg.SetHeader("From" , "from_address@example.com" ) msg.SetHeader("To" , "to_address@example.com" ) msg.SetHeader("Cc" , "cc_address@example.com" ) msg.SetHeader("Subject" , "Go 语言发送邮件" ) msg.SetBody("text/html" , "<h3>欢迎来到简单教程</h3><p>简单教程,简单编程</p>" ) dialer := mailer.NewDialer("smtp.exmail.qq.com" , 465 , "腾讯云企业邮箱账号" , "腾讯云企业邮箱授权码" ) if err := dialer.DialAndSend(msg); err != nil { panic (err) } }
示例2.gomail邮件消息群发 上述代码中的 DialAndSend()
方法是一次性的,也就是连接邮件服务器,发送邮件,然后关闭连接。 如果你有很多封邮件要发,那么正确的方法应该是 创建一个连接器然后不断的发,可以参考如下代码。
代码示例1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package mainimport ( "gopkg.in/gomail.v2" "log" ) type Address struct { Name, Address string } func main () { dialer := gomail.NewDialer("smtp.exmail.qq.com" , 465 , "腾讯云企业邮箱账号" , "腾讯云企业邮箱授权码" ) sock, err := dialer.Dial() if err != nil { panic (err) } list := []Address{ Address{Name: "管理员" , Address: "master@weiyigeek.top" }, Address{Name: "测试人员" , Address: "test@weiyigeek.top" }, } msg := gomail.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 := gomail.Send(sock, msg); err != nil { log.Printf("Could not send email to %q: %v" , r.Address, err) } msg.Reset() } }
<>br
示例3.自定义方法封装Gomail实现发送文本、HTML以及附件 代码示例:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 type Emailer struct { host, user, pass string port int d *gomail.Dialer m *gomail.Message bodyBuffer bytes.Buffer } var Email *Emailerfunc NewEmailer (host string , port int , user, pass string ) *Emailer { Email = &Emailer{ host: host, port: port, user: user, pass: pass, d: gomail.NewDialer(host, port, user, pass), m: gomail.NewMessage(), } return Email } func (e *Emailer) Setup () *gomail .Dialer { if e.d == nil { emailhost := setting.Conf.Get("email.host" ).(string ) emailport := setting.Conf.Get("email.port" ).(int ) emailuser := fmt.Sprintf("%s" , setting.Conf.Get("email.user" )) emailpass := fmt.Sprintf("%s" , setting.Conf.Get("email.pass" )) var s gomail.SendCloser defer func () { if err := recover (); err != nil { fmt.Println("gomail.NewDialer connect failed!" ) s.Close() panic (err) } }() e.d = gomail.NewDialer(emailhost, emailport, emailuser, emailpass) return e.d } return e.d } func (e *Emailer) MailDialAndSend (msg *gomail.Message) (string , error) { if err := e.d.DialAndSend(msg); err != nil { return "邮件发送失败!" , err } else { msg.Reset() return "邮件发送成功!" , nil } } func (e *Emailer) SendMsg (to []string , cc, subject, body, sendtype string ) (string , error) { e.Setup() e.m = gomail.NewMessage() e.m.SetHeader("From" , setting.Conf.Get("email.user" ).(string )) e.m.SetHeader("To" , to...) if cc != "" { chaosong := strings.Split(cc, "," ) e.m.SetAddressHeader("Cc" , chaosong[0 ], chaosong[1 ]) } if subject != "" && body != "" { e.m.SetHeader("Subject" , subject) if sendtype == "text" { e.m.SetBody("text/plain" , body) } else if sendtype == "html" { e.m.SetBody("text/html" , body) } } else { return "邮件 subject 或 body 字段不能为空" , errors.New("Email Message The subject or body field cannot be empty" ) } if err := e.d.DialAndSend(e.m); err != nil { return "邮件发送失败!" , err } else { e.m.Reset() return "邮件发送成功!" , err } } func (e *Emailer) SendAttachMsg (to []string , cc, subject, body, file, filename string ) (string , error) { e.Setup() e.m = gomail.NewMessage() e.m.SetHeader("From" , setting.Conf.Get("email.user" ).(string )) e.m.SetHeader("To" , to...) if cc != "" { chaosong := strings.Split(cc, "," ) e.m.SetAddressHeader("Cc" , chaosong[0 ], chaosong[1 ]) } e.m.SetHeader("Subject" , subject) e.m.SetBody("text/html" , body) if file != "" && len (file) > 0 { e.m.Attach(file, gomail.Rename(filename), gomail.SetHeader(map [string ][]string { "Content-Disposition" : { fmt.Sprintf(`attachment; filename="%s"` , mime.QEncoding.Encode("UTF-8" , filename)), }, }), ) } if err := e.d.DialAndSend(e.m); err != nil { e.m.Reset() return "邮件发送失败!" , err } else { e.m.Reset() return "邮件发送成功!" , nil } } func main () { obj := NewEmailer("smtp.exmail.qq.com" , 465 , "腾讯云企业邮箱账号" , "腾讯云企业邮箱授权码" ) mailTo := []String{ "master@weiyigeek.top" , "weiyigeek@qq.com" , } obj.SendMsg(mailTo,"chaosong@weiyigeek.top" ,"文本邮件示例" ,"来自【全栈工程师修炼指南】公众号的消息" ,"text" ) obj.EmailMsg(mailTo,"chaosong@weiyigeek.top" ,"网页邮件示例" ,"来自<b>【全栈工程师修炼指南】</b>公众号的消息" ,"html" ) obj.SendAttachMsg(mailTo,"chaosong@weiyigeek.top" ,"网页邮件示例" ,"来自<b>【全栈工程师修炼指南】</b>公众号的消息" ,"/app/devops/res/tmp/weiyigeek.docx" ,"公众号文档.docx" ) }
代码执行效果:
weiyigeek.top-使用Gomail发送文本消息图
weiyigeek.top-使用Gomail发送HTML消息带附件图
示例4.Gomail发送HTML注册找回验证码 描述: 我们需要在上节代码文件中加入如下函数。
HTML模板文件:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <div style ="background:#fff" > <table width ="100%" border ="0" cellspacing ="0" cellpadding ="0" > <thead > <tr > <td valign ="middle" style ="padding-left:30px;background-color:#415A94;color:#fff;padding:20px 40px;font-size: 21px;" > {{.SiteName}}</td > </tr > </thead > <tbody > <tr style ="padding:40px 40px 0 40px;display:table-cell" > <td style ="font-size:24px;line-height:1.5;color:#000;margin-top:40px" > 邮箱验证码</td > </tr > <tr > <td style ="font-size:14px;color:#333;padding:24px 40px 0 40px" > 尊敬的 <b > {{.UserName}} </b > 用户您好! <br > <br > 您的验证码是:<b > {{.UserCode}} </b > ,请在 <b > {{.UserCodeTime}} 分钟内</b > 进行验证, 过期将失效! <br > 如果该验证码不为您本人申请,请无视。 </td > </tr > <tr style ="padding:40px;display:table-cell" > </tr > </tbody > </table > </div > <div > <table width ="100%" border ="0" cellspacing ="0" cellpadding ="0" > <tbody > <tr > <td style ="padding:20px 40px;font-size:12px;color:#999;line-height:20px;background:#f7f7f7" > <a href ="{{.SiteAddr}}" style ="font-size:14px;color:#929292" rel ="noopener" target ="_blank" > 返回 {{.SiteName}} </a > </td > </tr > </tbody > </table > </div >
关键函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 func (e *Emailer) TemplateVerifiy (tpl *template.Template, body ...string ) string { e.bodyBuffer.Reset() tpl.Execute(&e.bodyBuffer, struct { SiteName string UserName string UserCode string UserCodeTime string SiteAddr string }{ SiteName: body[0 ], UserName: body[1 ], UserCode: body[2 ], UserCodeTime: body[3 ], SiteAddr: body[4 ], }) return e.bodyBuffer.String() } func (e *Emailer) reflectFunc (tpl *template.Template, tplname string , body ...string ) string { var t Emailer ref := reflect.ValueOf(&t) refFunc := ref.MethodByName(tplname) fmt.Printf("Kind : %s, Type : %s\n" , refFunc.Kind(), refFunc.Type()) refVal := make ([]reflect.Value, 0 ) refVal = append (refVal, reflect.ValueOf(tpl)) for _, v := range body { fmt.Println(v) refVal = append (refVal, reflect.ValueOf(v)) } fmt.Println(refVal) res := refFunc.Call(refVal) return res[0 ].String() } func (e *Emailer) TemplateMsg (to []string , tplname, subject string , bodys ...string ) (string , error) { e.Setup() e.m = gomail.NewMessage() e.m.SetHeader("From" , setting.Conf.Get("email.user" ).(string ), "全栈工程师" ) e.m.SetHeader("To" , to...) e.m.SetHeader("Subject" , subject) tpl, err := template.ParseFiles(fmt.Sprintf("./template/email/%s.html" , tplname)) if err != nil { return "传入的模板文件名称有误" , err } body := e.reflectFunc(tpl, tplname, bodys...) e.m.SetBody("text/html" , body) res, err := e.MailDialAndSend(e.m) return res, err }
执行效果:
weiyigeek.top-使用Gomail发送HTML模板信息图
示例 5.正式环境中我们一般会用管道 channel 来创建一个邮件发送服务. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 package mainimport ( mailer "gopkg.in/gomail.v2" "log" "time" ) type Address struct { Name, Address string } func main () { list := []Address{ Address{Name: "管理员" , Address: "master@weiyigeek.top" }, Address{Name: "测试人员" , Address: "test@weiyigeek.top" }, } ch := make (chan *mailer.Message) go func () { dialer := mailer.NewDialer("smtp.exmail.qq.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()) } 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) }
入坑出坑 问题1.使用Gomail时报x509:由未知颁发机构签名的证书
解决办法. 描述: 如果收到此错误,则表示SMTP服务器使用的证书不是被运行 Gomail 的客户端视为有效。 解决办法: 作为快速解决方法,您可以使用绕过对服务器的证书链和主机名的验证SetTLSConfig
, 但请注意这是不安全的其不应在生产中使用。1 2 3 4 5 6 7 8 9 10 11 12 13 14 package main import ( "crypto/tls" "gopkg.in/gomail.v2" ) func main () { d := gomail.NewDialer("smtp.example.com" , 587, "user" , "123456" ) d.TLSConfig = &tls.Config{InsecureSkipVerify: true } // Send emails using d. ...... }
问题2.gomail 执行时报 could not send email 1: 550 Error: content rejected.http://mail.qq.com/zh_CN/help/content/rejectedmail.html
错误解决办法。 描述: 上述QQ邮箱返回的退信信息,QQ邮箱认为您的邮件内容涉及群发的垃圾邮件而拒绝,由于发信smtp服务器中主题或者内容包含关键敏感字已经频繁发送邮件也会触发,则报如上所示信息。 解决办法: 删除敏感关键字,重新发送即可,尽量不同时且减少推销性文字,否则将被认为是垃圾邮件。1 2 3 4 5 联系收件方将您发信地址加入白名单; 更改邮件的主题和内容,避免出现广告和推广之类的字眼; 一次发送的收件人数不要超过100人; 如果还是退信,建议联系收件方管理员核实具体的退信原因。