fix: send email parts as quoted-printable so they are 7-bit clean and DKIM-signed
This commit is contained in:
+27
-15
@@ -14,6 +14,7 @@ import (
|
||||
"fmt"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
"mime/quotedprintable"
|
||||
"net"
|
||||
"net/smtp"
|
||||
"net/textproto"
|
||||
@@ -180,27 +181,21 @@ func recipients(to string) []string {
|
||||
// text part is sent beside every HTML one for the same reason, and because a
|
||||
// client that refuses HTML should not receive a blank message. Header values
|
||||
// are stripped of CR and LF so a crafted instance name cannot inject headers.
|
||||
//
|
||||
// Both parts are quoted-printable. They were raw UTF-8 with no
|
||||
// Content-Transfer-Encoding, which means 7bit, and every template carries
|
||||
// non-ASCII (the middot in the masthead at least). rspamd scored that
|
||||
// R_BAD_CTE_7BIT, the message went out without a DKIM signature, and Gmail
|
||||
// filed it as spam, while mail from the same mailbox via SOGo, sent
|
||||
// quoted-printable, was signed and delivered.
|
||||
func (s Sender) envelope(m message) ([]byte, error) {
|
||||
var parts strings.Builder
|
||||
w := multipart.NewWriter(&parts)
|
||||
|
||||
textPart, err := w.CreatePart(textproto.MIMEHeader{
|
||||
"Content-Type": {"text/plain; charset=utf-8"},
|
||||
})
|
||||
if err != nil {
|
||||
if err := writeQPPart(w, "text/plain; charset=utf-8", m.Text); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := textPart.Write([]byte(m.Text)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
htmlPart, err := w.CreatePart(textproto.MIMEHeader{
|
||||
"Content-Type": {"text/html; charset=utf-8"},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := htmlPart.Write([]byte(m.HTML)); err != nil {
|
||||
if err := writeQPPart(w, "text/html; charset=utf-8", m.HTML); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -224,6 +219,23 @@ func (s Sender) envelope(m message) ([]byte, error) {
|
||||
return []byte(b.String()), nil
|
||||
}
|
||||
|
||||
// writeQPPart adds one quoted-printable part, so the wire stays 7-bit and
|
||||
// lines stay under SMTP's 998-byte limit whatever the template produced.
|
||||
func writeQPPart(w *multipart.Writer, contentType, body string) error {
|
||||
part, err := w.CreatePart(textproto.MIMEHeader{
|
||||
"Content-Type": {contentType},
|
||||
"Content-Transfer-Encoding": {"quoted-printable"},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
qp := quotedprintable.NewWriter(part)
|
||||
if _, err := qp.Write([]byte(body)); err != nil {
|
||||
return err
|
||||
}
|
||||
return qp.Close()
|
||||
}
|
||||
|
||||
func messageID(from string) string {
|
||||
domain := "vantage.local"
|
||||
if at := strings.LastIndex(from, "@"); at >= 0 && at < len(from)-1 {
|
||||
|
||||
Reference in New Issue
Block a user