service.go 6.7 KB

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