From 2e96d9490ed5d7833819a143dd12ae7c104795bf Mon Sep 17 00:00:00 2001 From: Astra Date: Fri, 11 Sep 2026 17:39:55 +0100 Subject: [PATCH] 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 --- internal/otpdelivery/smtp/sender.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/internal/otpdelivery/smtp/sender.go b/internal/otpdelivery/smtp/sender.go index d3e07545..90267cd3 100644 --- a/internal/otpdelivery/smtp/sender.go +++ b/internal/otpdelivery/smtp/sender.go @@ -3,7 +3,9 @@ package smtp import ( "bytes" "context" + "crypto/rand" "crypto/tls" + "encoding/hex" "fmt" "mime" "net" @@ -146,6 +148,8 @@ func buildMessage(from, to, subject, body string) []byte { var b bytes.Buffer b.WriteString("From: " + from + "\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("MIME-Version: 1.0\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() } +// 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 // operator's configured product name (Config.AppName) instead of the // package's internal "telesrv" fallback.