This repository has no description
0

Configure Feed

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

core / appview / email / email.go
2.1 kB 94 lines
1package email 2 3import ( 4 "fmt" 5 "net" 6 "net/mail" 7 "strings" 8 9 "github.com/resend/resend-go/v3" 10) 11 12type Email struct { 13 From string 14 To string 15 Subject string 16 Text string 17 Html string 18 APIKey string 19} 20 21func SendEmail(email Email) error { 22 client := resend.NewClient(email.APIKey) 23 _, err := client.Emails.Send(&resend.SendEmailRequest{ 24 From: email.From, 25 To: []string{email.To}, 26 Subject: email.Subject, 27 Text: email.Text, 28 Html: email.Html, 29 }) 30 if err != nil { 31 return fmt.Errorf("error sending email: %w", err) 32 } 33 return nil 34} 35 36// AddNewsletterContact adds an email address to the Resend newsletter segment. 37func AddNewsletterContact(apiKey, segmentID, emailAddr string) error { 38 client := resend.NewClient(apiKey) 39 _, err := client.Contacts.Segments.Add(&resend.AddContactSegmentRequest{ 40 SegmentId: segmentID, 41 Email: emailAddr, 42 }) 43 if err != nil { 44 return fmt.Errorf("error adding contact to newsletter segment: %w", err) 45 } 46 return nil 47} 48 49func IsValidEmail(email string) bool { 50 // Reject whitespace (ParseAddress normalizes it away) 51 if strings.ContainsAny(email, " \t\n\r") { 52 return false 53 } 54 55 // Use stdlib RFC 5322 parser 56 addr, err := mail.ParseAddress(email) 57 if err != nil { 58 return false 59 } 60 61 // Split email into local and domain parts 62 parts := strings.Split(addr.Address, "@") 63 domain := parts[1] 64 65 canonical := coalesceToCanonicalName(domain) 66 mx, err := net.LookupMX(canonical) 67 68 // Don't check err here; mx will only contain valid mx records, and we should 69 // only fallback to an implicit mx if there are no mx records defined (whether 70 // they are valid or not). 71 if len(mx) != 0 { 72 return true 73 } 74 75 if err != nil { 76 // If the domain resolves to an address, assume it's an implicit mx. 77 address, _ := net.LookupIP(canonical) 78 if len(address) != 0 { 79 return true 80 } 81 } 82 83 return false 84} 85 86func coalesceToCanonicalName(domain string) string { 87 canonical, err := net.LookupCNAME(domain) 88 if err != nil { 89 // net.LookupCNAME() returns an error if there is no cname record *and* no 90 // a/aaaa records, but there may still be mx records. 91 return domain 92 } 93 return canonical 94}