package mail // VulnDigestRow is one newly opened finding as the digest shows it. // // It lives here rather than in server/ so the templates and the caller agree on // the fields without server's model package leaking into shared. type VulnDigestRow struct { CVEID string Severity string PackageName string ServerName string // FixedIn empty means no vendor fix has been published, which the template // says explicitly rather than leaving blank — it is a real state, not // missing data. FixedIn string } // VulnDigest is one batch of newly opened findings. // // One message per rule per scan, never one per finding: a database refresh can // open several hundred at once, and one message each would rate-limit the // webhook or get the channel muted. type VulnDigest struct { InstanceName string // Count is every newly opened finding in the batch, which may exceed // len(Rows) — Rows is capped and More carries the remainder. Count int TopSeverity string Summary string Rows []VulnDigestRow More int // DBAge is pre-formatted by the caller. A digest scanned against a // three-week-old database must say so rather than quietly imply freshness. DBAge string } // SendVulnDigest delivers one digest to an SMTP notification channel's // recipients, which may be a comma-separated list. func (s Sender) SendVulnDigest(to string, d VulnDigest) error { return s.sendTemplate(to, "", "vuln_digest", d) }