Files
Gumble-Backend/internal/mailer/mailer.go
Zane Walker 7e940c83a7
Some checks failed
Build & Test / build-test (push) Has been cancelled
Build & Test / swagger-codegen-cli (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
(Feat): Initial Commit
2026-07-03 19:41:31 +05:30

163 lines
4.8 KiB
Go

package mailer
import (
"bytes"
"context"
"errors"
"fmt"
"html/template"
"os"
"path/filepath"
"allaboutapps.dev/aw/go-starter/internal/config"
"allaboutapps.dev/aw/go-starter/internal/data/dto"
"allaboutapps.dev/aw/go-starter/internal/mailer/transport"
"allaboutapps.dev/aw/go-starter/internal/util"
"github.com/jordan-wright/email"
"github.com/rs/zerolog/log"
)
var (
ErrEmailTemplateNotFound = errors.New("email template not found")
emailTemplatePasswordReset = "password_reset" // /app/templates/email/password_reset/**.
emailTemplateAccountConfirmation = "account_confirmation" // /app/templates/email/account_confirmation/**
)
type Mailer struct {
Config config.Mailer
Transport transport.MailTransporter
Templates map[string]*template.Template
}
func New(config config.Mailer, transport transport.MailTransporter) *Mailer {
return &Mailer{
Config: config,
Transport: transport,
Templates: map[string]*template.Template{},
}
}
func NewWithConfig(cfg config.Mailer, smtpConfig transport.SMTPMailTransportConfig) (*Mailer, error) {
var mailer *Mailer
switch config.MailerTransporter(cfg.Transporter) {
case config.MailerTransporterMock:
log.Warn().Msg("Initializing mock mailer")
mailer = New(cfg, transport.NewMock())
case config.MailerTransporterSMTP:
mailer = New(cfg, transport.NewSMTP(smtpConfig))
default:
return nil, fmt.Errorf("unsupported mail transporter: %s", cfg.Transporter)
}
if err := mailer.ParseTemplates(); err != nil {
return nil, fmt.Errorf("failed to parse mailer templates: %w", err)
}
return mailer, nil
}
func (m *Mailer) ParseTemplates() error {
files, err := os.ReadDir(m.Config.WebTemplatesEmailBaseDirAbs)
if err != nil {
log.Error().Str("dir", m.Config.WebTemplatesEmailBaseDirAbs).Err(err).Msg("Failed to read email templates directory while parsing templates")
return fmt.Errorf("failed to read email templates directory while parsing templates: %w", err)
}
for _, file := range files {
if !file.IsDir() {
continue
}
tmpl, err := template.ParseGlob(filepath.Join(m.Config.WebTemplatesEmailBaseDirAbs, file.Name(), "**"))
if err != nil {
log.Error().Str("template", file.Name()).Err(err).Msg("Failed to parse email template files as glob")
return fmt.Errorf("failed to parse email template files as glob: %w", err)
}
m.Templates[file.Name()] = tmpl
}
return nil
}
func (m *Mailer) SendPasswordReset(ctx context.Context, to string, passwordResetLink string) error {
log := util.LogFromContext(ctx).With().Str("component", "mailer").Str("email_template", emailTemplatePasswordReset).Logger()
tmpl, ok := m.Templates[emailTemplatePasswordReset]
if !ok {
log.Error().Msg("Password reset email template not found")
return ErrEmailTemplateNotFound
}
data := map[string]interface{}{
"passwordResetLink": passwordResetLink,
}
var buf bytes.Buffer
if err := tmpl.Execute(&buf, data); err != nil {
log.Error().Err(err).Msg("Failed to execute password reset email template")
return fmt.Errorf("failed to execute password reset email template: %w", err)
}
mail := email.NewEmail()
mail.From = m.Config.DefaultSender
mail.To = []string{to}
mail.Subject = "Password reset"
mail.HTML = buf.Bytes()
if !m.Config.Send {
log.Warn().Str("to", to).Str("passwordResetLink", passwordResetLink).Msg("Sending has been disabled in mailer config, skipping password reset email")
return nil
}
if err := m.Transport.Send(mail); err != nil {
log.Debug().Err(err).Msg("Failed to send password reset email")
return fmt.Errorf("failed to send password reset email: %w", err)
}
log.Debug().Msg("Successfully sent password reset email")
return nil
}
func (m *Mailer) SendAccountConfirmation(ctx context.Context, to string, payload dto.ConfirmatioNotificationPayload) error {
log := util.LogFromContext(ctx)
tmpl, ok := m.Templates[emailTemplateAccountConfirmation]
if !ok {
log.Error().Msg("Account confirmation email template not found")
return ErrEmailTemplateNotFound
}
data := map[string]interface{}{
"confirmationLink": payload.ConfirmationLink,
}
var buf bytes.Buffer
if err := tmpl.Execute(&buf, data); err != nil {
log.Error().Err(err).Msg("Failed to execute account confirmation email template")
return fmt.Errorf("failed to execute account confirmation email template: %w", err)
}
mail := email.NewEmail()
mail.From = m.Config.DefaultSender
mail.To = []string{to}
mail.Subject = "Account confirmation"
mail.HTML = buf.Bytes()
if !m.Config.Send {
log.Warn().Str("to", to).Msg("Sending has been disabled in mailer config, skipping account confirmation email")
return nil
}
if err := m.Transport.Send(mail); err != nil {
log.Debug().Err(err).Msg("Failed to send account confirmation email")
return fmt.Errorf("failed to send account confirmation email: %w", err)
}
return nil
}