rss email digests over ssh because you're a cool kid herald.dunkirk.sh
go rss rss-reader ssh charm

feat: add profile to the emails

dunkirk.sh cfca0d55 1a3a518a

verified
Changed files
+59 -22
email
scheduler
+27 -10
email/send.go
··· 30 30 } 31 31 } 32 32 33 - func (m *Mailer) Send(to, subject, htmlBody, textBody, unsubToken string) error { 33 + func (m *Mailer) Send(to, subject, htmlBody, textBody, unsubToken, dashboardURL string) error { 34 34 addr := net.JoinHostPort(m.cfg.Host, fmt.Sprintf("%d", m.cfg.Port)) 35 35 36 36 boundary := "==herald-boundary-a1b2c3d4e5f6==" 37 37 38 - // Add unsubscribe footer if token provided 39 - if unsubToken != "" { 40 - unsubURL := m.unsubBaseURL + "/unsubscribe/" + unsubToken 41 - 42 - htmlFooter := fmt.Sprintf(`<hr><p style="font-size: 12px; color: #666;"><a href="%s">Unsubscribe</a></p>`, unsubURL) 43 - htmlBody = htmlBody + htmlFooter 44 - 45 - textFooter := fmt.Sprintf("\n\n---\nUnsubscribe: %s\n", unsubURL) 46 - textBody = textBody + textFooter 38 + // Add footer with unsubscribe and dashboard links 39 + var htmlFooter strings.Builder 40 + var textFooter strings.Builder 41 + 42 + if unsubToken != "" || dashboardURL != "" { 43 + htmlFooter.WriteString(`<hr><p style="font-size: 12px; color: #666;">`) 44 + textFooter.WriteString("\n\n---\n") 45 + 46 + if dashboardURL != "" { 47 + htmlFooter.WriteString(fmt.Sprintf(`<a href="%s">profile</a>`, dashboardURL)) 48 + textFooter.WriteString(fmt.Sprintf("profile: %s\n", dashboardURL)) 49 + } 50 + 51 + if unsubToken != "" { 52 + unsubURL := m.unsubBaseURL + "/unsubscribe/" + unsubToken 53 + if dashboardURL != "" { 54 + htmlFooter.WriteString(" • ") 55 + textFooter.WriteString("") 56 + } 57 + htmlFooter.WriteString(fmt.Sprintf(`<a href="%s">Unsubscribe</a>`, unsubURL)) 58 + textFooter.WriteString(fmt.Sprintf("unsubscribe: %s\n", unsubURL)) 59 + } 60 + 61 + htmlFooter.WriteString("</p>") 62 + htmlBody = htmlBody + htmlFooter.String() 63 + textBody = textBody + textFooter.String() 47 64 } 48 65 49 66 headers := make(map[string]string)
+1 -1
main.go
··· 151 151 From: cfg.SMTP.From, 152 152 }, cfg.Origin) 153 153 154 - sched := scheduler.NewScheduler(db, mailer, logger, 60*time.Second) 154 + sched := scheduler.NewScheduler(db, mailer, logger, 60*time.Second, cfg.Origin) 155 155 156 156 sshServer := ssh.NewServer(ssh.Config{ 157 157 Host: cfg.Host,
+31 -11
scheduler/scheduler.go
··· 12 12 ) 13 13 14 14 type Scheduler struct { 15 - store *store.DB 16 - mailer *email.Mailer 17 - logger *log.Logger 18 - interval time.Duration 15 + store *store.DB 16 + mailer *email.Mailer 17 + logger *log.Logger 18 + interval time.Duration 19 + originURL string 19 20 } 20 21 21 - func NewScheduler(st *store.DB, mailer *email.Mailer, logger *log.Logger, interval time.Duration) *Scheduler { 22 + func NewScheduler(st *store.DB, mailer *email.Mailer, logger *log.Logger, interval time.Duration, originURL string) *Scheduler { 22 23 return &Scheduler{ 23 - store: st, 24 - mailer: mailer, 25 - logger: logger, 26 - interval: interval, 24 + store: st, 25 + mailer: mailer, 26 + logger: logger, 27 + interval: interval, 28 + originURL: originURL, 27 29 } 28 30 } 29 31 ··· 158 160 unsubToken = "" 159 161 } 160 162 163 + // Get user fingerprint for dashboard URL 164 + user, err := s.store.GetUserByID(ctx, cfg.UserID) 165 + dashboardURL := "" 166 + if err == nil { 167 + dashboardURL = s.originURL + "/" + user.PubkeyFP 168 + } else { 169 + s.logger.Warn("failed to get user for dashboard URL", "err", err) 170 + } 171 + 161 172 subject := "feed digest" 162 - if err := s.mailer.Send(cfg.Email, subject, htmlBody, textBody, unsubToken); err != nil { 173 + if err := s.mailer.Send(cfg.Email, subject, htmlBody, textBody, unsubToken, dashboardURL); err != nil { 163 174 return 0, fmt.Errorf("send email: %w", err) 164 175 } 165 176 ··· 286 297 unsubToken = "" 287 298 } 288 299 300 + // Get user fingerprint for dashboard URL 301 + user, err := s.store.GetUserByID(ctx, cfg.UserID) 302 + dashboardURL := "" 303 + if err == nil { 304 + dashboardURL = s.originURL + "/" + user.PubkeyFP 305 + } else { 306 + s.logger.Warn("failed to get user for dashboard URL", "err", err) 307 + } 308 + 289 309 subject := "feed digest" 290 - if err := s.mailer.Send(cfg.Email, subject, htmlBody, textBody, unsubToken); err != nil { 310 + if err := s.mailer.Send(cfg.Email, subject, htmlBody, textBody, unsubToken, dashboardURL); err != nil { 291 311 return fmt.Errorf("send email: %w", err) 292 312 } 293 313