make Mailer an interface
This commit is contained in:
parent
dd2412463b
commit
08c09ffee7
10 changed files with 234 additions and 227 deletions
|
|
@ -1,29 +0,0 @@
|
|||
package email
|
||||
|
||||
import "time"
|
||||
|
||||
// ContentLoginToken defines content for login token email template.
|
||||
type ContentLoginToken struct {
|
||||
Email string
|
||||
Name string
|
||||
URL string
|
||||
Token string
|
||||
Expiry time.Time
|
||||
}
|
||||
|
||||
// LoginToken creates and sends a login token email with provided template content.
|
||||
func (m *Mailer) LoginToken(name, address string, content ContentLoginToken) error {
|
||||
msg := &message{
|
||||
from: m.from,
|
||||
to: NewEmail(name, address),
|
||||
subject: "Login Token",
|
||||
template: "loginToken",
|
||||
content: content,
|
||||
}
|
||||
|
||||
if err := msg.parse(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return m.Send(msg)
|
||||
}
|
||||
170
email/email.go
170
email/email.go
|
|
@ -1,170 +0,0 @@
|
|||
// Package email provides email sending functionality.
|
||||
package email
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-mail/mail"
|
||||
"github.com/jaytaylor/html2text"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/vanng822/go-premailer/premailer"
|
||||
)
|
||||
|
||||
var (
|
||||
debug bool
|
||||
templates *template.Template
|
||||
)
|
||||
|
||||
// Mailer is a SMTP mailer.
|
||||
type Mailer struct {
|
||||
client *mail.Dialer
|
||||
from Email
|
||||
}
|
||||
|
||||
// NewMailer returns a configured SMTP Mailer.
|
||||
func NewMailer() (*Mailer, error) {
|
||||
if err := parseTemplates(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
smtp := struct {
|
||||
Host string
|
||||
Port int
|
||||
User string
|
||||
Password string
|
||||
}{
|
||||
viper.GetString("email_smtp_host"),
|
||||
viper.GetInt("email_smtp_port"),
|
||||
viper.GetString("email_smtp_user"),
|
||||
viper.GetString("email_smtp_password"),
|
||||
}
|
||||
|
||||
s := &Mailer{
|
||||
client: mail.NewDialer(smtp.Host, smtp.Port, smtp.User, smtp.Password),
|
||||
from: NewEmail(viper.GetString("email_from_name"), viper.GetString("email_from_address")),
|
||||
}
|
||||
|
||||
if smtp.Host == "" {
|
||||
log.Println("SMTP host not set => printing emails to stdout")
|
||||
debug = true
|
||||
return s, nil
|
||||
}
|
||||
|
||||
d, err := s.client.Dial()
|
||||
if err == nil {
|
||||
d.Close()
|
||||
return s, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Send sends the mail via smtp.
|
||||
func (m *Mailer) Send(email *message) error {
|
||||
if debug {
|
||||
log.Println("To:", email.to.Address)
|
||||
log.Println("Subject:", email.subject)
|
||||
log.Println(email.text)
|
||||
return nil
|
||||
}
|
||||
|
||||
msg := mail.NewMessage()
|
||||
msg.SetAddressHeader("From", email.from.Address, email.from.Name)
|
||||
msg.SetAddressHeader("To", email.to.Address, email.to.Name)
|
||||
msg.SetHeader("Subject", email.subject)
|
||||
msg.SetBody("text/plain", email.text)
|
||||
msg.AddAlternative("text/html", email.html)
|
||||
|
||||
return m.client.DialAndSend(msg)
|
||||
}
|
||||
|
||||
// message struct holds all parts of a specific email message.
|
||||
type message struct {
|
||||
from Email
|
||||
to Email
|
||||
subject string
|
||||
template string
|
||||
content interface{}
|
||||
html string
|
||||
text string
|
||||
}
|
||||
|
||||
// parse parses the corrsponding template and content
|
||||
func (m *message) parse() error {
|
||||
buf := new(bytes.Buffer)
|
||||
if err := templates.ExecuteTemplate(buf, m.template, m.content); err != nil {
|
||||
return err
|
||||
}
|
||||
prem, err := premailer.NewPremailerFromString(buf.String(), premailer.NewOptions())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
html, err := prem.Transform()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.html = html
|
||||
|
||||
text, err := html2text.FromString(html, html2text.Options{PrettyTables: true})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.text = text
|
||||
return nil
|
||||
}
|
||||
|
||||
// Email struct holds email address and recipient name.
|
||||
type Email struct {
|
||||
Name string
|
||||
Address string
|
||||
}
|
||||
|
||||
// NewEmail returns an email address.
|
||||
func NewEmail(name string, address string) Email {
|
||||
return Email{
|
||||
Name: name,
|
||||
Address: address,
|
||||
}
|
||||
}
|
||||
|
||||
func parseTemplates() error {
|
||||
templates = template.New("").Funcs(fMap)
|
||||
return filepath.Walk("./templates", func(path string, info os.FileInfo, err error) error {
|
||||
if strings.Contains(path, ".html") {
|
||||
_, err = templates.ParseFiles(path)
|
||||
return err
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
var fMap = template.FuncMap{
|
||||
"formatAsDate": formatAsDate,
|
||||
"formatAsDuration": formatAsDuration,
|
||||
}
|
||||
|
||||
func formatAsDate(t time.Time) string {
|
||||
year, month, day := t.Date()
|
||||
return fmt.Sprintf("%d.%d.%d", day, month, year)
|
||||
}
|
||||
|
||||
func formatAsDuration(t time.Time) string {
|
||||
dur := time.Until(t)
|
||||
hours := int(dur.Hours())
|
||||
mins := int(dur.Minutes())
|
||||
|
||||
v := ""
|
||||
if hours != 0 {
|
||||
v += strconv.Itoa(hours) + " hours and "
|
||||
}
|
||||
v += strconv.Itoa(mins) + " minutes"
|
||||
return v
|
||||
}
|
||||
106
email/mailer.go
Normal file
106
email/mailer.go
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
// Package email provides email sending functionality.
|
||||
package email
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jaytaylor/html2text"
|
||||
"github.com/vanng822/go-premailer/premailer"
|
||||
)
|
||||
|
||||
var templates *template.Template
|
||||
|
||||
type Mailer interface {
|
||||
Send(Message) error
|
||||
}
|
||||
|
||||
// Message struct holds all parts of a specific email Message.
|
||||
type Message struct {
|
||||
From Email
|
||||
To Email
|
||||
Subject string
|
||||
Template string
|
||||
Content any
|
||||
html string
|
||||
text string
|
||||
}
|
||||
|
||||
// parse parses the corrsponding template and content
|
||||
func (m *Message) parse() error {
|
||||
buf := new(bytes.Buffer)
|
||||
if err := templates.ExecuteTemplate(buf, m.Template, m.Content); err != nil {
|
||||
return err
|
||||
}
|
||||
prem, err := premailer.NewPremailerFromString(buf.String(), premailer.NewOptions())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
html, err := prem.Transform()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.html = html
|
||||
|
||||
text, err := html2text.FromString(html, html2text.Options{PrettyTables: true})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.text = text
|
||||
return nil
|
||||
}
|
||||
|
||||
// Email struct holds email address and recipient name.
|
||||
type Email struct {
|
||||
Name string
|
||||
Address string
|
||||
}
|
||||
|
||||
// NewEmail returns an email address.
|
||||
func NewEmail(name string, address string) Email {
|
||||
return Email{
|
||||
Name: name,
|
||||
Address: address,
|
||||
}
|
||||
}
|
||||
|
||||
func parseTemplates() error {
|
||||
templates = template.New("").Funcs(fMap)
|
||||
return filepath.Walk("./templates", func(path string, info os.FileInfo, err error) error {
|
||||
if strings.Contains(path, ".html") {
|
||||
_, err = templates.ParseFiles(path)
|
||||
return err
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
var fMap = template.FuncMap{
|
||||
"formatAsDate": formatAsDate,
|
||||
"formatAsDuration": formatAsDuration,
|
||||
}
|
||||
|
||||
func formatAsDate(t time.Time) string {
|
||||
year, month, day := t.Date()
|
||||
return fmt.Sprintf("%d.%d.%d", day, month, year)
|
||||
}
|
||||
|
||||
func formatAsDuration(t time.Time) string {
|
||||
dur := time.Until(t)
|
||||
hours := int(dur.Hours())
|
||||
mins := int(dur.Minutes())
|
||||
|
||||
v := ""
|
||||
if hours != 0 {
|
||||
v += strconv.Itoa(hours) + " hours and "
|
||||
}
|
||||
v += strconv.Itoa(mins) + " minutes"
|
||||
return v
|
||||
}
|
||||
|
|
@ -1,13 +1,28 @@
|
|||
package email
|
||||
|
||||
import "log"
|
||||
|
||||
// MockMailer is a mock Mailer
|
||||
type MockMailer struct {
|
||||
LoginTokenFn func(name, email string, c ContentLoginToken) error
|
||||
LoginTokenInvoked bool
|
||||
SendFn func(m Message) error
|
||||
SendInvoked bool
|
||||
}
|
||||
|
||||
// LoginToken is a mock for LoginToken
|
||||
func (s *MockMailer) LoginToken(n, e string, c ContentLoginToken) error {
|
||||
s.LoginTokenInvoked = true
|
||||
return s.LoginTokenFn(n, e, c)
|
||||
func logMessage(m Message) {
|
||||
log.Printf("MockMailer email sent:\nSubject: %s\nTo: %s <%s>\nContext: %#v\n", m.Subject, m.To.Name, m.To.Address, m.Content)
|
||||
}
|
||||
|
||||
func NewMockMailer() *MockMailer {
|
||||
log.Println("ATTENTION: SMTP Mailer not configured => printing emails to stdout")
|
||||
return &MockMailer{
|
||||
SendFn: func(m Message) error {
|
||||
logMessage(m)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MockMailer) Send(m Message) error {
|
||||
s.SendInvoked = true
|
||||
return s.SendFn(m)
|
||||
}
|
||||
|
|
|
|||
63
email/smtp.go
Normal file
63
email/smtp.go
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
package email
|
||||
|
||||
import (
|
||||
"github.com/go-mail/mail"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// SMTPMailer is a SMTP mailer.
|
||||
type SMTPMailer struct {
|
||||
client *mail.Dialer
|
||||
from Email
|
||||
}
|
||||
|
||||
// NewMailer returns a configured SMTP Mailer.
|
||||
func NewMailer() (Mailer, error) {
|
||||
if err := parseTemplates(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
smtp := struct {
|
||||
Host string
|
||||
Port int
|
||||
User string
|
||||
Password string
|
||||
}{
|
||||
viper.GetString("email_smtp_host"),
|
||||
viper.GetInt("email_smtp_port"),
|
||||
viper.GetString("email_smtp_user"),
|
||||
viper.GetString("email_smtp_password"),
|
||||
}
|
||||
|
||||
if smtp.Host == "" {
|
||||
return NewMockMailer(), nil
|
||||
}
|
||||
|
||||
s := &SMTPMailer{
|
||||
client: mail.NewDialer(smtp.Host, smtp.Port, smtp.User, smtp.Password),
|
||||
from: NewEmail(viper.GetString("email_from_name"), viper.GetString("email_from_address")),
|
||||
}
|
||||
|
||||
d, err := s.client.Dial()
|
||||
if err == nil {
|
||||
d.Close()
|
||||
return s, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Send sends the mail via smtp.
|
||||
func (m *SMTPMailer) Send(email Message) error {
|
||||
if err := email.parse(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg := mail.NewMessage()
|
||||
msg.SetAddressHeader("From", email.From.Address, email.From.Name)
|
||||
msg.SetAddressHeader("To", email.To.Address, email.To.Name)
|
||||
msg.SetHeader("Subject", email.Subject)
|
||||
msg.SetBody("text/plain", email.text)
|
||||
msg.AddAlternative("text/html", email.html)
|
||||
|
||||
return m.client.DialAndSend(msg)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue