@@ -0,0 +1,163 @@
|
||||
package mailer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/wneessen/go-mail"
|
||||
|
||||
ht "html/template"
|
||||
tt "text/template"
|
||||
)
|
||||
|
||||
//go:embed "templates"
|
||||
var templateFS embed.FS
|
||||
|
||||
// Mailer renders embedded templates and delivers the resulting message.
|
||||
type Mailer struct {
|
||||
client *mail.Client
|
||||
mode Mode
|
||||
filePath string
|
||||
sender string
|
||||
fileMu sync.Mutex
|
||||
}
|
||||
|
||||
// Mode selects the delivery backend used by a Mailer.
|
||||
type Mode string
|
||||
|
||||
const (
|
||||
// ModeSMTP sends messages through an SMTP server.
|
||||
ModeSMTP Mode = "smtp"
|
||||
// ModeFile appends rendered messages to a local development file.
|
||||
ModeFile Mode = "file"
|
||||
)
|
||||
|
||||
// Config contains SMTP or development-file delivery settings.
|
||||
type Config struct {
|
||||
Mode Mode
|
||||
Host string
|
||||
Port int
|
||||
Username string
|
||||
Password string
|
||||
Sender string
|
||||
FilePath string
|
||||
}
|
||||
|
||||
// New validates config and creates a Mailer.
|
||||
func New(config Config) (*Mailer, error) {
|
||||
mailer := &Mailer{
|
||||
mode: config.Mode,
|
||||
filePath: config.FilePath,
|
||||
sender: config.Sender,
|
||||
}
|
||||
|
||||
switch config.Mode {
|
||||
case ModeSMTP:
|
||||
client, err := mail.NewClient(
|
||||
config.Host,
|
||||
mail.WithSMTPAuth(mail.SMTPAuthLogin),
|
||||
mail.WithPort(config.Port),
|
||||
mail.WithUsername(config.Username),
|
||||
mail.WithPassword(config.Password),
|
||||
mail.WithTimeout(5*time.Second),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mailer.client = client
|
||||
case ModeFile:
|
||||
if config.FilePath == "" {
|
||||
return nil, errors.New("mailer: file path must not be empty in file mode")
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("mailer: unsupported mode %q", config.Mode)
|
||||
}
|
||||
|
||||
return mailer, nil
|
||||
}
|
||||
|
||||
// Send renders templateFile with data and delivers it to recipient.
|
||||
func (m *Mailer) Send(recipient string, templateFile string, data any) error {
|
||||
textTmpl, err := tt.New("").ParseFS(templateFS, "templates/"+templateFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
subject := new(bytes.Buffer)
|
||||
err = textTmpl.ExecuteTemplate(subject, "subject", data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
plainBody := new(bytes.Buffer)
|
||||
err = textTmpl.ExecuteTemplate(plainBody, "plainBody", data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
htmlTmpl, err := ht.New("").ParseFS(templateFS, "templates/"+templateFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
htmlBody := new(bytes.Buffer)
|
||||
err = htmlTmpl.ExecuteTemplate(htmlBody, "htmlBody", data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg := mail.NewMsg()
|
||||
|
||||
err = msg.To(recipient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = msg.From(m.sender)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg.Subject(subject.String())
|
||||
msg.SetBodyString(mail.TypeTextPlain, plainBody.String())
|
||||
msg.AddAlternativeString(mail.TypeTextHTML, htmlBody.String())
|
||||
|
||||
if m.mode == ModeFile {
|
||||
return m.appendToFile(msg)
|
||||
}
|
||||
|
||||
return m.client.DialAndSend(msg)
|
||||
}
|
||||
|
||||
func (m *Mailer) appendToFile(msg *mail.Msg) error {
|
||||
var content bytes.Buffer
|
||||
if _, err := msg.WriteTo(&content); err != nil {
|
||||
return fmt.Errorf("mailer: format message: %w", err)
|
||||
}
|
||||
|
||||
m.fileMu.Lock()
|
||||
defer m.fileMu.Unlock()
|
||||
|
||||
file, err := os.OpenFile(m.filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o600)
|
||||
if err != nil {
|
||||
return fmt.Errorf("mailer: open output file: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if _, err = file.WriteString("\n=== gardomatic mail ===\n"); err != nil {
|
||||
return fmt.Errorf("mailer: append separator: %w", err)
|
||||
}
|
||||
if _, err = content.WriteTo(file); err != nil {
|
||||
return fmt.Errorf("mailer: append message: %w", err)
|
||||
}
|
||||
if _, err = file.WriteString("\n"); err != nil {
|
||||
return fmt.Errorf("mailer: finish message: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user