feat(mail): derive bare envelope sender from display-name From
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+13
-1
@@ -46,6 +46,18 @@ func (m OutgoingMessage) Recipients() []string {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// envelopeFrom returns the bare address for the SMTP envelope sender, stripping
|
||||||
|
// any display name. A display-name From (e.g. "Name <addr>") is a valid header
|
||||||
|
// but an invalid envelope sender, so it must be reduced to the bare address.
|
||||||
|
// Unparseable input is passed through unchanged (preserves prior behaviour for
|
||||||
|
// plain addresses).
|
||||||
|
func envelopeFrom(from string) string {
|
||||||
|
if a, err := gomail.ParseAddress(from); err == nil {
|
||||||
|
return a.Address
|
||||||
|
}
|
||||||
|
return from
|
||||||
|
}
|
||||||
|
|
||||||
func addrList(addrs []string) []*gomail.Address {
|
func addrList(addrs []string) []*gomail.Address {
|
||||||
out := make([]*gomail.Address, 0, len(addrs))
|
out := make([]*gomail.Address, 0, len(addrs))
|
||||||
for _, a := range addrs {
|
for _, a := range addrs {
|
||||||
@@ -163,7 +175,7 @@ func SendSMTP(cfg SMTPConfig, m OutgoingMessage) error {
|
|||||||
if err := c.Auth(auth); err != nil {
|
if err := c.Auth(auth); err != nil {
|
||||||
return fmt.Errorf("smtp auth: %w", err)
|
return fmt.Errorf("smtp auth: %w", err)
|
||||||
}
|
}
|
||||||
if err := c.SendMail(m.From, m.Recipients(), bytes.NewReader(raw)); err != nil {
|
if err := c.SendMail(envelopeFrom(m.From), m.Recipients(), bytes.NewReader(raw)); err != nil {
|
||||||
return fmt.Errorf("smtp send: %w", err)
|
return fmt.Errorf("smtp send: %w", err)
|
||||||
}
|
}
|
||||||
return c.Quit()
|
return c.Quit()
|
||||||
|
|||||||
@@ -100,6 +100,36 @@ func TestRecipientsCombinesAllFields(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEnvelopeFromStripsDisplayName(t *testing.T) {
|
||||||
|
cases := map[string]string{
|
||||||
|
"Steve Cliff <me@stevecliff.com>": "me@stevecliff.com",
|
||||||
|
"me@stevecliff.com": "me@stevecliff.com",
|
||||||
|
"<me@stevecliff.com>": "me@stevecliff.com",
|
||||||
|
"not a valid address": "not a valid address", // unparseable ⇒ passthrough
|
||||||
|
}
|
||||||
|
for in, want := range cases {
|
||||||
|
if got := envelopeFrom(in); got != want {
|
||||||
|
t.Fatalf("envelopeFrom(%q) = %q, want %q", in, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildMIMEKeepsDisplayNameInHeader(t *testing.T) {
|
||||||
|
raw, err := BuildMIME(OutgoingMessage{
|
||||||
|
From: "Steve Cliff <me@stevecliff.com>",
|
||||||
|
To: []string{"you@example.com"},
|
||||||
|
Subject: "hi",
|
||||||
|
BodyText: "body",
|
||||||
|
Date: time.Date(2026, 6, 23, 12, 0, 0, 0, time.UTC),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("BuildMIME: %v", err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(raw), "Steve Cliff") {
|
||||||
|
t.Fatalf("From header lost display name:\n%s", raw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestReadHeaderParsesReferences(t *testing.T) {
|
func TestReadHeaderParsesReferences(t *testing.T) {
|
||||||
raw := "From: a@x.com\r\n" +
|
raw := "From: a@x.com\r\n" +
|
||||||
"To: b@x.com\r\n" +
|
"To: b@x.com\r\n" +
|
||||||
|
|||||||
Reference in New Issue
Block a user