Monorepo for Tangled
0
fork

Configure Feed

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

at master 100 lines 2.3 kB view raw
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 creates a global contact in Resend and adds them to the newsletter segment. 37func AddNewsletterContact(apiKey, segmentID, emailAddr string) error { 38 client := resend.NewClient(apiKey) 39 _, err := client.Contacts.Create(&resend.CreateContactRequest{ 40 Email: emailAddr, 41 }) 42 if err != nil { 43 return fmt.Errorf("error creating contact: %w", err) 44 } 45 _, err = client.Contacts.Segments.Add(&resend.AddContactSegmentRequest{ 46 SegmentId: segmentID, 47 Email: emailAddr, 48 }) 49 if err != nil { 50 return fmt.Errorf("error adding contact to newsletter segment: %w", err) 51 } 52 return nil 53} 54 55func IsValidEmail(email string) bool { 56 // Reject whitespace (ParseAddress normalizes it away) 57 if strings.ContainsAny(email, " \t\n\r") { 58 return false 59 } 60 61 // Use stdlib RFC 5322 parser 62 addr, err := mail.ParseAddress(email) 63 if err != nil { 64 return false 65 } 66 67 // Split email into local and domain parts 68 parts := strings.Split(addr.Address, "@") 69 domain := parts[1] 70 71 canonical := coalesceToCanonicalName(domain) 72 mx, err := net.LookupMX(canonical) 73 74 // Don't check err here; mx will only contain valid mx records, and we should 75 // only fallback to an implicit mx if there are no mx records defined (whether 76 // they are valid or not). 77 if len(mx) != 0 { 78 return true 79 } 80 81 if err != nil { 82 // If the domain resolves to an address, assume it's an implicit mx. 83 address, _ := net.LookupIP(canonical) 84 if len(address) != 0 { 85 return true 86 } 87 } 88 89 return false 90} 91 92func coalesceToCanonicalName(domain string) string { 93 canonical, err := net.LookupCNAME(domain) 94 if err != nil { 95 // net.LookupCNAME() returns an error if there is no cname record *and* no 96 // a/aaaa records, but there may still be mx records. 97 return domain 98 } 99 return canonical 100}