package smtp import ( "bytes" "html/template" "strings" "github.com/sirupsen/logrus" "github.com/wneessen/go-mail" ) // EmailData holds the data for the default email template type EmailData struct { Subject string Body string } // VerificationData holds the data for the verification email template type VerificationData struct { VerificationURL string } // WelcomeData holds the data for the welcome email template type WelcomeData struct { Username string Password string WebAppURL string } // SMTPService handles email sending operations type SMTPService struct { client *mail.Client logger *logrus.Logger from string } // NewSMTPService creates a new SMTP service instance func NewSMTPService(config *Config, logger *logrus.Logger) (*SMTPService, error) { if logger == nil { return nil, NewError("initialization", ErrInvalidConfig, "logger is required") } // Validate config if config.Host == "" || config.Port == 0 || config.Username == "" || config.Password == "" || config.From == "" { return nil, NewError("initialization", ErrInvalidConfig, "missing required configuration") } // Create mail client with proper configuration client, err := mail.NewClient(config.Host, mail.WithPort(config.Port), mail.WithSMTPAuth(mail.SMTPAuthPlain), mail.WithUsername(config.Username), mail.WithPassword(config.Password), mail.WithTLSPolicy(mail.TLSMandatory), // Enforce TLS ) if err != nil { logger.WithError(err).Error("Failed to create SMTP client") return nil, NewError("initialization", ErrConnectionFailed, err.Error()) } return &SMTPService{ client: client, logger: logger, from: config.From, }, nil } // SendEmail sends a basic email using the default template func (s *SMTPService) SendEmail(to string, data EmailData) error { if strings.TrimSpace(to) == "" { return NewSendError("send", to, ErrInvalidRecipient, "empty recipient email") } // Parse and execute template tmpl, err := template.New("email").Parse(defaultTemplate) if err != nil { s.logger.WithError(err).Error("Failed to parse email template") return NewTemplateError("send", "default", ErrTemplateParsingFailed, err.Error()) } var body bytes.Buffer if err := tmpl.Execute(&body, data); err != nil { s.logger.WithError(err).Error("Failed to execute email template") return NewTemplateError("send", "default", ErrTemplateExecutionFailed, err.Error()) } // Create and configure message msg := mail.NewMsg() if err := msg.From(s.from); err != nil { return NewError("send", ErrMessageCreationFailed, "failed to set from address") } if err := msg.To(to); err != nil { return NewSendError("send", to, ErrMessageCreationFailed, "failed to set recipient") } msg.Subject(data.Subject) msg.SetBodyString(mail.TypeTextHTML, body.String()) // Send email if err := s.client.DialAndSend(msg); err != nil { s.logger.WithError(err).Error("Failed to send email") return NewSendError("send", to, ErrSendFailed, err.Error()) } s.logger.WithFields(logrus.Fields{ "to": to, "subject": data.Subject, }).Info("Email sent successfully") return nil } // SendVerificationEmail sends an email verification message func (s *SMTPService) SendVerificationEmail(to string, data VerificationData) error { if strings.TrimSpace(to) == "" || strings.TrimSpace(data.VerificationURL) == "" { return NewError("send_verification", ErrInvalidTemplate, "empty recipient or verification URL") } // Parse and execute template tmpl, err := template.New("verification").Parse(verificationTemplate) if err != nil { s.logger.WithError(err).Error("Failed to parse verification template") return NewTemplateError("send_verification", "verification", ErrTemplateParsingFailed, err.Error()) } var body bytes.Buffer if err := tmpl.Execute(&body, data); err != nil { s.logger.WithError(err).Error("Failed to execute verification template") return NewTemplateError("send_verification", "verification", ErrTemplateExecutionFailed, err.Error()) } // Create and configure message msg := mail.NewMsg() if err := msg.From(s.from); err != nil { return NewError("send_verification", ErrMessageCreationFailed, "failed to set from address") } if err := msg.To(to); err != nil { return NewSendError("send_verification", to, ErrMessageCreationFailed, "failed to set recipient") } msg.Subject("Verify Your Email Address") msg.SetBodyString(mail.TypeTextHTML, body.String()) // Send email if err := s.client.DialAndSend(msg); err != nil { s.logger.WithError(err).Error("Failed to send verification email") return NewSendError("send_verification", to, ErrSendFailed, err.Error()) } s.logger.WithFields(logrus.Fields{ "to": to, }).Info("Verification email sent successfully") return nil } // SendWelcomeEmail sends a welcome email with credentials func (s *SMTPService) SendWelcomeEmail(to string, data WelcomeData) error { if strings.TrimSpace(to) == "" || strings.TrimSpace(data.Username) == "" { return NewError("send_welcome", ErrInvalidTemplate, "empty recipient or username") } // Parse and execute template tmpl, err := template.New("welcome").Parse(welcomeTemplate) if err != nil { s.logger.WithError(err).Error("Failed to parse welcome template") return NewTemplateError("send_welcome", "welcome", ErrTemplateParsingFailed, err.Error()) } var body bytes.Buffer if err := tmpl.Execute(&body, data); err != nil { s.logger.WithError(err).Error("Failed to execute welcome template") return NewTemplateError("send_welcome", "welcome", ErrTemplateExecutionFailed, err.Error()) } // Create and configure message msg := mail.NewMsg() if err := msg.From(s.from); err != nil { return NewError("send_welcome", ErrMessageCreationFailed, "failed to set from address") } if err := msg.To(to); err != nil { return NewSendError("send_welcome", to, ErrMessageCreationFailed, "failed to set recipient") } msg.Subject("Welcome to Our Platform!") msg.SetBodyString(mail.TypeTextHTML, body.String()) // Send email if err := s.client.DialAndSend(msg); err != nil { s.logger.WithError(err).Error("Failed to send welcome email") return NewSendError("send_welcome", to, ErrSendFailed, err.Error()) } s.logger.WithFields(logrus.Fields{ "to": to, "username": data.Username, }).Info("Welcome email sent successfully") return nil } // Close closes the SMTP client connection func (s *SMTPService) Close() error { if s.client != nil { if err := s.client.Close(); err != nil { s.logger.WithError(err).Error("Failed to close SMTP client") return NewError("close", ErrClientClosed, err.Error()) } s.logger.Info("SMTP client closed successfully") } return nil }