smtp: add Date and Message-ID headers to outgoing mail
RFC 5322 requires both headers. Date uses RFC1123Z formatting; Message-ID is a random 16-byte token scoped to the sending domain parsed from From. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
f1c24e483c
commit
3289882dbd
1 changed files with 23 additions and 0 deletions
|
|
@ -3,7 +3,9 @@ package smtp
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"mime"
|
"mime"
|
||||||
"net"
|
"net"
|
||||||
|
|
@ -146,6 +148,8 @@ func buildMessage(from, to, subject, body string) []byte {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
b.WriteString("From: " + from + "\r\n")
|
b.WriteString("From: " + from + "\r\n")
|
||||||
b.WriteString("To: " + to + "\r\n")
|
b.WriteString("To: " + to + "\r\n")
|
||||||
|
b.WriteString("Date: " + time.Now().Format(time.RFC1123Z) + "\r\n")
|
||||||
|
b.WriteString("Message-ID: " + generateMessageID(from) + "\r\n")
|
||||||
b.WriteString("Subject: " + mime.QEncoding.Encode("utf-8", subject) + "\r\n")
|
b.WriteString("Subject: " + mime.QEncoding.Encode("utf-8", subject) + "\r\n")
|
||||||
b.WriteString("MIME-Version: 1.0\r\n")
|
b.WriteString("MIME-Version: 1.0\r\n")
|
||||||
b.WriteString("Content-Type: text/plain; charset=utf-8\r\n")
|
b.WriteString("Content-Type: text/plain; charset=utf-8\r\n")
|
||||||
|
|
@ -155,6 +159,25 @@ func buildMessage(from, to, subject, body string) []byte {
|
||||||
return b.Bytes()
|
return b.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// generateMessageID builds a Message-ID header value (RFC 5322 3.6.4), using
|
||||||
|
// the sending domain parsed out of the From address and a random token so
|
||||||
|
// each message gets a unique id even under concurrent sends.
|
||||||
|
func generateMessageID(from string) string {
|
||||||
|
domain := "localhost"
|
||||||
|
if addr, err := stdmail.ParseAddress(from); err == nil {
|
||||||
|
if i := strings.LastIndex(addr.Address, "@"); i >= 0 {
|
||||||
|
domain = addr.Address[i+1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var raw [16]byte
|
||||||
|
if _, err := rand.Read(raw[:]); err != nil {
|
||||||
|
// crypto/rand failing is effectively unheard of, but fall back to a
|
||||||
|
// time-based token rather than emit a non-unique Message-ID.
|
||||||
|
return fmt.Sprintf("<%d@%s>", time.Now().UnixNano(), domain)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("<%s@%s>", hex.EncodeToString(raw[:]), domain)
|
||||||
|
}
|
||||||
|
|
||||||
// emailContent builds the login-code email subject/body, branded with the
|
// emailContent builds the login-code email subject/body, branded with the
|
||||||
// operator's configured product name (Config.AppName) instead of the
|
// operator's configured product name (Config.AppName) instead of the
|
||||||
// package's internal "telesrv" fallback.
|
// package's internal "telesrv" fallback.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue