From 53812bf2275850bfe1893c2f29f501012b23d425 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 22 Jan 2026 21:21:16 +0800 Subject: [PATCH] refactor: extract smtp module --- internal/tools/smtp/client.go | 96 +++++++++++++++++++ internal/tools/smtp/config.go | 20 ++++ .../tools/smtp/errhandler.go | 2 +- internal/tools/smtp/message.go | 18 ++++ pkg/core/notifier/providers/email/email.go | 69 +++---------- 5 files changed, 149 insertions(+), 56 deletions(-) create mode 100644 internal/tools/smtp/client.go create mode 100644 internal/tools/smtp/config.go rename pkg/core/notifier/providers/email/error_handlers.go => internal/tools/smtp/errhandler.go (97%) create mode 100644 internal/tools/smtp/message.go diff --git a/internal/tools/smtp/client.go b/internal/tools/smtp/client.go new file mode 100644 index 000000000..1a5a45a3a --- /dev/null +++ b/internal/tools/smtp/client.go @@ -0,0 +1,96 @@ +package smtp + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/wneessen/go-mail" + + xtls "github.com/certimate-go/certimate/pkg/utils/tls" +) + +type Client struct { + cli *mail.Client +} + +func NewClient(config *Config) (*Client, error) { + if config == nil { + return nil, fmt.Errorf("the configuration of SMTP client is nil") + } + + client, err := createSmtpClient(config) + if err != nil { + return nil, err + } + + return &Client{cli: client}, nil +} + +func (c *Client) Close() error { + return c.cli.Close() +} + +func (c *Client) Send(ctx context.Context, msg *Message) error { + if err := c.cli.DialAndSendWithContext(ctx, msg); err != nil { + errShouldBeIgnored := false + + // REF: https://github.com/wneessen/go-mail/issues/463 + var sendErr *mail.SendError + if errors.As(err, &sendErr) { + if sendErr.Reason == mail.ErrSMTPReset { + errShouldBeIgnored = true + } + } + + if !errShouldBeIgnored { + return fmt.Errorf("smtp: %w", err) + } + } + + return nil +} + +func createSmtpClient(config *Config) (*mail.Client, error) { + clientOptions := []mail.Option{ + mail.WithSMTPAuth(mail.SMTPAuthAutoDiscover), + mail.WithUsername(config.Username), + mail.WithPassword(config.Password), + mail.WithTimeout(time.Second * 30), + } + + if config.Port == 0 { + if config.UseSsl { + clientOptions = append(clientOptions, mail.WithPort(mail.DefaultPortSSL)) + } else { + clientOptions = append(clientOptions, mail.WithPort(mail.DefaultPort)) + } + } else { + clientOptions = append(clientOptions, mail.WithPort(config.Port)) + } + + if config.UseSsl { + tlsConfig := xtls.NewCompatibleConfig() + if config.SkipTlsVerify { + tlsConfig.InsecureSkipVerify = true + } else { + tlsConfig.ServerName = config.Host + } + + clientOptions = append(clientOptions, mail.WithSSL()) + clientOptions = append(clientOptions, mail.WithTLSConfig(tlsConfig)) + clientOptions = append(clientOptions, mail.WithTLSPolicy(mail.TLSMandatory)) + } else { + clientOptions = append(clientOptions, mail.WithTLSPolicy(mail.TLSOpportunistic)) + } + + client, err := mail.NewClient(config.Host, clientOptions...) + if err != nil { + return nil, fmt.Errorf("smtp: %w", err) + } + + client.ErrorHandlerRegistry.RegisterHandler("smtp.qq.com", "QUIT", &wQQMailQuitErrorHandler{}) + + return client, nil +} diff --git a/internal/tools/smtp/config.go b/internal/tools/smtp/config.go new file mode 100644 index 000000000..7b66aa458 --- /dev/null +++ b/internal/tools/smtp/config.go @@ -0,0 +1,20 @@ +package smtp + +const ( + defaultPort int = 25 +) + +type Config struct { + Host string + Port int + Username string + Password string + UseSsl bool + SkipTlsVerify bool +} + +func NewDefaultConfig() *Config { + return &Config{ + Port: defaultPort, + } +} diff --git a/pkg/core/notifier/providers/email/error_handlers.go b/internal/tools/smtp/errhandler.go similarity index 97% rename from pkg/core/notifier/providers/email/error_handlers.go rename to internal/tools/smtp/errhandler.go index a431c5f28..1e1a3d165 100644 --- a/pkg/core/notifier/providers/email/error_handlers.go +++ b/internal/tools/smtp/errhandler.go @@ -1,4 +1,4 @@ -package email +package smtp import ( "bytes" diff --git a/internal/tools/smtp/message.go b/internal/tools/smtp/message.go new file mode 100644 index 000000000..a06673cf0 --- /dev/null +++ b/internal/tools/smtp/message.go @@ -0,0 +1,18 @@ +package smtp + +import ( + "github.com/wneessen/go-mail" +) + +type Message = mail.Msg + +func NewMessage() *Message { + return mail.NewMsg() +} + +type MIMEType = mail.ContentType + +const ( + MIMETypeTextHTML MIMEType = mail.TypeTextHTML + MIMETypeTextPlain MIMEType = mail.TypeTextPlain +) diff --git a/pkg/core/notifier/providers/email/email.go b/pkg/core/notifier/providers/email/email.go index 57c470af1..2ee60fc1c 100644 --- a/pkg/core/notifier/providers/email/email.go +++ b/pkg/core/notifier/providers/email/email.go @@ -5,12 +5,9 @@ import ( "errors" "fmt" "log/slog" - "time" - - "github.com/wneessen/go-mail" + "github.com/certimate-go/certimate/internal/tools/smtp" "github.com/certimate-go/certimate/pkg/core/notifier" - xtls "github.com/certimate-go/certimate/pkg/utils/tls" ) type NotifierConfig struct { @@ -62,49 +59,23 @@ func (n *Notifier) SetLogger(logger *slog.Logger) { } func (n *Notifier) Notify(ctx context.Context, subject string, message string) (*notifier.NotifyResult, error) { - clientOptions := []mail.Option{ - mail.WithSMTPAuth(mail.SMTPAuthAutoDiscover), - mail.WithUsername(n.config.Username), - mail.WithPassword(n.config.Password), - mail.WithTimeout(time.Second * 30), - } - - if n.config.SmtpPort == 0 { - if n.config.SmtpTls { - clientOptions = append(clientOptions, mail.WithPort(mail.DefaultPortSSL)) - } else { - clientOptions = append(clientOptions, mail.WithPort(mail.DefaultPort)) - } - } else { - clientOptions = append(clientOptions, mail.WithPort(int(n.config.SmtpPort))) - } - - if n.config.SmtpTls { - tlsConfig := xtls.NewCompatibleConfig() - if n.config.AllowInsecureConnections { - tlsConfig.InsecureSkipVerify = true - } else { - tlsConfig.ServerName = n.config.SmtpHost - } - - clientOptions = append(clientOptions, mail.WithSSL()) - clientOptions = append(clientOptions, mail.WithTLSConfig(tlsConfig)) - clientOptions = append(clientOptions, mail.WithTLSPolicy(mail.TLSMandatory)) - } else { - clientOptions = append(clientOptions, mail.WithTLSPolicy(mail.TLSOpportunistic)) - } - - client, err := mail.NewClient(n.config.SmtpHost, clientOptions...) + clientCfg := smtp.NewDefaultConfig() + clientCfg.Host = n.config.SmtpHost + clientCfg.Port = int(n.config.SmtpPort) + clientCfg.Username = n.config.Username + clientCfg.Password = n.config.Password + clientCfg.UseSsl = n.config.SmtpTls + clientCfg.SkipTlsVerify = n.config.AllowInsecureConnections + client, err := smtp.NewClient(clientCfg) if err != nil { - return nil, fmt.Errorf("failed to create smtp client: %w", err) + return nil, fmt.Errorf("failed to create SMTP client: %w", err) } - client.ErrorHandlerRegistry.RegisterHandler("smtp.qq.com", "QUIT", &wQQMailQuitErrorHandler{}) defer client.Close() - msg := mail.NewMsg() + msg := smtp.NewMessage() msg.Subject(subject) - msg.SetBodyString(mail.TypeTextPlain, message) + msg.SetBodyString(smtp.MIMETypeTextPlain, message) if n.config.SenderName == "" { msg.From(n.config.SenderAddress) } else { @@ -112,20 +83,8 @@ func (n *Notifier) Notify(ctx context.Context, subject string, message string) ( } msg.To(n.config.ReceiverAddress) - if err := client.DialAndSend(msg); err != nil { - errShouldBeIgnored := false - - // REF: https://github.com/wneessen/go-mail/issues/463 - var sendErr *mail.SendError - if errors.As(err, &sendErr) { - if sendErr.Reason == mail.ErrSMTPReset { - errShouldBeIgnored = true - } - } - - if !errShouldBeIgnored { - return nil, fmt.Errorf("failed to send mail: %w", err) - } + if err := client.Send(ctx, msg); err != nil { + return nil, fmt.Errorf("failed to send mail: %w", err) } return ¬ifier.NotifyResult{}, nil