123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209 |
- 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
- }
|