package mail import ( "fmt" "time" ) // VerifyWindow is how long a verification or invitation link stays usable. It // is stated in the email, so it lives beside the message rather than in the // caller. const VerifyWindow = 24 * time.Hour // SendVerification asks someone to confirm the address they signed up with. // The link is built from the Sender's PublicURL, because the address of the // portal is a property of the deployment, not of the call site. func (s Sender) SendVerification(to, token string) error { return s.sendTemplate(to, "", "verification", s.verificationData(to, token)) } // verificationData is split from its Send method, as every message's is, so // render_test.go renders the exact struct the message is sent with. func (s Sender) verificationData(to, token string) any { return struct { Email string Link string TTLHours int Expires time.Time }{ Email: to, Link: fmt.Sprintf("%s/verify?token=%s", s.PublicURL, token), TTLHours: int(VerifyWindow.Hours()), Expires: time.Now().UTC().Add(VerifyWindow), } } // SendInvite asks someone to join an existing account and set their own // password. It names the account, because an unexpected invitation from a // service you have never used is otherwise indistinguishable from spam. func (s Sender) SendInvite(to, accountName, token string) error { return s.sendTemplate(to, "", "invite", s.inviteData(to, accountName, token)) } func (s Sender) inviteData(to, accountName, token string) any { return struct { Email string AccountName string Link string TTLHours int }{ Email: to, AccountName: accountName, Link: fmt.Sprintf("%s/accept-invite?token=%s", s.PublicURL, token), TTLHours: int(VerifyWindow.Hours()), } }