service.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. package smtp
  2. import (
  3. "bytes"
  4. "html/template"
  5. "strings"
  6. "github.com/sirupsen/logrus"
  7. "github.com/wneessen/go-mail"
  8. )
  9. // EmailData holds the data for the default email template
  10. type EmailData struct {
  11. Subject string
  12. Body string
  13. }
  14. // VerificationData holds the data for the verification email template
  15. type VerificationData struct {
  16. VerificationURL string
  17. }
  18. // WelcomeData holds the data for the welcome email template
  19. type WelcomeData struct {
  20. Username string
  21. Password string
  22. WebAppURL string
  23. }
  24. // SMTPService handles email sending operations
  25. type SMTPService struct {
  26. client *mail.Client
  27. logger *logrus.Logger
  28. from string
  29. }
  30. // NewSMTPService creates a new SMTP service instance
  31. func NewSMTPService(config *Config, logger *logrus.Logger) (*SMTPService, error) {
  32. if logger == nil {
  33. return nil, NewError("initialization", ErrInvalidConfig, "logger is required")
  34. }
  35. // Validate config
  36. if config.Host == "" || config.Port == 0 || config.Username == "" || config.Password == "" || config.From == "" {
  37. return nil, NewError("initialization", ErrInvalidConfig, "missing required configuration")
  38. }
  39. // Create mail client with proper configuration
  40. client, err := mail.NewClient(config.Host,
  41. mail.WithPort(config.Port),
  42. mail.WithSMTPAuth(mail.SMTPAuthPlain),
  43. mail.WithUsername(config.Username),
  44. mail.WithPassword(config.Password),
  45. mail.WithTLSPolicy(mail.TLSMandatory), // Enforce TLS
  46. )
  47. if err != nil {
  48. logger.WithError(err).Error("Failed to create SMTP client")
  49. return nil, NewError("initialization", ErrConnectionFailed, err.Error())
  50. }
  51. return &SMTPService{
  52. client: client,
  53. logger: logger,
  54. from: config.From,
  55. }, nil
  56. }
  57. // SendEmail sends a basic email using the default template
  58. func (s *SMTPService) SendEmail(to string, data EmailData) error {
  59. if strings.TrimSpace(to) == "" {
  60. return NewSendError("send", to, ErrInvalidRecipient, "empty recipient email")
  61. }
  62. // Parse and execute template
  63. tmpl, err := template.New("email").Parse(defaultTemplate)
  64. if err != nil {
  65. s.logger.WithError(err).Error("Failed to parse email template")
  66. return NewTemplateError("send", "default", ErrTemplateParsingFailed, err.Error())
  67. }
  68. var body bytes.Buffer
  69. if err := tmpl.Execute(&body, data); err != nil {
  70. s.logger.WithError(err).Error("Failed to execute email template")
  71. return NewTemplateError("send", "default", ErrTemplateExecutionFailed, err.Error())
  72. }
  73. // Create and configure message
  74. msg := mail.NewMsg()
  75. if err := msg.From(s.from); err != nil {
  76. return NewError("send", ErrMessageCreationFailed, "failed to set from address")
  77. }
  78. if err := msg.To(to); err != nil {
  79. return NewSendError("send", to, ErrMessageCreationFailed, "failed to set recipient")
  80. }
  81. msg.Subject(data.Subject)
  82. msg.SetBodyString(mail.TypeTextHTML, body.String())
  83. // Send email
  84. if err := s.client.DialAndSend(msg); err != nil {
  85. s.logger.WithError(err).Error("Failed to send email")
  86. return NewSendError("send", to, ErrSendFailed, err.Error())
  87. }
  88. s.logger.WithFields(logrus.Fields{
  89. "to": to,
  90. "subject": data.Subject,
  91. }).Info("Email sent successfully")
  92. return nil
  93. }
  94. // SendVerificationEmail sends an email verification message
  95. func (s *SMTPService) SendVerificationEmail(to string, data VerificationData) error {
  96. if strings.TrimSpace(to) == "" || strings.TrimSpace(data.VerificationURL) == "" {
  97. return NewError("send_verification", ErrInvalidTemplate, "empty recipient or verification URL")
  98. }
  99. // Parse and execute template
  100. tmpl, err := template.New("verification").Parse(verificationTemplate)
  101. if err != nil {
  102. s.logger.WithError(err).Error("Failed to parse verification template")
  103. return NewTemplateError("send_verification", "verification", ErrTemplateParsingFailed, err.Error())
  104. }
  105. var body bytes.Buffer
  106. if err := tmpl.Execute(&body, data); err != nil {
  107. s.logger.WithError(err).Error("Failed to execute verification template")
  108. return NewTemplateError("send_verification", "verification", ErrTemplateExecutionFailed, err.Error())
  109. }
  110. // Create and configure message
  111. msg := mail.NewMsg()
  112. if err := msg.From(s.from); err != nil {
  113. return NewError("send_verification", ErrMessageCreationFailed, "failed to set from address")
  114. }
  115. if err := msg.To(to); err != nil {
  116. return NewSendError("send_verification", to, ErrMessageCreationFailed, "failed to set recipient")
  117. }
  118. msg.Subject("Verify Your Email Address")
  119. msg.SetBodyString(mail.TypeTextHTML, body.String())
  120. // Send email
  121. if err := s.client.DialAndSend(msg); err != nil {
  122. s.logger.WithError(err).Error("Failed to send verification email")
  123. return NewSendError("send_verification", to, ErrSendFailed, err.Error())
  124. }
  125. s.logger.WithFields(logrus.Fields{
  126. "to": to,
  127. }).Info("Verification email sent successfully")
  128. return nil
  129. }
  130. // SendWelcomeEmail sends a welcome email with credentials
  131. func (s *SMTPService) SendWelcomeEmail(to string, data WelcomeData) error {
  132. if strings.TrimSpace(to) == "" || strings.TrimSpace(data.Username) == "" {
  133. return NewError("send_welcome", ErrInvalidTemplate, "empty recipient or username")
  134. }
  135. // Parse and execute template
  136. tmpl, err := template.New("welcome").Parse(welcomeTemplate)
  137. if err != nil {
  138. s.logger.WithError(err).Error("Failed to parse welcome template")
  139. return NewTemplateError("send_welcome", "welcome", ErrTemplateParsingFailed, err.Error())
  140. }
  141. var body bytes.Buffer
  142. if err := tmpl.Execute(&body, data); err != nil {
  143. s.logger.WithError(err).Error("Failed to execute welcome template")
  144. return NewTemplateError("send_welcome", "welcome", ErrTemplateExecutionFailed, err.Error())
  145. }
  146. // Create and configure message
  147. msg := mail.NewMsg()
  148. if err := msg.From(s.from); err != nil {
  149. return NewError("send_welcome", ErrMessageCreationFailed, "failed to set from address")
  150. }
  151. if err := msg.To(to); err != nil {
  152. return NewSendError("send_welcome", to, ErrMessageCreationFailed, "failed to set recipient")
  153. }
  154. msg.Subject("Welcome to Our Platform!")
  155. msg.SetBodyString(mail.TypeTextHTML, body.String())
  156. // Send email
  157. if err := s.client.DialAndSend(msg); err != nil {
  158. s.logger.WithError(err).Error("Failed to send welcome email")
  159. return NewSendError("send_welcome", to, ErrSendFailed, err.Error())
  160. }
  161. s.logger.WithFields(logrus.Fields{
  162. "to": to,
  163. "username": data.Username,
  164. }).Info("Welcome email sent successfully")
  165. return nil
  166. }
  167. // Close closes the SMTP client connection
  168. func (s *SMTPService) Close() error {
  169. if s.client != nil {
  170. if err := s.client.Close(); err != nil {
  171. s.logger.WithError(err).Error("Failed to close SMTP client")
  172. return NewError("close", ErrClientClosed, err.Error())
  173. }
  174. s.logger.Info("SMTP client closed successfully")
  175. }
  176. return nil
  177. }