- Add Package Pooler For SMTP :

* implement SMTP lib interface
* check number of sent mail in a time lapse before sending new mail
* if time since last reset is over, reset counter
* add feature to call a user function on each counter reset
- Pkg SMTP : Fix race detection & segFault
- Pkg Mailer : add new function to parse all template value with the given map[string]string
This commit is contained in:
Nicolas JUHEL
2021-12-29 16:07:09 +01:00
parent c37dd090b6
commit 561a4c923b
10 changed files with 561 additions and 83 deletions

View File

@@ -39,16 +39,17 @@ const (
MinPkgLDAP = 1000
MinPkgMail = 1100
MinPkgMailer = 1200
MinPkgNetwork = 1300
MinPkgNats = 1400
MinPkgNutsDB = 1500
MinPkgOAuth = 1600
MinPkgAws = 1700
MinPkgRouter = 1800
MinPkgSemaphore = 1900
MinPkgSMTP = 2000
MinPkgStatic = 2100
MinPkgVersion = 2200
MinPkgMailPooler = 1300
MinPkgNetwork = 1400
MinPkgNats = 1500
MinPkgNutsDB = 1600
MinPkgOAuth = 1700
MinPkgAws = 1800
MinPkgRouter = 1900
MinPkgSemaphore = 2000
MinPkgSMTP = 2100
MinPkgStatic = 2200
MinPkgVersion = 2300
MinAvailable = 4000

44
mailPooler/config.go Normal file
View File

@@ -0,0 +1,44 @@
/*
* MIT License
*
* Copyright (c) 2021 Nicolas JUHEL
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
package mailPooler
import (
"time"
liberr "github.com/nabbar/golib/errors"
)
type FuncCaller func() liberr.Error
type Config struct {
Max int `json:"max" yaml:"max" toml:"max" mapstructure:"max"`
Wait time.Duration `json:"wait" yaml:"wait" toml:"wait" mapstructure:"wait"`
_fct FuncCaller
}
func (c *Config) SetFuncCaller(fct FuncCaller) {
c._fct = fct
}

129
mailPooler/counter.go Normal file
View File

@@ -0,0 +1,129 @@
/*
* MIT License
*
* Copyright (c) 2021 Nicolas JUHEL
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
package mailPooler
import (
"context"
"sync"
"time"
liberr "github.com/nabbar/golib/errors"
)
type Counter interface {
Pool(ctx context.Context) liberr.Error
Reset() liberr.Error
Clone() Counter
}
type counter struct {
m sync.Mutex
num int
max int
dur time.Duration
tim time.Time
fct FuncCaller
}
func newCounter(max int, dur time.Duration, fct FuncCaller) Counter {
return &counter{
m: sync.Mutex{},
num: max,
max: max,
dur: dur,
tim: time.Time{},
fct: fct,
}
}
func (c *counter) Pool(ctx context.Context) liberr.Error {
c.m.Lock()
defer c.m.Unlock()
if c.max <= 0 || c.dur <= 0 {
return nil
}
if e := ctx.Err(); e != nil {
return ErrorMailPoolerContext.ErrorParent(e)
}
if c.tim.IsZero() {
c.num = c.max
} else if time.Since(c.tim) > c.dur {
c.num = c.max
c.tim = time.Time{}
}
if c.num > 0 {
c.num--
c.tim = time.Now()
} else {
time.Sleep(c.dur - time.Since(c.tim))
c.num = c.max - 1
c.tim = time.Now()
if e := ctx.Err(); e != nil {
return ErrorMailPoolerContext.ErrorParent(e)
} else if err := c.fct(); err != nil {
return err
}
}
return nil
}
func (c *counter) Reset() liberr.Error {
c.m.Lock()
defer c.m.Unlock()
if c.max <= 0 || c.dur <= 0 {
return nil
}
c.num = c.max
c.tim = time.Time{}
if err := c.fct(); err != nil {
return err
}
return nil
}
func (c *counter) Clone() Counter {
return &counter{
m: sync.Mutex{},
num: c.num,
max: c.max,
dur: c.dur,
tim: time.Time{},
fct: c.fct,
}
}

60
mailPooler/error.go Normal file
View File

@@ -0,0 +1,60 @@
/*
* MIT License
*
* Copyright (c) 2021 Nicolas JUHEL
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
package mailPooler
import "github.com/nabbar/golib/errors"
const (
ErrorParamsEmpty errors.CodeError = iota + errors.MinPkgMailPooler
ErrorMailPooler
ErrorMailPoolerContext
)
var isCodeError = false
func IsCodeError() bool {
return isCodeError
}
func init() {
isCodeError = errors.ExistInMapMessage(ErrorParamsEmpty)
errors.RegisterIdFctMessage(ErrorParamsEmpty, getMessage)
}
func getMessage(code errors.CodeError) (message string) {
switch code {
case errors.UNK_ERROR:
return ""
case ErrorParamsEmpty:
return "given parameters is empty"
case ErrorMailPooler:
return "generic mail pooler error"
case ErrorMailPoolerContext:
return "context has trigger error"
}
return ""
}

125
mailPooler/pooler.go Normal file
View File

@@ -0,0 +1,125 @@
/*
* MIT License
*
* Copyright (c) 2021 Nicolas JUHEL
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
package mailPooler
import (
"context"
"errors"
liberr "github.com/nabbar/golib/errors"
libsmtp "github.com/nabbar/golib/smtp"
"io"
"net/smtp"
)
type Pooler interface {
Reset() liberr.Error
NewPooler() Pooler
libsmtp.SMTP
}
type pooler struct {
s libsmtp.SMTP
c Counter
}
func New(cfg *Config, cli libsmtp.SMTP) Pooler {
if cli == nil {
return &pooler{
s: nil,
c: newCounter(cfg.Max, cfg.Wait, cfg._fct),
}
} else {
return &pooler{
s: cli.Clone(),
c: newCounter(cfg.Max, cfg.Wait, cfg._fct),
}
}
}
func (p *pooler) Reset() liberr.Error {
if p.s == nil {
return ErrorParamsEmpty.ErrorParent(errors.New("smtp client is not define"))
}
if err := p.c.Reset(); err != nil {
return err
}
return nil
}
func (p *pooler) NewPooler() Pooler {
if p.s == nil {
return &pooler{
s: nil,
c: p.c.Clone(),
}
} else {
return &pooler{
s: p.s.Clone(),
c: p.c.Clone(),
}
}
}
func (p *pooler) Send(ctx context.Context, from string, to []string, data io.WriterTo) liberr.Error {
if p.s == nil {
return ErrorParamsEmpty.ErrorParent(errors.New("smtp client is not define"))
}
if err := p.c.Pool(ctx); err != nil {
return err
}
return p.s.Send(ctx, from, to, data)
}
func (p *pooler) Client(ctx context.Context) (*smtp.Client, liberr.Error) {
if p.s == nil {
return nil, ErrorParamsEmpty.ErrorParent(errors.New("smtp client is not define"))
}
return p.s.Client(ctx)
}
func (p *pooler) Close() {
if p.s != nil {
p.s.Close()
}
}
func (p *pooler) Check(ctx context.Context) liberr.Error {
if p.s == nil {
return ErrorParamsEmpty.ErrorParent(errors.New("smtp client is not define"))
}
return p.s.Check(ctx)
}
func (p *pooler) Clone() libsmtp.SMTP {
return p.NewPooler()
}

View File

@@ -61,6 +61,8 @@ type Mailer interface {
SetTroubleText(text string)
GetTroubleText() string
ParseData(data map[string]string)
GenerateHTML() (*bytes.Buffer, liberr.Error)
GeneratePlainText() (*bytes.Buffer, liberr.Error)
}

View File

@@ -27,11 +27,58 @@ package mailer
import (
"bytes"
"strings"
"github.com/matcornic/hermes/v2"
liberr "github.com/nabbar/golib/errors"
)
func (e *email) ParseData(data map[string]string) {
for k, v := range data {
e.p.Copyright = strings.ReplaceAll(e.p.Copyright, k, v)
e.p.Link = strings.ReplaceAll(e.p.Link, k, v)
e.p.Logo = strings.ReplaceAll(e.p.Logo, k, v)
e.p.Name = strings.ReplaceAll(e.p.Name, k, v)
e.p.TroubleText = strings.ReplaceAll(e.p.TroubleText, k, v)
e.b.Greeting = strings.ReplaceAll(e.b.Greeting, k, v)
e.b.Name = strings.ReplaceAll(e.b.Name, k, v)
e.b.Signature = strings.ReplaceAll(e.b.Signature, k, v)
e.b.Title = strings.ReplaceAll(e.b.Title, k, v)
e.b.FreeMarkdown = hermes.Markdown(strings.ReplaceAll(string(e.b.FreeMarkdown), k, v))
for i := range e.b.Intros {
e.b.Intros[i] = strings.ReplaceAll(e.b.Intros[i], k, v)
}
for i := range e.b.Outros {
e.b.Outros[i] = strings.ReplaceAll(e.b.Outros[i], k, v)
}
for i := range e.b.Dictionary {
e.b.Dictionary[i].Key = strings.ReplaceAll(e.b.Dictionary[i].Key, k, v)
e.b.Dictionary[i].Value = strings.ReplaceAll(e.b.Dictionary[i].Value, k, v)
}
for i := range e.b.Table.Data {
for j := range e.b.Table.Data[i] {
e.b.Table.Data[i][j].Key = strings.ReplaceAll(e.b.Table.Data[i][j].Key, k, v)
e.b.Table.Data[i][j].Value = strings.ReplaceAll(e.b.Table.Data[i][j].Value, k, v)
}
}
for i := range e.b.Actions {
e.b.Actions[i].Instructions = strings.ReplaceAll(e.b.Actions[i].Instructions, k, v)
e.b.Actions[i].InviteCode = strings.ReplaceAll(e.b.Actions[i].InviteCode, k, v)
e.b.Actions[i].Button.Link = strings.ReplaceAll(e.b.Actions[i].Button.Link, k, v)
e.b.Actions[i].Button.Text = strings.ReplaceAll(e.b.Actions[i].Button.Text, k, v)
e.b.Actions[i].Button.Color = strings.ReplaceAll(e.b.Actions[i].Button.Color, k, v)
e.b.Actions[i].Button.TextColor = strings.ReplaceAll(e.b.Actions[i].Button.TextColor, k, v)
}
}
}
func (e *email) GenerateHTML() (*bytes.Buffer, liberr.Error) {
return e.generated(e.genHtml)
}

View File

@@ -34,6 +34,7 @@ import (
"net/url"
"strconv"
"strings"
"sync"
"github.com/nabbar/golib/errors"
)
@@ -90,6 +91,7 @@ func NewSMTP(cfg Config, tlsConfig *tls.Config) (SMTP, errors.Error) {
return nil, ErrorParamsEmpty.Error(nil)
} else {
return &smtpClient{
mut: sync.Mutex{},
cfg: cfg,
tls: tlsConfig,
}, nil

View File

@@ -33,11 +33,13 @@ import (
"net"
"net/smtp"
"strings"
"sync"
"github.com/nabbar/golib/errors"
)
type smtpClient struct {
mut sync.Mutex
con net.Conn
cli *smtp.Client
tls *tls.Config
@@ -47,6 +49,7 @@ type smtpClient struct {
// Check Try to initiate SMTP dial and negotiation and try to close connection.
func (s smtpClient) Clone() SMTP {
return &smtpClient{
mut: sync.Mutex{},
con: nil,
cli: nil,
tls: s.tls,
@@ -56,30 +59,22 @@ func (s smtpClient) Clone() SMTP {
// Close Terminate SMTP negotiation client and close connection.
func (s *smtpClient) Close() {
if s.cli != nil {
if e := s.cli.Quit(); e != nil {
_ = s.cli.Close()
}
s.cli = nil
}
if s.con != nil {
_ = s.con.Close()
s.con = nil
}
s.mut.Lock()
defer s.mut.Unlock()
s._close()
}
// Check Try to initiate SMTP dial and negotiation and try to close connection.
func (s *smtpClient) Check(ctx context.Context) errors.Error {
defer s.Close()
s.mut.Lock()
defer func() {
s._close()
s.mut.Unlock()
}()
if s.cli == nil {
if _, e := s.Client(ctx); e != nil {
return e
}
}
if e := s.cli.Noop(); e != nil {
if c, err := s._client(ctx); err != nil {
return err
} else if e := c.Noop(); e != nil {
return ErrorSMTPClientNoop.ErrorParent(e)
}
@@ -88,7 +83,100 @@ func (s *smtpClient) Check(ctx context.Context) errors.Error {
// Client Get SMTP Client interface.
func (s *smtpClient) Client(ctx context.Context) (*smtp.Client, errors.Error) {
if s.cli == nil {
s.mut.Lock()
defer s.mut.Unlock()
return s._client(ctx)
}
// validateLine checks to see if a line has CR or LF as per RFC 5321.
func (s smtpClient) validateLine(line string) errors.Error {
if strings.ContainsAny(line, "\n\r") {
return ErrorSMTPLineCRLF.Error(nil)
}
return nil
}
func (s *smtpClient) Send(ctx context.Context, from string, to []string, data io.WriterTo) errors.Error {
//from smtp.SendMail()
var (
e error
c *smtp.Client
w io.WriteCloser
err errors.Error
)
s.mut.Lock()
defer func() {
if w != nil {
_ = w.Close()
}
//mandatory for SMTP protocol
s._close()
s.mut.Unlock()
}()
if err = s.validateLine(from); err != nil {
return err
}
for _, recp := range to {
if err = s.validateLine(recp); err != nil {
return err
}
}
if c, err = s._client(ctx); err != nil {
return ErrorSMTPClientInit.Error(err)
}
if e = c.Noop(); e != nil {
return ErrorSMTPClientNoop.ErrorParent(e)
}
if e = c.Mail(from); e != nil {
return ErrorSMTPClientMail.ErrorParent(e)
}
for _, addr := range to {
if e = c.Rcpt(addr); e != nil {
return ErrorSMTPClientRcpt.ErrorParent(e)
}
}
if w, e = c.Data(); e != nil {
return ErrorSMTPClientData.ErrorParent(e)
}
if _, e = data.WriteTo(w); e != nil {
return ErrorSMTPClientWrite.ErrorParent(e)
}
return nil
}
func (s *smtpClient) _client(ctx context.Context) (*smtp.Client, errors.Error) {
if s.cli != nil && s.con != nil {
return s.cli, nil
}
if s.con == nil && s.cli != nil {
if e := s.cli.Quit(); e != nil {
_ = s.cli.Close()
}
} else if s.con != nil && s.cli == nil {
_ = s.con.Close()
}
s.cli = nil
s.con = nil
var (
addr = s.cfg.GetHost()
tlsc = s.tls.Clone()
@@ -108,78 +196,26 @@ func (s *smtpClient) Client(ctx context.Context) (*smtp.Client, errors.Error) {
if cli, con, err := s.tryClient(ctx, addr, tlsc); err != nil {
return nil, err
} else if err := s.auth(cli, addr); err != nil {
} else if err = s.auth(cli, addr); err != nil {
return nil, err
} else {
s.Close()
s.con = con
s.cli = cli
}
}
return s.cli, nil
}
// validateLine checks to see if a line has CR or LF as per RFC 5321.
func (s smtpClient) validateLine(line string) errors.Error {
if strings.ContainsAny(line, "\n\r") {
return ErrorSMTPLineCRLF.Error(nil)
func (s *smtpClient) _close() {
if s.cli != nil {
if e := s.cli.Quit(); e != nil {
_ = s.cli.Close()
}
s.cli = nil
}
return nil
}
func (s *smtpClient) Send(ctx context.Context, from string, to []string, data io.WriterTo) errors.Error {
//from smtp.SendMail()
var (
e error
w io.WriteCloser
)
defer func() {
if w != nil {
_ = w.Close()
}
s.Close()
}()
if err := s.validateLine(from); err != nil {
return err
}
for _, recp := range to {
if err := s.validateLine(recp); err != nil {
return err
}
}
if err := s.Check(ctx); err != nil {
return err
}
if _, e = s.Client(ctx); e != nil {
return ErrorSMTPClientInit.ErrorParent(e)
}
if e = s.cli.Mail(from); e != nil {
return ErrorSMTPClientMail.ErrorParent(e)
}
for _, addr := range to {
if e = s.cli.Rcpt(addr); e != nil {
return ErrorSMTPClientRcpt.ErrorParent(e)
}
}
if w, e = s.cli.Data(); e != nil {
return ErrorSMTPClientData.ErrorParent(e)
}
if _, e = data.WriteTo(w); e != nil {
return ErrorSMTPClientWrite.ErrorParent(e)
}
return nil
if s.con != nil {
_ = s.con.Close()
s.con = nil
}
}

View File

@@ -35,15 +35,17 @@ import (
libiot "github.com/nabbar/golib/ioutils"
liblog "github.com/nabbar/golib/logger"
libsnd "github.com/nabbar/golib/mail"
libpool "github.com/nabbar/golib/mailPooler"
libtpl "github.com/nabbar/golib/mailer"
libsem "github.com/nabbar/golib/semaphore"
libsmtp "github.com/nabbar/golib/smtp"
)
const (
CONFIG_SMTP_DSN = "login@email-example.com:password@tcp4(smtp.mail.example.com:25)/starttls?ServerName=mail.domain.com"
CONFIG_EMAIL_FROM = "email@example.com"
CONFIG_EMAIL_TO = "email@example.com"
CONFIG_SUBJECT = "Testing Send Mail"
_ConfigSmtpDSN = "login@email-example.com:password@tcp4(smtp.mail.example.com:25)/starttls?ServerName=mail.domain.com"
_ConfigEmailFrom = "email@example.com"
_ConfigEmailTo = "email@example.com"
_ConfigSubject = "Testing Send Mail"
)
var (
@@ -71,22 +73,36 @@ func init() {
func main() {
var (
cli libsmtp.SMTP
cli libpool.Pooler
err liberr.Error
sem = libsem.NewSemaphoreWithContext(ctx, 0)
)
defer func() {
sem.DeferMain()
cnl()
if cli != nil {
cli.Close()
}
}()
cli = getSmtp()
//cli = getSmtp()
cli = getPool()
err = getSendMail(getTemplate()).SendClose(ctx, cli)
snd := getSendMail(getTemplate())
liblog.FatalLevel.LogErrorCtxf(liblog.InfoLevel, "sending email", err)
// err = getSendMail(getTemplate()).SendClose(ctx, cli)
for i := 0; i < 5; i++ {
_ = sem.NewWorker()
go func() {
defer sem.DeferWorker()
err = snd.Send(ctx, cli)
liblog.FatalLevel.LogErrorCtxf(liblog.InfoLevel, "[sender] sending email", err)
time.Sleep(2 * time.Second)
}()
}
_ = sem.WaitAll()
}
func getTemplate() libtpl.Mailer {
@@ -127,27 +143,43 @@ func getTemplate() libtpl.Mailer {
}
func getSmtp() libsmtp.SMTP {
cfg, err := libsmtp.NewConfig(CONFIG_SMTP_DSN)
liblog.FatalLevel.LogErrorCtxf(liblog.InfoLevel, "smtp config parsing", err)
cfg, err := libsmtp.NewConfig(_ConfigSmtpDSN)
liblog.FatalLevel.LogErrorCtxf(liblog.InfoLevel, "[smtp] config parsing", err)
/* #nosec */
//nolint #nosec
s, err := libsmtp.NewSMTP(cfg, &tls.Config{})
liblog.FatalLevel.LogErrorCtxf(liblog.InfoLevel, "smtp create client", err)
liblog.FatalLevel.LogErrorCtxf(liblog.InfoLevel, "smtp checking working", s.Check(ctx))
liblog.FatalLevel.LogErrorCtxf(liblog.InfoLevel, "[smtp] init", err)
liblog.FatalLevel.LogErrorCtxf(liblog.InfoLevel, "[smtp] checking working", s.Check(ctx))
return s
}
func getPool() libpool.Pooler {
cfg := &libpool.Config{
Max: 2,
Wait: 10 * time.Second,
}
cfg.SetFuncCaller(func() liberr.Error {
liblog.FatalLevel.LogErrorCtxf(liblog.InfoLevel, "[mail pooler] reset counter", nil)
return nil
})
p := libpool.New(cfg, getSmtp())
liblog.FatalLevel.LogErrorCtxf(liblog.InfoLevel, "[mail pooler] init", nil)
return p
}
func getSendMail(tpl libtpl.Mailer) libsnd.Sender {
m := libsnd.New()
m.Email().SetFrom(CONFIG_EMAIL_FROM)
m.Email().SetRecipients(libsnd.RecipientTo, CONFIG_EMAIL_TO)
m.Email().SetFrom(_ConfigEmailFrom)
m.Email().SetRecipients(libsnd.RecipientTo, _ConfigEmailTo)
m.SetCharset("UTF-8")
m.SetPriority(libsnd.PriorityNormal)
m.SetSubject(CONFIG_SUBJECT)
m.SetSubject(_ConfigSubject)
m.SetEncoding(libsnd.EncodingBinary)
m.SetDateTime(time.Now())