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

说明

能复用连接,肯定很不错,但是也得慎用,同时碰到问题应该多看日志,分析下原因,对于网络的应用,抓包也是一个很不错的选择(如果需要到那一步的时候)

参考资料

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

posted on 2020-11-22 10:48  荣锋亮  阅读(368)  评论(0编辑  收藏  举报

导航