91 lines
2.0 KiB
Go
91 lines
2.0 KiB
Go
package mailer
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"mime"
|
|
"mime/quotedprintable"
|
|
"net/mail"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
senderDisplayName = "RenewCMS"
|
|
messageIDBytes = 16
|
|
lineBreak = "\r\n"
|
|
)
|
|
|
|
type message struct {
|
|
from mail.Address
|
|
to mail.Address
|
|
subject string
|
|
body string
|
|
sentAt time.Time
|
|
}
|
|
|
|
func newMessage(sender string, receiver string, subject string, body string, sentAt time.Time) (message, error) {
|
|
from, err := mail.ParseAddress(sender)
|
|
if err != nil {
|
|
return message{}, fmt.Errorf("invalid sender address: %w", err)
|
|
}
|
|
to, err := mail.ParseAddress(receiver)
|
|
if err != nil {
|
|
return message{}, fmt.Errorf("invalid receiver address: %w", err)
|
|
}
|
|
if strings.ContainsAny(subject, "\r\n") {
|
|
return message{}, errors.New("subject contains a line break")
|
|
}
|
|
return message{
|
|
from: mail.Address{Name: senderDisplayName, Address: from.Address},
|
|
to: *to,
|
|
subject: subject,
|
|
body: body,
|
|
sentAt: sentAt,
|
|
}, nil
|
|
}
|
|
|
|
func (m message) messageID() (string, error) {
|
|
_, domain, _ := strings.Cut(m.from.Address, "@")
|
|
random := make([]byte, messageIDBytes)
|
|
if _, err := rand.Read(random); err != nil {
|
|
return "", err
|
|
}
|
|
return "<" + hex.EncodeToString(random) + "@" + domain + ">", nil
|
|
}
|
|
|
|
func (m message) bytes() ([]byte, error) {
|
|
id, err := m.messageID()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var out bytes.Buffer
|
|
headers := []struct{ name, value string }{
|
|
{"From", m.from.String()},
|
|
{"To", m.to.String()},
|
|
{"Subject", mime.QEncoding.Encode("utf-8", m.subject)},
|
|
{"Date", m.sentAt.Format(time.RFC1123Z)},
|
|
{"Message-ID", id},
|
|
{"MIME-Version", "1.0"},
|
|
{"Content-Type", "text/html; charset=UTF-8"},
|
|
{"Content-Transfer-Encoding", "quoted-printable"},
|
|
}
|
|
for _, h := range headers {
|
|
out.WriteString(h.name + ": " + h.value + lineBreak)
|
|
}
|
|
out.WriteString(lineBreak)
|
|
|
|
encoder := quotedprintable.NewWriter(&out)
|
|
if _, err := encoder.Write([]byte(m.body)); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := encoder.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
return out.Bytes(), nil
|
|
}
|