Monorepo for Tangled
at master 81 lines 1.7 kB view raw
1package email 2 3import ( 4 "fmt" 5 "net" 6 "net/mail" 7 "strings" 8 9 "github.com/resend/resend-go/v2" 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 36func IsValidEmail(email string) bool { 37 // Reject whitespace (ParseAddress normalizes it away) 38 if strings.ContainsAny(email, " \t\n\r") { 39 return false 40 } 41 42 // Use stdlib RFC 5322 parser 43 addr, err := mail.ParseAddress(email) 44 if err != nil { 45 return false 46 } 47 48 // Split email into local and domain parts 49 parts := strings.Split(addr.Address, "@") 50 domain := parts[1] 51 52 canonical := coalesceToCanonicalName(domain) 53 mx, err := net.LookupMX(canonical) 54 55 // Don't check err here; mx will only contain valid mx records, and we should 56 // only fallback to an implicit mx if there are no mx records defined (whether 57 // they are valid or not). 58 if len(mx) != 0 { 59 return true 60 } 61 62 if err != nil { 63 // If the domain resolves to an address, assume it's an implicit mx. 64 address, _ := net.LookupIP(canonical) 65 if len(address) != 0 { 66 return true 67 } 68 } 69 70 return false 71} 72 73func coalesceToCanonicalName(domain string) string { 74 canonical, err := net.LookupCNAME(domain) 75 if err != nil { 76 // net.LookupCNAME() returns an error if there is no cname record *and* no 77 // a/aaaa records, but there may still be mx records. 78 return domain 79 } 80 return canonical 81}