Improve customisability of headers when using simplemail

This commit is contained in:
Melon 2025-03-12 14:29:13 +00:00
parent 0141a7426e
commit fccaef81bc
Signed by: melon
GPG Key ID: 6C9D970C50D26A25
4 changed files with 97 additions and 51 deletions

View File

@ -22,7 +22,6 @@ func (f *FromAddress) UnmarshalText(b []byte) error {
return nil return nil
} }
func (f FromAddress) ToMailAddress() *mail.Address { func (f *FromAddress) ToMailAddress() *mail.Address {
m := mail.Address(f) return (*mail.Address)(f)
return &m
} }

50
mail.go
View File

@ -1,14 +1,12 @@
package simplemail package simplemail
import ( import (
"bytes"
"context" "context"
"github.com/emersion/go-message/mail" "github.com/emersion/go-message/mail"
"github.com/emersion/go-sasl" "github.com/emersion/go-sasl"
"github.com/emersion/go-smtp" "github.com/emersion/go-smtp"
"io" "io"
"net" "net"
"time"
) )
type Mail struct { type Mail struct {
@ -69,51 +67,17 @@ func isLocalhost(host string) bool {
return true return true
} }
func (m *Mail) SendMail(subject string, to []*mail.Address, htmlBody, textBody io.Reader) error { // SendMail sends a mail message to the provided mail addresses.
// generate the email in this template //
buf := new(bytes.Buffer) // The reader should follow the format of an RFC 822-style email.
//
// setup mail headers // See github.com/emersion/go-smtp.SendMail
var h mail.Header func (m *Mail) SendMail(to []*mail.Address, mailMessage io.Reader) error {
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
}
// convert all to addresses to strings // convert all to addresses to strings
toStr := make([]string, len(to)) toStr := make([]string, len(to))
for i := range toStr { for i := range toStr {
toStr[i] = to[i].String() toStr[i] = to[i].String()
} }
return m.mailCall(toStr, buf) return m.mailCall(toStr, mailMessage)
} }

62
preparedmail.go Normal file
View File

@ -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()
}

View File

@ -1,13 +1,13 @@
package simplemail package simplemail
import ( import (
"bytes"
"github.com/emersion/go-message/mail" "github.com/emersion/go-message/mail"
htmlTemplate "html/template" htmlTemplate "html/template"
"io" "io"
"io/fs" "io/fs"
"log" "log"
textTemplate "text/template" textTemplate "text/template"
"time"
) )
type SimpleMail struct { 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 { // PrepareSingle constructs the headers for sending an email to the provided mail address.
var bufHtml, bufTxt bytes.Buffer func (m *SimpleMail) PrepareSingle(templateName, subject string, to *mail.Address, data map[string]any) *PreparedMail {
m.render(&bufHtml, &bufTxt, templateName, data) return m.PrepareMany(templateName, subject, []*mail.Address{to}, data)
return m.mailSender.SendMail(subject, []*mail.Address{to}, &bufHtml, &bufTxt) }
// 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()
} }