golang go-simple-mail+fasttemplate+mailhog 发送邮件
一个很简单的需求,就是基于golang 的模版发送邮件,同时为了提高性能,希望复用smtp的连接,以下是
一个基于开源库实现的学习,同时包含了一些使用中问题的说明
依赖的库
为了简化配置以及提高性能,使用了fasttemplate 进行模版处理,go-simple-mail 进行email发送,yaml进行配置管理
Mailhog 作为测试的smtp服务器(方便测试)
环境准备
- go mod
module demoapp
go 1.15
require (
github.com/valyala/fasttemplate v1.2.1
github.com/xhit/go-simple-mail/v2 v2.5.1
gopkg.in/yaml.v2 v2.3.0
)
- 项目结构
├── README.md
├── config
│ └── config.go
├── config.yaml
├── docker-compose.yaml
├── go.mod
├── go.sum
├── main.go
├── notify
│ ├── email.go
│ └── notidy.go
├── pprof
│ ├── README.md
│ ├── app.sh
│ ├── profile001.pb.gz
│ └── trace.log
└── templates
└── email.html
- 代码说明
yaml 配置解析config/config.go
package config
import (
"io/ioutil"
"log"
"gopkg.in/yaml.v2"
)
// Config cmp sync config
type Config struct {
Email struct {
ServerHost string `yaml:"serverhost"`
ServerPort int `yaml:"serverport"`
FromEmail string `yaml:"fromemail"`
FromPasswd string `yaml:"from_passwd"`
} `yaml:"email"`
Template struct {
EmailTemplate string `yaml:"email"`
} `yaml:"template"`
}
// Default config path is local with name config.yaml
// New get sync config
func New() Config {
config := Config{}
bytes, err := ioutil.ReadFile("config.yaml")
log.Printf("%s", bytes)
if err != nil {
log.Fatalln("read config error: ", err.Error())
}
err = yaml.Unmarshal(bytes, &config)
if err != nil {
log.Fatalln("Unmarshal config error: ", err.Error())
}
return config
}
email.go 核心发送
package notify
import (
"demoapp/config"
"io/ioutil"
"log"
"time"
"github.com/valyala/fasttemplate"
mail "github.com/xhit/go-simple-mail/v2"
)
// EmailNotidy is a email notify
type EmailNotidy struct {
config config.Config
smtpClient *mail.SMTPClient
templateCache map[string]string
}
// NewEailNotidy NewEailNotidy instance
func NewEailNotidy() *EmailNotidy {
config := config.New()
server := mail.NewSMTPClient()
// SMTP Server
server.Host = config.Email.ServerHost
server.Port = config.Email.ServerPort
server.Username = config.Email.FromEmail
server.Password = config.Email.FromPasswd
server.Encryption = mail.EncryptionNone
// Since v2.3.0 you can specified authentication type:
// - PLAIN (default)
// - LOGIN
// - CRAM-MD5
server.Authentication = mail.AuthPlain
// Variable to keep alive connection
// 实现smtp 连接的复用,注意有坑
server.KeepAlive = true
server.ConnectTimeout = 10 * time.Second
server.SendTimeout = 10 * time.Second
smtpClient, err := server.Connect()
if err != nil {
log.Fatalf("init mail instance error:%s", err.Error())
}
bytes, err := ioutil.ReadFile(config.Template.EmailTemplate)
if err != nil {
log.Fatalf("init mail instance error:%s", err.Error())
}
return &EmailNotidy{
config: config,
smtpClient: smtpClient,
templateCache: map[string]string{
config.Template.EmailTemplate: string(bytes),
},
}
}
// Send Send
func (e *EmailNotidy) Send(to string, subject string, datafiles map[string]interface{}) error {
// 使用fasttemplate 进行内容替换处理,使用了cache
t := fasttemplate.New(e.templateCache[e.config.Template.EmailTemplate], "{{", "}}")
htmlBody := t.ExecuteString(datafiles)
email := mail.NewMSG()
from := e.config.Email.FromEmail
email.SetFrom(from).
AddTo(to).
AddCc([]string{"dalongdemo@qq.com"}
SetSubject(subject)
email.SetBody(mail.TextHTML, htmlBody)
err := email.Send(e.smtpClient)
if err != nil {
return err
}
return nil
}
main.go 入口
package main
import (
"demoapp/notify"
"log"
"net/http"
_ "net/http/pprof"
"sync"
)
func main() {
emailnotidy := notify.NewEailNotidy()
// not working tcp out of order
// 不能工作,因为复用连接,发送的数据包不一致
http.HandleFunc("/send", func(res http.ResponseWriter, req *http.Request) {
res.Write([]byte("send email"))
wg := sync.WaitGroup{}
wg.Add(2)
for i := 0; i < 2; i++ {
go func(wg *sync.WaitGroup) {
defer wg.Done()
err := emailnotidy.Send("dalong@qq.com", "demoapp", map[string]interface{}{
"content": "dalongdemoapp",
})
if err != nil {
log.Println("err:", err.Error())
}
}(&wg)
}
wg.Wait()
})
// 推荐使用次方法进行连接复用以及多人发送
http.HandleFunc("/send2", func(res http.ResponseWriter, req *http.Request) {
res.Write([]byte("send email"))
for _, to := range []string{
"to1@example1.com",
"to3@example2.com",
"to4@example3.com",
} {
err := emailnotidy.Send(to, "demoapp", map[string]interface{}{
"content": "dalongdemoapp",
})
if err != nil {
log.Println("err:", err.Error())
}
}
})
http.ListenAndServe(":9090", nil)
}
运行效果
- 命令
go run main.go
- 效果
可以复用的发送 http://localhost:9090/send2
异常的send smtp 请求日志
说明
能复用连接,肯定很不错,但是也得慎用,同时碰到问题应该多看日志,分析下原因,对于网络的应用,抓包也是一个很不错的选择(如果需要到那一步的时候)
参考资料
https://github.com/rongfengliang/golang-email-learning
https://github.com/xhit/go-simple-mail
https://github.com/mailhog/MailHog
https://github.com/valyala/fasttemplate
https://github.com/go-yaml/yaml