package mail import ( "cmp" "context" "fmt" "log/slog" "github.com/wneessen/go-mail" "github.com/SecurityBrewery/catalyst/app/database/sqlc" "github.com/SecurityBrewery/catalyst/app/settings" ) type Mailer struct { queries *sqlc.Queries } func New(queries *sqlc.Queries) *Mailer { return &Mailer{ queries: queries, } } func (m *Mailer) Send(ctx context.Context, to, subject, plainTextBody, htmlBody string) error { settings, err := settings.Load(ctx, m.queries) if err != nil { return fmt.Errorf("failed to load settings: %w", err) } if !settings.SMTP.Enabled { return fmt.Errorf("SMTP is not enabled in settings") } if settings.SMTP.Host == "" || settings.SMTP.Username == "" || settings.SMTP.Password == "" { return fmt.Errorf("SMTP settings are not configured properly: host, username, and password must be set") } client, err := mailClient(settings) if err != nil { return fmt.Errorf("failed to create mail client: %w", err) } message, err := createMessage(settings, to, subject, plainTextBody, htmlBody) if err != nil { return fmt.Errorf("failed to create mail message: %w", err) } if err := client.DialAndSend(message); err != nil { return fmt.Errorf("failed to deliver mail: %w", err) } slog.InfoContext(ctx, "mail sent successfully", "to", to, "subject", subject) return nil } func createMessage(settings *settings.Settings, to string, subject string, plainTextBody, htmlBody string) (*mail.Msg, error) { message := mail.NewMsg() if err := message.FromFormat(settings.Meta.SenderName, settings.Meta.SenderAddress); err != nil { return nil, fmt.Errorf("failed to set FROM address: %w", err) } if err := message.To(to); err != nil { return nil, fmt.Errorf("failed to set TO address: %w", err) } message.Subject(subject) message.SetBodyString(mail.TypeTextPlain, plainTextBody) if htmlBody != "" { message.SetBodyString(mail.TypeTextHTML, htmlBody) } return message, nil } func mailClient(settings *settings.Settings) (*mail.Client, error) { var authType mail.SMTPAuthType if err := authType.UnmarshalString(cmp.Or(settings.SMTP.AuthMethod, "plain")); err != nil { return nil, fmt.Errorf("failed to parse SMTP auth method: %w", err) } opts := []mail.Option{ mail.WithSMTPAuth(authType), mail.WithUsername(settings.SMTP.Username), mail.WithPassword(settings.SMTP.Password), } if settings.SMTP.Port != 0 { opts = append(opts, mail.WithPort(settings.SMTP.Port)) } if settings.SMTP.TLS { opts = append(opts, mail.WithSSL()) } if settings.SMTP.LocalName != "" { opts = append(opts, mail.WithHELO(settings.SMTP.LocalName)) } client, err := mail.NewClient(settings.SMTP.Host, opts...) if err != nil { return nil, fmt.Errorf("failed to create new mail delivery client: %w", err) } return client, nil }