repos / pico

pico services mono repo
git clone https://github.com/picosh/pico.git

commit
0a997c5
parent
d80b355
author
Eric Bower
date
2025-08-30 08:22:02 -0400 EDT
chore(feeds): better formatting for smtp emails
1 files changed,  +23, -11
M pkg/apps/feeds/cron.go
+23, -11
  1@@ -132,11 +132,14 @@ type Fetcher struct {
  2 	db   db.DB
  3 	auth sasl.Client
  4 	gron *gronx.Gronx
  5+	host string
  6 }
  7 
  8 func NewFetcher(dbpool db.DB, cfg *shared.ConfigSite) *Fetcher {
  9+	host := os.Getenv("PICO_SMTP_HOST")
 10 	smtPass := os.Getenv("PICO_SMTP_PASS")
 11 	emailLogin := os.Getenv("PICO_SMTP_USER")
 12+
 13 	auth := sasl.NewPlainClient("", emailLogin, smtPass)
 14 	gron := gronx.New()
 15 	return &Fetcher{
 16@@ -144,6 +147,7 @@ func NewFetcher(dbpool db.DB, cfg *shared.ConfigSite) *Fetcher {
 17 		cfg:  cfg,
 18 		auth: auth,
 19 		gron: gron,
 20+		host: host,
 21 	}
 22 }
 23 
 24@@ -253,7 +257,8 @@ func (f *Fetcher) RunPost(logger *slog.Logger, user *db.User, post *db.Post, ski
 25 		return err
 26 	}
 27 
 28-	subject := fmt.Sprintf("%s feed digest", post.Title)
 29+	subject := fmt.Sprintf("%s feed digest", post.Filename)
 30+	unsubURL := getUnsubURL(post)
 31 
 32 	msgBody, err := f.FetchAll(logger, urls, parsed.InlineContent, user.Name, post)
 33 	if err != nil {
 34@@ -277,9 +282,11 @@ Also, we have centralized logs in our pico.sh TUI that will display realtime fee
 35 
 36 %s`, post.Data.Attempts, maxAttempts, errForUser.Error(), post.Text)
 37 		err = f.SendEmail(
 38-			logger, user.Name,
 39+			logger,
 40+			user.Name,
 41 			parsed.Email,
 42 			subject,
 43+			unsubURL,
 44 			&MsgBody{Html: strings.ReplaceAll(errBody, "\n", "<br />"), Text: errBody},
 45 		)
 46 		if err != nil {
 47@@ -307,7 +314,7 @@ Also, we have centralized logs in our pico.sh TUI that will display realtime fee
 48 	}
 49 
 50 	if msgBody != nil {
 51-		err = f.SendEmail(logger, user.Name, parsed.Email, subject, msgBody)
 52+		err = f.SendEmail(logger, user.Name, parsed.Email, subject, unsubURL, msgBody)
 53 		if err != nil {
 54 			return err
 55 		}
 56@@ -460,6 +467,10 @@ type MsgBody struct {
 57 	Text string
 58 }
 59 
 60+func getUnsubURL(post *db.Post) string {
 61+	return fmt.Sprintf("https://feeds.pico.sh/unsub/%s", post.ID)
 62+}
 63+
 64 func (f *Fetcher) FetchAll(logger *slog.Logger, urls []string, inlineContent bool, username string, post *db.Post) (*MsgBody, error) {
 65 	logger.Info("fetching feeds", "inlineContent", inlineContent)
 66 	fp := gofeed.NewParser()
 67@@ -475,7 +486,7 @@ func (f *Fetcher) FetchAll(logger *slog.Logger, urls []string, inlineContent boo
 68 	}
 69 	feeds := &DigestFeed{
 70 		KeepAliveURL: fmt.Sprintf("https://feeds.pico.sh/keep-alive/%s", post.ID),
 71-		UnsubURL:     fmt.Sprintf("https://feeds.pico.sh/unsub/%s", post.ID),
 72+		UnsubURL:     getUnsubURL(post),
 73 		DaysLeft:     daysLeft,
 74 		ShowBanner:   showBanner,
 75 		Options:      DigestOptions{InlineContent: inlineContent},
 76@@ -564,19 +575,20 @@ func (f *Fetcher) FetchAll(logger *slog.Logger, urls []string, inlineContent boo
 77 	}, nil
 78 }
 79 
 80-func (f *Fetcher) SendEmail(logger *slog.Logger, username, email, subject string, msg *MsgBody) error {
 81+func (f *Fetcher) SendEmail(logger *slog.Logger, username, email, subject, unsubURL string, msg *MsgBody) error {
 82 	if email == "" {
 83 		return fmt.Errorf("(%s) does not have an email associated with their feed post", username)
 84 	}
 85-	smtpAddr := "smtp.fastmail.com:587"
 86+	smtpAddr := f.host
 87 	fromEmail := "hello@pico.sh"
 88 	to := []string{email}
 89 	headers := map[string]string{
 90-		"From":         fromEmail,
 91-		"To":           email,
 92-		"Subject":      subject,
 93-		"MIME-Version": "1.0",
 94-		"Content-Type": `multipart/alternative; boundary="boundary123"`,
 95+		"From":             fromEmail,
 96+		"Subject":          subject,
 97+		"To":               email,
 98+		"MIME-Version":     "1.0",
 99+		"Content-Type":     `multipart/alternative; boundary="boundary123"`,
100+		"List-Unsubscribe": "<" + unsubURL + ">",
101 	}
102 	var content strings.Builder
103 	for k, v := range headers {