This repository has no description
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}