github.com/dhax/go-base@v0.0.0-20231004214136-8be7e5c1972b/email/email.go (about)

     1  // Package email provides email sending functionality.
     2  package email
     3  
     4  import (
     5  	"bytes"
     6  	"fmt"
     7  	"html/template"
     8  	"log"
     9  	"os"
    10  	"path/filepath"
    11  	"strconv"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/go-mail/mail"
    16  	"github.com/jaytaylor/html2text"
    17  	"github.com/spf13/viper"
    18  	"github.com/vanng822/go-premailer/premailer"
    19  )
    20  
    21  var (
    22  	debug     bool
    23  	templates *template.Template
    24  )
    25  
    26  // Mailer is a SMTP mailer.
    27  type Mailer struct {
    28  	client *mail.Dialer
    29  	from   Email
    30  }
    31  
    32  // NewMailer returns a configured SMTP Mailer.
    33  func NewMailer() (*Mailer, error) {
    34  	if err := parseTemplates(); err != nil {
    35  		return nil, err
    36  	}
    37  
    38  	smtp := struct {
    39  		Host     string
    40  		Port     int
    41  		User     string
    42  		Password string
    43  	}{
    44  		viper.GetString("email_smtp_host"),
    45  		viper.GetInt("email_smtp_port"),
    46  		viper.GetString("email_smtp_user"),
    47  		viper.GetString("email_smtp_password"),
    48  	}
    49  
    50  	s := &Mailer{
    51  		client: mail.NewPlainDialer(smtp.Host, smtp.Port, smtp.User, smtp.Password),
    52  		from:   NewEmail(viper.GetString("email_from_name"), viper.GetString("email_from_address")),
    53  	}
    54  
    55  	if smtp.Host == "" {
    56  		log.Println("SMTP host not set => printing emails to stdout")
    57  		debug = true
    58  		return s, nil
    59  	}
    60  
    61  	d, err := s.client.Dial()
    62  	if err == nil {
    63  		d.Close()
    64  		return s, nil
    65  	}
    66  	return nil, err
    67  }
    68  
    69  // Send sends the mail via smtp.
    70  func (m *Mailer) Send(email *message) error {
    71  	if debug {
    72  		log.Println("To:", email.to.Address)
    73  		log.Println("Subject:", email.subject)
    74  		log.Println(email.text)
    75  		return nil
    76  	}
    77  
    78  	msg := mail.NewMessage()
    79  	msg.SetAddressHeader("From", email.from.Address, email.from.Name)
    80  	msg.SetAddressHeader("To", email.to.Address, email.to.Name)
    81  	msg.SetHeader("Subject", email.subject)
    82  	msg.SetBody("text/plain", email.text)
    83  	msg.AddAlternative("text/html", email.html)
    84  
    85  	return m.client.DialAndSend(msg)
    86  }
    87  
    88  // message struct holds all parts of a specific email message.
    89  type message struct {
    90  	from     Email
    91  	to       Email
    92  	subject  string
    93  	template string
    94  	content  interface{}
    95  	html     string
    96  	text     string
    97  }
    98  
    99  // parse parses the corrsponding template and content
   100  func (m *message) parse() error {
   101  	buf := new(bytes.Buffer)
   102  	if err := templates.ExecuteTemplate(buf, m.template, m.content); err != nil {
   103  		return err
   104  	}
   105  	prem, err := premailer.NewPremailerFromString(buf.String(), premailer.NewOptions())
   106  	if err != nil {
   107  		return err
   108  	}
   109  
   110  	html, err := prem.Transform()
   111  	if err != nil {
   112  		return err
   113  	}
   114  	m.html = html
   115  
   116  	text, err := html2text.FromString(html, html2text.Options{PrettyTables: true})
   117  	if err != nil {
   118  		return err
   119  	}
   120  	m.text = text
   121  	return nil
   122  }
   123  
   124  // Email struct holds email address and recipient name.
   125  type Email struct {
   126  	Name    string
   127  	Address string
   128  }
   129  
   130  // NewEmail returns an email address.
   131  func NewEmail(name string, address string) Email {
   132  	return Email{
   133  		Name:    name,
   134  		Address: address,
   135  	}
   136  }
   137  
   138  func parseTemplates() error {
   139  	templates = template.New("").Funcs(fMap)
   140  	return filepath.Walk("./templates", func(path string, info os.FileInfo, err error) error {
   141  		if strings.Contains(path, ".html") {
   142  			_, err = templates.ParseFiles(path)
   143  			return err
   144  		}
   145  		return err
   146  	})
   147  }
   148  
   149  var fMap = template.FuncMap{
   150  	"formatAsDate":     formatAsDate,
   151  	"formatAsDuration": formatAsDuration,
   152  }
   153  
   154  func formatAsDate(t time.Time) string {
   155  	year, month, day := t.Date()
   156  	return fmt.Sprintf("%d.%d.%d", day, month, year)
   157  }
   158  
   159  func formatAsDuration(t time.Time) string {
   160  	dur := t.Sub(time.Now())
   161  	hours := int(dur.Hours())
   162  	mins := int(dur.Minutes())
   163  
   164  	v := ""
   165  	if hours != 0 {
   166  		v += strconv.Itoa(hours) + " hours and "
   167  	}
   168  	v += strconv.Itoa(mins) + " minutes"
   169  	return v
   170  }