package email import ( "crypto/tls" "fmt" "html" "net/smtp" "strconv" "strings" "landing/internal/config" ) type Sender struct { cfg *config.Config } func New(cfg *config.Config) *Sender { return &Sender{cfg: cfg} } func (s *Sender) SendConfirmationEmail( email string, unsubscribeLink string, ) error { subject := "Thanks for subscribing!" htmlBody := fmt.Sprintf(`

Welcome to RideAware!

Thank you for subscribing to our newsletter.

Unsubscribe

`, unsubscribeLink) return s.sendEmail(email, subject, htmlBody) } func (s *Sender) SendContactConfirmation(email, name string) error { subject := "We received your message - RideAware" htmlBody := fmt.Sprintf(`

Thank you for reaching out, %s!

We've received your message and will get back to you as soon as possible.

In the meantime, feel free to check out more about RideAware on our website.

Best regards,
The RideAware Team

`, html.EscapeString(name)) return s.sendEmail(email, subject, htmlBody) } func (s *Sender) SendContactNotification( adminEmail, name, email, subject, message string, ) error { emailSubject := fmt.Sprintf("New contact message from %s", name) htmlBody := fmt.Sprintf(`

New Contact Message

From: %s (%s)

Subject: %s

Message:

%s

`, html.EscapeString(name), html.EscapeString(email), html.EscapeString(subject), strings.ReplaceAll(html.EscapeString(message), "\n", "
"), ) return s.sendEmail(adminEmail, emailSubject, htmlBody) } func (s *Sender) sendEmail(toEmail, subject, htmlBody string) error { port, err := strconv.Atoi(s.cfg.SMTPPort) if err != nil { return fmt.Errorf("invalid SMTP port '%s': %w", s.cfg.SMTPPort, err) } message := fmt.Sprintf( "From: %s\r\nTo: %s\r\nSubject: %s\r\nContent-Type: text/html; charset=utf-8\r\n\r\n%s", s.cfg.SMTPUser, toEmail, subject, htmlBody, ) addr := fmt.Sprintf("%s:%d", s.cfg.SMTPHost, port) // Port 465 uses direct SSL/TLS return s.sendEmailSSL(addr, toEmail, message) } func (s *Sender) sendEmailSSL(addr, toEmail, message string) error { // Create TLS config tlsConfig := &tls.Config{ ServerName: s.cfg.SMTPHost, } // Try to dial with TLS conn, err := tls.Dial("tcp", addr, tlsConfig) if err != nil { return fmt.Errorf("failed to dial TLS to %s: %w", addr, err) } defer conn.Close() // Create SMTP client client, err := smtp.NewClient(conn, s.cfg.SMTPHost) if err != nil { return fmt.Errorf("failed to create SMTP client: %w", err) } defer client.Close() // Authenticate auth := smtp.PlainAuth("", s.cfg.SMTPUser, s.cfg.SMTPPass, s.cfg.SMTPHost) if err := client.Auth(auth); err != nil { return fmt.Errorf("failed to authenticate with %s: %w", s.cfg.SMTPUser, err) } // Set sender if err := client.Mail(s.cfg.SMTPUser); err != nil { return fmt.Errorf("failed to set mail from %s: %w", s.cfg.SMTPUser, err) } // Set recipient if err := client.Rcpt(toEmail); err != nil { return fmt.Errorf("failed to set mail to %s: %w", toEmail, err) } // Get data writer wc, err := client.Data() if err != nil { return fmt.Errorf("failed to get data writer: %w", err) } defer wc.Close() // Write message if _, err := wc.Write([]byte(message)); err != nil { return fmt.Errorf("failed to write message: %w", err) } // Quit - ignore quit errors since email was already queued _ = client.Quit() return nil } func (s *Sender) TestConnection() error { port, err := strconv.Atoi(s.cfg.SMTPPort) if err != nil { return fmt.Errorf("invalid SMTP port '%s': %w", s.cfg.SMTPPort, err) } addr := fmt.Sprintf("%s:%d", s.cfg.SMTPHost, port) // Test TLS connection tlsConfig := &tls.Config{ ServerName: s.cfg.SMTPHost, } conn, err := tls.Dial("tcp", addr, tlsConfig) if err != nil { return fmt.Errorf("failed to dial TLS to %s: %w", addr, err) } defer conn.Close() // Test SMTP client creation client, err := smtp.NewClient(conn, s.cfg.SMTPHost) if err != nil { return fmt.Errorf("failed to create SMTP client: %w", err) } defer client.Close() return nil }