mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-29 22:06:38 +08:00
162 lines
4.1 KiB
Go
162 lines
4.1 KiB
Go
// Copyright 2026 Arctel.net
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package mail
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/tls"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
gomail "github.com/wneessen/go-mail"
|
|
golog "github.com/wneessen/go-mail/log"
|
|
)
|
|
|
|
const (
|
|
smtpSSLPort = 465 // SMTP SSL 端口
|
|
smtpDialTimeout = 5 * time.Second // SMTP 连接超时
|
|
)
|
|
|
|
// Config represents SMTP mail configuration
|
|
type Config struct {
|
|
Host string
|
|
Port int
|
|
Username string
|
|
Password string
|
|
FromName string // 可选发件人显示名称
|
|
InsecureSkipVerify bool // 是否跳过证书校验 (默认跳过以兼容自签名证书)
|
|
}
|
|
|
|
// Option modifies internal mail send options
|
|
type Option func(*clientOptions)
|
|
|
|
type clientOptions struct {
|
|
debugLogger golog.Logger
|
|
}
|
|
|
|
func withLogger(l golog.Logger) Option {
|
|
return func(co *clientOptions) {
|
|
co.debugLogger = l
|
|
}
|
|
}
|
|
|
|
// SendMail sends an HTML email using the provided config and message details
|
|
func SendMail(ctx context.Context, cfg Config, to, subject, body string) error {
|
|
return SendMailHTML(ctx, cfg, to, subject, body)
|
|
}
|
|
|
|
// SendMailHTML sends an HTML format email
|
|
func SendMailHTML(ctx context.Context, cfg Config, to, subject, body string) error {
|
|
return send(ctx, cfg, to, subject, body)
|
|
}
|
|
|
|
// SendMailWithLog sends a test email and records a detailed SMTP connection log
|
|
func SendMailWithLog(ctx context.Context, cfg Config, to, subject, body string) (string, error) {
|
|
var logBuf bytes.Buffer
|
|
logger := &bufferLogger{buf: &logBuf}
|
|
|
|
fmt.Fprintf(&logBuf, "[System] Connecting to %s:%d...\n", cfg.Host, cfg.Port)
|
|
|
|
err := send(ctx, cfg, to, subject, body, withLogger(logger))
|
|
if err != nil {
|
|
fmt.Fprintf(&logBuf, "[Error] Mail sending failed: %v\n", err)
|
|
return logBuf.String(), err
|
|
}
|
|
|
|
fmt.Fprintf(&logBuf, "[System] Mail sent successfully!\n")
|
|
return logBuf.String(), nil
|
|
}
|
|
|
|
func send(ctx context.Context, cfg Config, to, subject, body string, opts ...Option) error {
|
|
var co clientOptions
|
|
for _, opt := range opts {
|
|
opt(&co)
|
|
}
|
|
|
|
msg := gomail.NewMsg()
|
|
var err error
|
|
if cfg.FromName != "" {
|
|
err = msg.FromFormat(cfg.FromName, cfg.Username)
|
|
} else {
|
|
err = msg.From(cfg.Username)
|
|
}
|
|
if err != nil {
|
|
return fmt.Errorf(errCreateMailMessageFailed, err)
|
|
}
|
|
|
|
if err = msg.To(to); err != nil {
|
|
return fmt.Errorf(errCreateMailMessageFailed, err)
|
|
}
|
|
|
|
msg.Subject(subject)
|
|
msg.SetBodyString(gomail.TypeTextHTML, body)
|
|
|
|
clientOpts := []gomail.Option{
|
|
gomail.WithPort(cfg.Port),
|
|
gomail.WithTimeout(smtpDialTimeout),
|
|
}
|
|
|
|
tlsConfig := &tls.Config{
|
|
InsecureSkipVerify: true, //nolint:gosec // SMTP servers might use self-signed certificates
|
|
ServerName: cfg.Host,
|
|
}
|
|
clientOpts = append(clientOpts, gomail.WithTLSConfig(tlsConfig))
|
|
|
|
if cfg.Port == smtpSSLPort {
|
|
clientOpts = append(clientOpts, gomail.WithSSL())
|
|
} else {
|
|
clientOpts = append(clientOpts, gomail.WithTLSPolicy(gomail.TLSOpportunistic))
|
|
}
|
|
|
|
if cfg.Username != "" && cfg.Password != "" {
|
|
clientOpts = append(clientOpts,
|
|
gomail.WithSMTPAuth(gomail.SMTPAuthPlain),
|
|
gomail.WithUsername(cfg.Username),
|
|
gomail.WithPassword(cfg.Password),
|
|
)
|
|
}
|
|
|
|
if co.debugLogger != nil {
|
|
clientOpts = append(clientOpts, gomail.WithDebugLog(), gomail.WithLogger(co.debugLogger))
|
|
}
|
|
|
|
client, err := gomail.NewClient(cfg.Host, clientOpts...)
|
|
if err != nil {
|
|
return fmt.Errorf(errCreateMailClientFailed, err)
|
|
}
|
|
defer func() { _ = client.Close() }()
|
|
|
|
if err = client.DialAndSendWithContext(ctx, msg); err != nil {
|
|
return fmt.Errorf(errSendMailFailed, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type bufferLogger struct {
|
|
buf *bytes.Buffer
|
|
}
|
|
|
|
func (l *bufferLogger) log(level string, entry golog.Log) {
|
|
msg := fmt.Sprintf(entry.Format, entry.Messages...)
|
|
msg = strings.TrimRight(msg, "\r\n")
|
|
var dir string
|
|
switch entry.Direction {
|
|
case golog.DirClientToServer:
|
|
dir = "C"
|
|
case golog.DirServerToClient:
|
|
dir = "S"
|
|
default:
|
|
dir = level
|
|
}
|
|
fmt.Fprintf(l.buf, "[%s] %s\n", dir, msg)
|
|
}
|
|
|
|
func (l *bufferLogger) Debugf(e golog.Log) { l.log("Debug", e) }
|
|
func (l *bufferLogger) Infof(e golog.Log) { l.log("Info", e) }
|
|
func (l *bufferLogger) Warnf(e golog.Log) { l.log("Warn", e) }
|
|
func (l *bufferLogger) Errorf(e golog.Log) { l.log("Error", e) }
|