diff --git a/fromaddress.go b/fromaddress.go index 11a1c26..a5bf8e8 100644 --- a/fromaddress.go +++ b/fromaddress.go @@ -22,7 +22,6 @@ func (f *FromAddress) UnmarshalText(b []byte) error { return nil } -func (f FromAddress) ToMailAddress() *mail.Address { - m := mail.Address(f) - return &m +func (f *FromAddress) ToMailAddress() *mail.Address { + return (*mail.Address)(f) } diff --git a/mail.go b/mail.go index 9866914..c640f12 100644 --- a/mail.go +++ b/mail.go @@ -1,14 +1,12 @@ package simplemail import ( - "bytes" "context" "github.com/emersion/go-message/mail" "github.com/emersion/go-sasl" "github.com/emersion/go-smtp" "io" "net" - "time" ) type Mail struct { @@ -69,51 +67,17 @@ func isLocalhost(host string) bool { return true } -func (m *Mail) SendMail(subject string, to []*mail.Address, htmlBody, textBody io.Reader) error { - // generate the email in this template - buf := new(bytes.Buffer) - - // setup mail headers - var h mail.Header - h.SetDate(time.Now()) - h.SetSubject(subject) - h.SetAddressList("From", []*mail.Address{m.From.ToMailAddress()}) - h.SetAddressList("To", to) - h.Set("Content-Type", "multipart/alternative") - - // setup html and text alternative headers - var hHtml, hTxt mail.InlineHeader - hHtml.Set("Content-Type", "text/html; charset=utf-8") - hTxt.Set("Content-Type", "text/plain; charset=utf-8") - - createWriter, err := mail.CreateWriter(buf, h) - if err != nil { - return err - } - inline, err := createWriter.CreateInline() - if err != nil { - return err - } - partHtml, err := inline.CreatePart(hHtml) - if err != nil { - return err - } - if _, err := io.Copy(partHtml, htmlBody); err != nil { - return err - } - partTxt, err := inline.CreatePart(hTxt) - if err != nil { - return err - } - if _, err := io.Copy(partTxt, textBody); err != nil { - return err - } - +// SendMail sends a mail message to the provided mail addresses. +// +// The reader should follow the format of an RFC 822-style email. +// +// See github.com/emersion/go-smtp.SendMail +func (m *Mail) SendMail(to []*mail.Address, mailMessage io.Reader) error { // convert all to addresses to strings toStr := make([]string, len(to)) for i := range toStr { toStr[i] = to[i].String() } - return m.mailCall(toStr, buf) + return m.mailCall(toStr, mailMessage) } diff --git a/preparedmail.go b/preparedmail.go new file mode 100644 index 0000000..82498fc --- /dev/null +++ b/preparedmail.go @@ -0,0 +1,62 @@ +package simplemail + +import ( + "bytes" + "github.com/emersion/go-message/mail" + "io" +) + +type PreparedMail struct { + simpleMail *SimpleMail + templateName string + Header mail.Header + rcpt []*mail.Address + data map[string]any +} + +func (p *PreparedMail) SendMail() error { + buf := new(bytes.Buffer) + + var bufHtml, bufTxt bytes.Buffer + p.simpleMail.render(&bufHtml, &bufTxt, p.templateName, p.data) + + // setup html and text alternative headers + var hHtml, hTxt mail.InlineHeader + hHtml.Set("Content-Type", "text/html; charset=utf-8") + hTxt.Set("Content-Type", "text/plain; charset=utf-8") + + createWriter, err := mail.CreateWriter(buf, p.Header) + if err != nil { + return err + } + inline, err := createWriter.CreateInline() + if err != nil { + return err + } + err = copyToPart(inline, hHtml, &bufHtml) + if err != nil { + return err + } + err = copyToPart(inline, hTxt, &bufTxt) + if err != nil { + return err + } + err = inline.Close() + if err != nil { + return err + } + + return p.simpleMail.mailSender.SendMail(p.rcpt, buf) +} + +func copyToPart(inline *mail.InlineWriter, header mail.InlineHeader, body io.Reader) error { + part, err := inline.CreatePart(header) + if err != nil { + return err + } + _, err = io.Copy(part, body) + if err != nil { + return err + } + return part.Close() +} diff --git a/simplemail.go b/simplemail.go index 9ee70cb..10abc66 100644 --- a/simplemail.go +++ b/simplemail.go @@ -1,13 +1,13 @@ package simplemail import ( - "bytes" "github.com/emersion/go-message/mail" htmlTemplate "html/template" "io" "io/fs" "log" textTemplate "text/template" + "time" ) type SimpleMail struct { @@ -40,8 +40,29 @@ func (m *SimpleMail) render(wrHtml, wrTxt io.Writer, name string, data any) { } } -func (m *SimpleMail) Send(templateName, subject string, to *mail.Address, data map[string]any) error { - var bufHtml, bufTxt bytes.Buffer - m.render(&bufHtml, &bufTxt, templateName, data) - return m.mailSender.SendMail(subject, []*mail.Address{to}, &bufHtml, &bufTxt) +// PrepareSingle constructs the headers for sending an email to the provided mail address. +func (m *SimpleMail) PrepareSingle(templateName, subject string, to *mail.Address, data map[string]any) *PreparedMail { + return m.PrepareMany(templateName, subject, []*mail.Address{to}, data) +} + +// PrepareMany constructs the headers for sending an email to the provided mail addresses. +func (m *SimpleMail) PrepareMany(templateName, subject string, to []*mail.Address, data map[string]any) *PreparedMail { + p := &PreparedMail{ + simpleMail: m, + templateName: templateName, + rcpt: to, + data: data, + } + p.Header.SetDate(time.Now()) + p.Header.SetSubject(subject) + p.Header.SetAddressList("From", []*mail.Address{m.mailSender.From.ToMailAddress()}) + p.Header.SetAddressList("To", to) + p.Header.Set("Content-Type", "multipart/alternative") + return p +} + +// Send is a simplified version of PrepareSingle which sends the mail without allowing header modifications. +func (m *SimpleMail) Send(templateName, subject string, to *mail.Address, data map[string]any) error { + p := m.PrepareSingle(templateName, subject, to, data) + return p.SendMail() }