fix: return announcement refusals as prepare-phase send errors, test the List-Unsubscribe pair on the wire
This commit is contained in:
+15
-4
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package mail
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"net/mail"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
@@ -36,7 +39,7 @@ func TestRenderAnnouncement(t *testing.T) {
|
||||
t.Errorf("%s body missing %q", label, want)
|
||||
}
|
||||
}
|
||||
if strings.Contains(body, "—") {
|
||||
if strings.Contains(body, "\u2014") {
|
||||
t.Errorf("%s body contains an em dash", label)
|
||||
}
|
||||
}
|
||||
@@ -59,12 +62,35 @@ func TestRenderAnnouncementWithoutPostalAddress(t *testing.T) {
|
||||
|
||||
func TestSendAnnouncementRefusesListsAndMissingLink(t *testing.T) {
|
||||
s := Sender{Host: "smtp.invalid", From: "updates@example.com"}
|
||||
if err := s.SendAnnouncement("a@example.com, b@example.com", sampleAnnouncement()); err == nil {
|
||||
t.Error("a comma-separated To must be refused")
|
||||
prepare := func(what string, err error) {
|
||||
t.Helper()
|
||||
var se *SendError
|
||||
if !errors.As(err, &se) || se.Phase != PhasePrepare {
|
||||
t.Errorf("%s: err = %v, want a *SendError at PhasePrepare", what, err)
|
||||
}
|
||||
}
|
||||
prepare("a comma-separated To", s.SendAnnouncement("a@example.com, b@example.com", sampleAnnouncement()))
|
||||
a := sampleAnnouncement()
|
||||
a.UnsubscribeURL = ""
|
||||
if err := s.SendAnnouncement("a@example.com", a); err == nil {
|
||||
t.Error("an announcement without an unsubscribe URL must be refused")
|
||||
prepare("no unsubscribe URL", s.SendAnnouncement("a@example.com", a))
|
||||
}
|
||||
|
||||
// Gmail and Yahoo require both headers of the RFC 8058 pair on bulk mail.
|
||||
func TestSendAnnouncementPutsListUnsubscribeOnTheWire(t *testing.T) {
|
||||
port, got := fakeSMTPData(t, nil)
|
||||
s := Sender{Host: "127.0.0.1", Port: port, From: "updates@example.com"}
|
||||
a := sampleAnnouncement()
|
||||
if err := s.SendAnnouncement("a@example.com", a); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
msg, err := mail.ReadMessage(bytes.NewReader(<-got))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if h := msg.Header.Get("List-Unsubscribe"); h != "<"+a.UnsubscribeURL+">" {
|
||||
t.Errorf("List-Unsubscribe = %q", h)
|
||||
}
|
||||
if h := msg.Header.Get("List-Unsubscribe-Post"); h != "List-Unsubscribe=One-Click" {
|
||||
t.Errorf("List-Unsubscribe-Post = %q", h)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user