1. 程式人生 > 實用技巧 >golang go-simple-mail+fasttemplate+mailhog 傳送郵件

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{"[email protected]"}...).
    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("[email protected]", "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{
      "[email protected]",
      "[email protected]",
      "[email protected]",
     } {
      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