Przeglądaj źródła

add resend support, configure email interface types

abhishek9686 1 rok temu
rodzic
commit
cbfd43e0c5
9 zmienionych plików z 181 dodań i 49 usunięć
  1. 5 0
      config/config.go
  2. 2 1
      controllers/user.go
  3. 25 39
      email/email.go
  4. 55 0
      email/resend.go
  5. 42 0
      email/smtp.go
  6. 1 0
      go.mod
  7. 2 0
      go.sum
  8. 5 3
      scripts/netmaker.default.env
  9. 44 6
      servercfg/serverconf.go

+ 5 - 0
config/config.go

@@ -94,6 +94,11 @@ type ServerConfig struct {
 	CacheEnabled               string        `yaml:"caching_enabled"`
 	EndpointDetection          bool          `json:"endpoint_detection"`
 	AllowedEmailDomains        string        `yaml:"allowed_email_domains"`
+	EmailSenderAddr            string        `json:"email_sender_addr"`
+	EmailSenderAuth            string        `json:"email_sender_auth"`
+	EmailSenderType            string        `json:"email_sender_type"`
+	SmtpHost                   string        `json:"smtp_host"`
+	SmtpPort                   int           `json:"smtp_port"`
 }
 
 // SQLConfig - Generic SQL Config

+ 2 - 1
controllers/user.go

@@ -1,6 +1,7 @@
 package controller
 
 import (
+	"context"
 	"encoding/json"
 	"errors"
 	"fmt"
@@ -1227,7 +1228,7 @@ func inviteUsers(w http.ResponseWriter, r *http.Request) {
 			n := email.Notification{
 				RecipientMail: invite.Email,
 			}
-			err = email.Send(n.NewEmailSender(e))
+			err = email.GetClient().SendEmail(context.Background(), n, e)
 			if err != nil {
 				slog.Error("failed to send email invite", "user", invite.Email, "error", err)
 			}

+ 25 - 39
email/email.go

@@ -1,21 +1,25 @@
 package email
 
 import (
-	"crypto/tls"
-
-	gomail "gopkg.in/mail.v2"
+	"context"
 
 	"github.com/gravitl/netmaker/servercfg"
 )
 
-var (
-	smtpHost       = servercfg.GetSmtpHost()
-	smtpPort       = servercfg.GetSmtpPort()
-	senderEmail    = servercfg.GetSenderEmail()
-	senderPassword = servercfg.GetSenderEmailPassWord()
+type EmailSenderType string
+
+const (
+	Smtp   EmailSenderType = "smtp"
+	Resend EmailSenderType = "resend"
 )
 
-type Email interface {
+// EmailSender - an interface for sending emails based on notifications and mail templates
+type EmailSender interface {
+	// SendEmail - sends an email based on a context, notification and mail template
+	SendEmail(ctx context.Context, notification Notification, email Mail) error
+}
+
+type Mail interface {
 	GetBody(info Notification) string
 	GetSubject(info Notification) string
 }
@@ -27,35 +31,17 @@ type Notification struct {
 	ProductName   string
 }
 
-func (n Notification) NewEmailSender(e Email) *gomail.Message {
-	m := gomail.NewMessage()
-
-	// Set E-Mail sender
-	m.SetHeader("From", senderEmail)
-
-	// Set E-Mail receivers
-	m.SetHeader("To", n.RecipientMail)
-	// Set E-Mail subject
-	m.SetHeader("Subject", e.GetSubject(n))
-	// Set E-Mail body. You can set plain text or html with text/html
-	m.SetBody("text/html", e.GetBody(n))
-
-	return m
-}
-
-func Send(m *gomail.Message) error {
-
-	// Settings for SMTP server
-	d := gomail.NewDialer(smtpHost, smtpPort, senderEmail, senderPassword)
-
-	// This is only needed when SSL/TLS certificate is not valid on server.
-	// In production this should be set to false.
-	d.TLSConfig = &tls.Config{InsecureSkipVerify: true}
-
-	// Now send E-Mail
-	if err := d.DialAndSend(m); err != nil {
-		return err
+func GetClient() (e EmailSender) {
+	switch EmailSenderType(servercfg.EmailSenderType()) {
+	case Smtp:
+		e = &SmtpSender{
+			SmtpHost:    servercfg.GetSmtpHost(),
+			SmtpPort:    servercfg.GetSmtpPort(),
+			SenderEmail: servercfg.GetSenderEmail(),
+			SenderPass:  servercfg.GetEmaiSenderAuth(),
+		}
+	case Resend:
+		e = NewResendEmailSenderFromConfig()
 	}
-
-	return nil
+	return
 }

+ 55 - 0
email/resend.go

@@ -0,0 +1,55 @@
+package email
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/gravitl/netmaker/servercfg"
+	"github.com/resendlabs/resend-go"
+)
+
+// ResendEmailSender - implementation of EmailSender using Resend (https://resend.com)
+type ResendEmailSender struct {
+	client ResendClient
+	from   string
+}
+
+// ResendClient - dependency interface for resend client
+type ResendClient interface {
+	Send(*resend.SendEmailRequest) (resend.SendEmailResponse, error)
+}
+
+// NewResendEmailSender - constructs a ResendEmailSender
+func NewResendEmailSender(client ResendClient, from string) ResendEmailSender {
+	return ResendEmailSender{client: client, from: from}
+}
+
+// NewResendEmailSender - constructs a ResendEmailSender from config
+// TODO let main.go handle this and use dependency injection instead of calling this function
+func NewResendEmailSenderFromConfig() ResendEmailSender {
+	key, from := servercfg.GetEmaiSenderAuth(), servercfg.GetSenderEmail()
+	resender := resend.NewClient(key)
+	return NewResendEmailSender(resender.Emails, from)
+}
+
+// SendEmail - sends an email using resend-go (https://github.com/resendlabs/resend-go)
+func (es ResendEmailSender) SendEmail(ctx context.Context, notification Notification, email Mail) error {
+	var (
+		from    = es.from
+		to      = notification.RecipientMail
+		subject = email.GetSubject(notification)
+		body    = email.GetBody(notification)
+	)
+	params := resend.SendEmailRequest{
+		From:    from,
+		To:      []string{to},
+		Subject: subject,
+		Html:    body,
+	}
+	_, err := es.client.Send(&params)
+	if err != nil {
+		return fmt.Errorf("failed sending mail via resend: %w", err)
+	}
+
+	return nil
+}

+ 42 - 0
email/smtp.go

@@ -0,0 +1,42 @@
+package email
+
+import (
+	"context"
+	"crypto/tls"
+
+	gomail "gopkg.in/mail.v2"
+)
+
+type SmtpSender struct {
+	SmtpHost    string
+	SmtpPort    int
+	SenderEmail string
+	SenderPass  string
+}
+
+func (s *SmtpSender) SendEmail(ctx context.Context, n Notification, e Mail) error {
+	m := gomail.NewMessage()
+
+	// Set E-Mail sender
+	m.SetHeader("From", s.SenderEmail)
+
+	// Set E-Mail receivers
+	m.SetHeader("To", n.RecipientMail)
+	// Set E-Mail subject
+	m.SetHeader("Subject", e.GetSubject(n))
+	// Set E-Mail body. You can set plain text or html with text/html
+	m.SetBody("text/html", e.GetBody(n))
+	// Settings for SMTP server
+	d := gomail.NewDialer(s.SmtpHost, s.SmtpPort, s.SenderEmail, s.SenderPass)
+
+	// This is only needed when SSL/TLS certificate is not valid on server.
+	// In production this should be set to false.
+	d.TLSConfig = &tls.Config{InsecureSkipVerify: true}
+
+	// Now send E-Mail
+	if err := d.DialAndSend(m); err != nil {
+		return err
+	}
+
+	return nil
+}

+ 1 - 0
go.mod

@@ -41,6 +41,7 @@ require (
 	github.com/guumaster/tablewriter v0.0.10
 	github.com/matryer/is v1.4.1
 	github.com/olekukonko/tablewriter v0.0.5
+	github.com/resendlabs/resend-go v1.7.0
 	github.com/spf13/cobra v1.8.1
 	gopkg.in/mail.v2 v2.3.1
 )

+ 2 - 0
go.sum

@@ -63,6 +63,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0 h1:Y2hUrkfuM0on62KZOci/VLijlkdF/yeWU262BQgvcjE=
 github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0/go.mod h1:oa2sAs9tGai3VldabTV0eWejt/O4/OOD7azP8GaikqU=
+github.com/resendlabs/resend-go v1.7.0 h1:DycOqSXtw2q7aB+Nt9DDJUDtaYcrNPGn1t5RFposas0=
+github.com/resendlabs/resend-go v1.7.0/go.mod h1:yip1STH7Bqfm4fD0So5HgyNbt5taG5Cplc4xXxETyLI=
 github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=

+ 5 - 3
scripts/netmaker.default.env

@@ -81,6 +81,8 @@ SMTP_HOST=smtp.gmail.com
 # mail server port
 SMTP_PORT=587
 # sender email
-SENDER_EMAIL=
-# sender email password
-SENDER_PASSWORD=
+EMAIL_SENDER_ADDR=
+# sender email auth
+EMAIL_SENDER_AUTH=
+# mail sender type (smtp or resend)
+EMAIL_SENDER_TYPE=smtp

+ 44 - 6
servercfg/serverconf.go

@@ -242,18 +242,56 @@ func GetPublicBrokerEndpoint() string {
 }
 
 func GetSmtpHost() string {
-	return os.Getenv("SMTP_HOST")
+	v := ""
+	if fromEnv := os.Getenv("SMTP_HOST"); fromEnv != "" {
+		v = fromEnv
+	} else if fromCfg := config.Config.Server.SmtpHost; fromCfg != "" {
+		v = fromCfg
+	}
+	return v
 }
 
 func GetSmtpPort() int {
-	port, _ := strconv.Atoi(os.Getenv("SMTP_PORT"))
-	return port
+	v := 587
+	if fromEnv := os.Getenv("SMTP_PORT"); fromEnv != "" {
+		port, err := strconv.Atoi(fromEnv)
+		if err == nil {
+			v = port
+		}
+	} else if fromCfg := config.Config.Server.SmtpPort; fromCfg != 0 {
+		v = fromCfg
+	}
+	return v
 }
+
 func GetSenderEmail() string {
-	return os.Getenv("SENDER_EMAIL")
+	v := ""
+	if fromEnv := os.Getenv("EMAIL_SENDER_ADDR"); fromEnv != "" {
+		v = fromEnv
+	} else if fromCfg := config.Config.Server.EmailSenderAddr; fromCfg != "" {
+		v = fromCfg
+	}
+	return v
 }
-func GetSenderEmailPassWord() string {
-	return os.Getenv("SENDER_PASSWORD")
+
+func GetEmaiSenderAuth() string {
+	v := ""
+	if fromEnv := os.Getenv("EMAIL_SENDER_AUTH"); fromEnv != "" {
+		v = fromEnv
+	} else if fromCfg := config.Config.Server.EmailSenderAddr; fromCfg != "" {
+		v = fromCfg
+	}
+	return v
+}
+
+func EmailSenderType() string {
+	s := ""
+	if fromEnv := os.Getenv("EMAIL_SENDER_TYPE"); fromEnv != "" {
+		s = fromEnv
+	} else if fromCfg := config.Config.Server.EmailSenderType; fromCfg != "" {
+		s = fromCfg
+	}
+	return s
 }
 
 // GetOwnerEmail - gets the owner email (saas)