dev: add email sender

This commit is contained in:
bryan 2025-06-18 18:34:51 +08:00
parent a8e7881a8b
commit 57e6cb33dc
4 changed files with 197 additions and 4 deletions

191
emailsender/emailsender.go Normal file
View File

@ -0,0 +1,191 @@
package emailsender
import (
"net/mail"
"regexp"
"strings"
"time"
"qoobing.com/gomod/email"
"qoobing.com/gomod/email/smtp"
"qoobing.com/gomod/log"
)
// ///////////////////////////////////////
// 发送器接口
// ///////////////////////////////////////
type Sender interface {
Send(to string, name, body string) error
SenderType() string
SenderSupportAddressType() string
}
type Config struct {
Id string `toml:"id"` //id
Type string `toml:"type"` //必选,发送器类型
Smtp string `toml:"smtp" ` //必选SMTP服务器地址含端口
From string `toml:"from"` //必选,发件人
DryRun bool `toml:"dryrun"` //可选,只执行发送流程,但不真实发送
SmtpUser string `toml:"smtpuser"` //可选SMTP授权人地址默认时从from中取
SmtpPass string `toml:"smtppass"` //可选SMTP授权人密码默认时从from中取
SmtpProxy string `toml:"smtpproxy"` //可选SMTP代理地址默认时为空
WhiteList []string `toml:"whitelist"` //可选,发送白名单,默认为空
BlackList []string `toml:"blacklist"` //可选,发送黑名单,默认为空
}
func (cfg Config) GetId() string {
return cfg.Id
}
func (cfg Config) NewSender() (Sender, error) {
return New(cfg), nil
}
type EmailPlainAuthSender struct {
cfg Config
send func(to string, name, body string) error
}
func New(cfg Config) *EmailPlainAuthSender {
var (
arrFrom = strings.Split(cfg.From, "/")
fromName = arrFrom[0]
fromAddr = arrFrom[1]
fromPass = arrFrom[2]
arrSmtp = strings.Split(cfg.Smtp, ":")
smtpAddr = arrSmtp[0]
smtpPort = arrSmtp[1]
smtpUser = cfg.SmtpUser
smtpPass = cfg.SmtpPass
)
if smtpUser == "" {
smtpUser = fromAddr
}
if smtpPass == "" {
smtpPass = fromPass
}
type emailItem struct {
to string
name string
body string
}
var whiteListRegs = []*regexp.Regexp{}
for _, r := range cfg.WhiteList {
whiteListRegs = append(whiteListRegs, regexp.MustCompile(r))
}
var blackListRegs = []*regexp.Regexp{}
for _, r := range cfg.BlackList {
blackListRegs = append(blackListRegs, regexp.MustCompile(r))
}
var addressInWhiteList = func(address string) bool {
for _, reg := range blackListRegs {
if reg.Match([]byte(address)) {
return false
}
}
if len(whiteListRegs) == 0 {
return true
}
for _, reg := range whiteListRegs {
if reg.Match([]byte(address)) {
return true
}
}
return false
}
var emailQueue = make(chan emailItem, 1024)
var emailSendFunc = func(to string, name, body string) (err error) {
eml := emailItem{to, name, body}
emailQueue <- eml
return nil
}
var emailSendFuncInner = func() {
defer func() {
if re := recover(); re != nil {
log.Errorf("email inner sender exit with recover: %v", re)
}
}()
for {
eml, ok := <-emailQueue
if !ok {
log.Infof("emailQueue closed")
return
}
log.Infof("start send email to [%s]", eml.to)
if !addressInWhiteList(eml.to) {
// white-blck-list
log.Infof("send email to [%s] ignored for not in white list.", eml.to)
continue
} else if cfg.DryRun {
// dryrun mode
log.Infof("end send email to [%s]: ignored for dryrun mode.", eml.to)
continue
}
// compose the message
m := email.NewHTMLMessage(eml.name, eml.body)
m.To = []string{eml.to}
m.From = mail.Address{Name: fromName, Address: fromAddr}
// send it to smtp server
auth := smtp.PlainAuth("", smtpUser, smtpPass, smtpAddr)
addr := smtpAddr + ":" + smtpPort
if cfg.SmtpProxy != "" {
addr += "|" + cfg.SmtpProxy
}
if err := email.Send(addr, auth, m); err != nil {
log.Errorf("failed send email to [%s]", err.Error())
log.Infof("to[%v],from[%s]", m.To, m.From)
log.Infof("smtpUser[%s],smtpPass[%s],smtpAddr[%s:%s],smtpProxy[%s]",
smtpUser, secLogPass(smtpPass), smtpAddr, smtpPort, secLogProxy(cfg.SmtpProxy))
continue
}
log.Infof("success send email to [%s]", eml.to)
}
}
go func() {
for {
emailSendFuncInner()
log.Infof("email inner sender exit abnormal, restart it 5s later...")
time.Sleep(5 * time.Second)
}
}()
var sender = &EmailPlainAuthSender{
cfg: cfg,
send: emailSendFunc,
}
return sender
}
func (sender *EmailPlainAuthSender) Send(to string, name, body string) error {
return sender.send(to, name, body)
}
func (sender *EmailPlainAuthSender) SenderType() string {
return "EMAIL" // SENDER_TYPE_EMAIL
}
func (sender *EmailPlainAuthSender) SenderSupportAddressType() string {
return "EMAIL_ADDRESS" // SENDER_SUPPORT_ADDRESS_TYPE_EMAIL_ADDRESS
}
func secLogPass(password string) string {
n := len(password)
if n < 3 {
return "*****"
} else if n < 8 {
return "***" + password[n-2:]
}
return "" + password[:1] + "**" + password[n-2:]
}
func secLogProxy(proxy string) string {
if strings.HasPrefix(proxy, "socks5://") && strings.Contains(proxy, "@") {
arr := strings.Split(proxy, "@")
if len(arr) == 2 {
return arr[1]
}
}
return proxy
}

View File

@ -3,10 +3,12 @@ package email
import ( import (
"strings" "strings"
"testing" "testing"
"qoobing.com/gomod/email"
) )
func TestAttachment(t *testing.T) { func TestAttachment(t *testing.T) {
m := NewMessage("Hi", "this is the body") m := email.NewMessage("Hi", "this is the body")
if err := m.AttachBuffer("test.ics", []byte("test"), false); err != nil { if err := m.AttachBuffer("test.ics", []byte("test"), false); err != nil {
t.Fatal(err) t.Fatal(err)

View File

@ -22,7 +22,7 @@ func main1() {
m.To = []string{toAddr} m.To = []string{toAddr}
// add attachments // add attachments
if err := m.Attach("aaa.txt"); err != nil { if err := m.Attach("attachment.file"); err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -48,7 +48,7 @@ func main2() {
m.To = []string{toAddr} m.To = []string{toAddr}
// add attachments // add attachments
if err := m.Attach("aaa.txt"); err != nil { if err := m.Attach("attachment.file"); err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -75,7 +75,7 @@ func main3() {
m.To = []string{toAddr} m.To = []string{toAddr}
// add attachments // add attachments
if err := m.Attach("aaa.txt"); err != nil { if err := m.Attach("attachment.file"); err != nil {
log.Fatal(err) log.Fatal(err)
} }