package mailer import ( domain "RenewCMS/internal/domain/mail" "RenewCMS/internal/infrastructure/config" "bytes" "crypto/tls" "embed" "html/template" "log" "time" ) type MailRepository struct { transport transport sender string now func() time.Time } type Option func(*options) type options struct { tlsConfig *tls.Config now func() time.Time } func WithTLSConfig(tlsConfig *tls.Config) Option { return func(o *options) { o.tlsConfig = tlsConfig } } func WithClock(now func() time.Time) Option { return func(o *options) { o.now = now } } //go:embed templates/* var mailTemplateFiles embed.FS func NewMailRepository(smtp config.SmtpSettings, opts ...Option) *MailRepository { configured := options{now: time.Now} for _, opt := range opts { opt(&configured) } return &MailRepository{ transport: newTransport(smtp, configured.tlsConfig), sender: smtp.Sender, now: configured.now, } } const defaultSubject = "RenewCMS | Action required" type subjectProvider interface { Subject() string } func subjectFor(templateName string, data any) string { if provider, ok := data.(subjectProvider); ok { return provider.Subject() } switch templateName { case "verification": return "Verify your RenewCMS account" case "password-reset": return "Reset your RenewCMS password" case "verify-new-email": return "Confirm your new RenewCMS email address" case "email-change-notice": return "Your RenewCMS email address change was requested" case "account-deleted": return "Your RenewCMS account has been deleted" } return defaultSubject } func (m MailRepository) Render(templateName string, data any) (string, string, error) { subject := subjectFor(templateName, data) tmpl, err := template.ParseFS(mailTemplateFiles, "templates/"+templateName+".html") if err != nil { log.Println("error when template.ParseFS:", err) return subject, "", err } var body bytes.Buffer if err := tmpl.Execute(&body, data); err != nil { log.Println("error when tmpl.Execute:", err) return subject, "", err } return subject, body.String(), nil } func (m MailRepository) Send(receiverAddress string, templateName string, data any) error { subject, body, err := m.Render(templateName, data) if err != nil { return err } msg, err := newMessage(m.sender, receiverAddress, subject, body, m.now()) if err != nil { log.Println("error when building email:", err) return err } payload, err := msg.bytes() if err != nil { log.Println("error when encoding email:", err) return err } if err := m.transport.deliver(msg.from.Address, msg.to.Address, payload); err != nil { log.Println("error when sending email:", err) return err } return nil } var _ domain.Repository = &MailRepository{}