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

Configure Feed

Select the types of activity you want to include in your feed.

feat: use randomized boundary and fix signing and host

+18 -2
+18 -2
email/send.go
··· 155 155 func (m *Mailer) Send(to, subject, htmlBody, textBody, unsubToken, dashboardURL, keepAliveURL string) error { 156 156 addr := net.JoinHostPort(m.cfg.Host, fmt.Sprintf("%d", m.cfg.Port)) 157 157 158 - boundary := "==herald-boundary-a1b2c3d4e5f6==" 158 + boundary := "==herald-" + generateMessageIDToken() + "==" 159 159 160 160 // Add footer with keep-alive, unsubscribe, and dashboard links 161 161 var htmlFooter strings.Builder ··· 199 199 headers["MIME-Version"] = "1.0" 200 200 headers["Content-Type"] = fmt.Sprintf("multipart/alternative; boundary=%q", boundary) 201 201 headers["Date"] = time.Now().Format(time.RFC1123Z) 202 - headers["Message-ID"] = fmt.Sprintf("<%d.%s@%s>", time.Now().Unix(), generateMessageIDToken(), m.cfg.Host) 202 + headers["Message-ID"] = fmt.Sprintf("<%d.%s@%s>", time.Now().Unix(), generateMessageIDToken(), senderDomain(m.cfg.From)) 203 203 204 204 // RFC 2369 list headers 205 205 headers["List-Id"] = fmt.Sprintf("<herald.%s>", m.cfg.Host) ··· 260 260 } 261 261 262 262 return m.sendWithSTARTTLS(addr, auth, to, messageBytes) 263 + } 264 + 265 + // senderDomain extracts the domain part from an email address. 266 + // Falls back to the full address if parsing fails. 267 + func senderDomain(from string) string { 268 + if idx := strings.LastIndex(from, "@"); idx >= 0 { 269 + return from[idx+1:] 270 + } 271 + return from 263 272 } 264 273 265 274 func generateMessageIDToken() string { ··· 395 404 "From", 396 405 "To", 397 406 "Subject", 407 + "Date", 408 + "Message-ID", 409 + "MIME-Version", 410 + "Content-Type", 411 + "List-Id", 398 412 "List-Unsubscribe", 399 413 "List-Unsubscribe-Post", 414 + "List-Post", 415 + "Precedence", 400 416 }, 401 417 Expiration: time.Now().Add(72 * time.Hour), 402 418 }