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
}