130 lines
3.0 KiB
Go
130 lines
3.0 KiB
Go
|
package mailer
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"crypto/tls"
|
||
|
"crypto/x509"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"github.com/emersion/go-message"
|
||
|
"github.com/emersion/go-sasl"
|
||
|
"github.com/emersion/go-smtp"
|
||
|
"io"
|
||
|
"os"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
type Mailer struct {
|
||
|
auth sasl.Client
|
||
|
conf SmtpConfig
|
||
|
pool *x509.CertPool
|
||
|
}
|
||
|
|
||
|
func NewMailer(conf SmtpConfig) (*Mailer, error) {
|
||
|
var pool *x509.CertPool = nil
|
||
|
if conf.Cert != "" {
|
||
|
pool = x509.NewCertPool()
|
||
|
open, err := os.Open(conf.Cert)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
all, err := io.ReadAll(open)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
if !pool.AppendCertsFromPEM(all) {
|
||
|
return nil, errors.New("invalid SMTP certificate")
|
||
|
}
|
||
|
}
|
||
|
return &Mailer{
|
||
|
auth: sasl.NewPlainClient("", conf.Username, conf.Password),
|
||
|
conf: conf,
|
||
|
pool: pool,
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
func (m *Mailer) MailHtml(subject string, to []string, body io.Reader) error {
|
||
|
h := message.Header{}
|
||
|
h.SetContentType("multipart/alternative", nil)
|
||
|
h.SetText("From", m.conf.From)
|
||
|
h.SetText("To", strings.Join(to, ", "))
|
||
|
h.SetText("Subject", subject)
|
||
|
h.SetText("MIME-version", "1.0")
|
||
|
|
||
|
msg := new(bytes.Buffer)
|
||
|
w, err := message.CreateWriter(msg, h)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to create message writer: %w", err)
|
||
|
}
|
||
|
|
||
|
h2 := message.Header{}
|
||
|
h2.SetContentType("text/html", map[string]string{"charset": "UTF-8"})
|
||
|
|
||
|
part, err := w.CreatePart(h2)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to create part: %w", err)
|
||
|
}
|
||
|
_, err = io.Copy(part, body)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to copy body into part: %w", err)
|
||
|
}
|
||
|
err = part.Close()
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to close part: %w", err)
|
||
|
}
|
||
|
err = w.Close()
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to close message writer: %w", err)
|
||
|
}
|
||
|
|
||
|
return m.sendMail(to, msg)
|
||
|
}
|
||
|
|
||
|
func (m *Mailer) sendMail(to []string, body io.Reader) error {
|
||
|
var err error
|
||
|
var client *smtp.Client
|
||
|
if m.conf.Tls {
|
||
|
client, err = smtp.DialTLS(m.conf.Server, &tls.Config{RootCAs: m.pool})
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to dial tls: %w", err)
|
||
|
}
|
||
|
} else {
|
||
|
client, err = smtp.Dial(m.conf.Server)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to dial: %w", err)
|
||
|
}
|
||
|
}
|
||
|
if m.conf.StartTls {
|
||
|
if ok, _ := client.Extension("STARTTLS"); ok {
|
||
|
if err = client.StartTLS(nil); err != nil {
|
||
|
return fmt.Errorf("failed to start tls: %w", err)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if err = client.Auth(m.auth); err != nil {
|
||
|
return fmt.Errorf("failed to add auth: %w", err)
|
||
|
}
|
||
|
if err = client.Mail(m.conf.Username, nil); err != nil {
|
||
|
return fmt.Errorf("failed to start mail: %w", err)
|
||
|
}
|
||
|
for _, i := range to {
|
||
|
if err = client.Rcpt(i); err != nil {
|
||
|
return fmt.Errorf("failed to add recipient: %w", err)
|
||
|
}
|
||
|
}
|
||
|
wc, err := client.Data()
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to start mail data: %w", err)
|
||
|
}
|
||
|
if _, err = io.Copy(wc, body); err != nil {
|
||
|
return fmt.Errorf("failed to : %w", err)
|
||
|
}
|
||
|
if err = wc.Close(); err != nil {
|
||
|
return fmt.Errorf("failed to close: %w", err)
|
||
|
}
|
||
|
if err = client.Quit(); err != nil {
|
||
|
return fmt.Errorf("failed to quit: %w", err)
|
||
|
}
|
||
|
return nil
|
||
|
}
|