fix: return announcement refusals as prepare-phase send errors, test the List-Unsubscribe pair on the wire

This commit is contained in:
2026-09-14 09:37:01 +00:00
parent 9832213967
commit f1a049823e
2 changed files with 46 additions and 9 deletions
+15 -4
View File
@@ -50,20 +50,31 @@ func RenderAnnouncement(a Announcement, publicURL string) (subject, html, text s
// SendAnnouncement sends one announcement to one address, with the
// List-Unsubscribe pair Gmail and Yahoo require of bulk senders.
func (s Sender) SendAnnouncement(to string, a Announcement) error {
m, err := s.announcementFor(to, a)
if err != nil {
return err
}
return s.send(m)
}
// announcementFor builds the one-recipient message SendAnnouncement puts on
// the wire. Every refusal is a *SendError at PhasePrepare: it is about this
// one message, never the mail server, so a caller moves on to the next row.
func (s Sender) announcementFor(to string, a Announcement) (message, error) {
if strings.Contains(to, ",") {
return fmt.Errorf("mail: an announcement goes to exactly one address")
return message{}, &SendError{Phase: PhasePrepare, Err: fmt.Errorf("mail: an announcement goes to exactly one address")}
}
if a.UnsubscribeURL == "" {
return fmt.Errorf("mail: announcement without an unsubscribe URL")
return message{}, &SendError{Phase: PhasePrepare, Err: fmt.Errorf("mail: announcement without an unsubscribe URL")}
}
m, err := announcementMessage(a, s.PublicURL)
if err != nil {
return err
return message{}, &SendError{Phase: PhasePrepare, Err: err}
}
m.To = to
m.Headers = map[string]string{
"List-Unsubscribe": "<" + a.UnsubscribeURL + ">",
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click",
}
return s.send(m)
return m, nil
}