feat: announcement email with one-click unsubscribe headers
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
package mail
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
htmltmpl "html/template"
|
||||
"strings"
|
||||
|
||||
"github.com/yuin/goldmark"
|
||||
)
|
||||
|
||||
// Announcement is one product email to one person: a feature launch, a guide,
|
||||
// an offer. vantage-admin's announce package builds it per recipient, because
|
||||
// the unsubscribe links carry that person's token.
|
||||
type Announcement struct {
|
||||
CategoryLabel string // "New features"
|
||||
Subject string
|
||||
BodyMarkdown string
|
||||
UnsubscribeURL string // RFC 8058 one-click target for this category
|
||||
PreferencesURL string // the no-login preferences page
|
||||
PostalAddress string // optional footer line
|
||||
}
|
||||
|
||||
type announcementData struct {
|
||||
Announcement
|
||||
BodyHTML htmltmpl.HTML
|
||||
}
|
||||
|
||||
// md renders with goldmark's defaults, which omit raw HTML: a pasted <script>
|
||||
// never reaches an inbox.
|
||||
var md = goldmark.New()
|
||||
|
||||
func announcementMessage(a Announcement, publicURL string) (message, error) {
|
||||
var buf bytes.Buffer
|
||||
if err := md.Convert([]byte(a.BodyMarkdown), &buf); err != nil {
|
||||
return message{}, fmt.Errorf("mail: markdown: %w", err)
|
||||
}
|
||||
return render("announcement", announcementData{Announcement: a, BodyHTML: htmltmpl.HTML(buf.String())}, publicURL)
|
||||
}
|
||||
|
||||
// RenderAnnouncement is the staff preview: exactly what a recipient gets.
|
||||
func RenderAnnouncement(a Announcement, publicURL string) (subject, html, text string, err error) {
|
||||
m, err := announcementMessage(a, publicURL)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
return m.Subject, m.HTML, m.Text, nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if strings.Contains(to, ",") {
|
||||
return fmt.Errorf("mail: an announcement goes to exactly one address")
|
||||
}
|
||||
if a.UnsubscribeURL == "" {
|
||||
return fmt.Errorf("mail: announcement without an unsubscribe URL")
|
||||
}
|
||||
m, err := announcementMessage(a, s.PublicURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.To = to
|
||||
m.Headers = map[string]string{
|
||||
"List-Unsubscribe": "<" + a.UnsubscribeURL + ">",
|
||||
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click",
|
||||
}
|
||||
return s.send(m)
|
||||
}
|
||||
Reference in New Issue
Block a user