package mail import ( "fmt" "time" ) // SendLicense delivers the blob inline. It is signed public data, not a secret - // it is useless on any instance other than the one it names. func (s Sender) SendLicense(to, instanceName, blob string) error { return s.sendTemplate(to, "", "license", licenseData(instanceName, blob)) } func licenseData(instanceName, blob string) any { return struct { InstanceName string Blob string Issued time.Time }{instanceName, blob, time.Now().UTC()} } // SendInstanceReady tells a customer their cloud instance exists, where it is, // and when its licence runs out. // // The expiry is stated here rather than only in a later reminder: a Free licence // that quietly expires in a month is a surprise, and the first email is the one // people keep. func (s Sender) SendInstanceReady(to, instanceName, loginURL string, expires time.Time) error { return s.sendTemplate(to, "", "instanceready", instanceReadyData(instanceName, loginURL, expires)) } func instanceReadyData(instanceName, loginURL string, expires time.Time) any { return struct { InstanceName string LoginURL string Expires time.Time }{instanceName, loginURL, expires} } // SendRenewed confirms a renewal and states the new date. func (s Sender) SendRenewed(to, instanceName string, expires time.Time) error { return s.sendTemplate(to, "", "renewed", renewedData(instanceName, expires)) } func renewedData(instanceName string, expires time.Time) any { return struct { InstanceName string Expires time.Time }{instanceName, expires} } // SendExpiring is the renew-now nudge, seven days out. func (s Sender) SendExpiring(to, instanceName, portalURL string, expires time.Time) error { return s.sendTemplate(to, "", "expiring", expiringData(instanceName, portalURL, expires)) } func expiringData(instanceName, portalURL string, expires time.Time) any { return struct { InstanceName string PortalURL string Expires time.Time }{instanceName, portalURL, expires} } // SendExpired states plainly what has stopped and what happens next. // // It names the deletion date rather than a vague warning: the whole point of the // sequence is that nobody loses an instance without having been told a date. A // zero deleteOn means the reaper is disabled, and then no date is claimed. func (s Sender) SendExpired(to, instanceName, portalURL string, deleteOn time.Time) error { return s.sendTemplate(to, "", "expired", expiredData(instanceName, portalURL, deleteOn)) } func expiredData(instanceName, portalURL string, deleteOn time.Time) any { return struct { InstanceName string PortalURL string DeleteOn time.Time }{instanceName, portalURL, deleteOn} } // SendDeletionWarning is the final countdown, sent at seven days and one day. func (s Sender) SendDeletionWarning(to, instanceName, portalURL string, deleteOn time.Time, daysLeft int) error { return s.sendTemplate(to, "", "deletionwarning", deletionWarningData(instanceName, portalURL, deleteOn, daysLeft)) } func deletionWarningData(instanceName, portalURL string, deleteOn time.Time, daysLeft int) any { when := fmt.Sprintf("in %d days", daysLeft) if daysLeft <= 1 { when = "tomorrow" } return struct { InstanceName string PortalURL string When string DaysLeft int DeleteOn time.Time }{instanceName, portalURL, when, daysLeft, deleteOn} }