From 82ac33ec665d055351e5d9ec4fa6e3e8d1335908 Mon Sep 17 00:00:00 2001 From: juancwu <46619361+juancwu@users.noreply.github.com> Date: Sun, 14 Dec 2025 18:24:30 -0500 Subject: [PATCH] setup resend client for email service --- go.mod | 1 + go.sum | 2 ++ internal/app/app.go | 21 +++++++++++++-------- internal/config/config.go | 6 ++++++ internal/service/email.go | 35 ++++++++++++++++++++++++++++++++++- 5 files changed, 56 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index f7b4dcb..3956fbd 100644 --- a/go.mod +++ b/go.mod @@ -52,6 +52,7 @@ require ( github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/resend/resend-go/v2 v2.28.0 // indirect github.com/segmentio/asm v1.2.0 // indirect github.com/sethvargo/go-retry v0.3.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect diff --git a/go.sum b/go.sum index d865300..deb32e0 100644 --- a/go.sum +++ b/go.sum @@ -191,6 +191,8 @@ github.com/rekby/fixenv v0.6.1 h1:jUFiSPpajT4WY2cYuc++7Y1zWrnCxnovGCIX72PZniM= github.com/rekby/fixenv v0.6.1/go.mod h1:/b5LRc06BYJtslRtHKxsPWFT/ySpHV+rWvzTg+XWk4c= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/resend/resend-go/v2 v2.28.0 h1:ttM1/VZR4fApBv3xI1TneSKi1pbfFsVrq7fXFlHKtj4= +github.com/resend/resend-go/v2 v2.28.0/go.mod h1:3YCb8c8+pLiqhtRFXTyFwlLvfjQtluxOr9HEh2BwCkQ= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= diff --git a/internal/app/app.go b/internal/app/app.go index a3cc268..736d1ca 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -11,10 +11,11 @@ import ( ) type App struct { - Cfg *config.Config - DB *sqlx.DB - UserService *service.UserService - AuthService *service.AuthService + Cfg *config.Config + DB *sqlx.DB + UserService *service.UserService + AuthService *service.AuthService + EmailService *service.EmailService } func New(cfg *config.Config) (*App, error) { @@ -28,16 +29,20 @@ func New(cfg *config.Config) (*App, error) { return nil, fmt.Errorf("failed to run migrations: %w", err) } + emailClient := service.NewResendClient(cfg.ResendKey) + userRepository := repository.NewUserRepository(database) userService := service.NewUserService(userRepository) authService := service.NewAuthService(userRepository) + emailService := service.NewEmailService(emailClient, cfg.EmailFrom, cfg.AppURL, cfg.AppName, cfg.AppEnv == "development") return &App{ - Cfg: cfg, - DB: database, - UserService: userService, - AuthService: authService, + Cfg: cfg, + DB: database, + UserService: userService, + AuthService: authService, + EmailService: emailService, }, nil } diff --git a/internal/config/config.go b/internal/config/config.go index 82d7e56..484af08 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -20,6 +20,9 @@ type Config struct { JWTSecret string JWTExpiry time.Duration + + EmailFrom string + ResendKey string } func Load() *Config { @@ -40,6 +43,9 @@ func Load() *Config { JWTSecret: envRequired("JWT_SECRET"), JWTExpiry: envDuration("JWT_EXPIRY", 168*time.Hour), // 7 days default + + EmailFrom: envString("EMAIL_FROM", ""), + ResendKey: envString("RESEND_KEY", ""), } return cfg diff --git a/internal/service/email.go b/internal/service/email.go index 83e44c0..4eef9b6 100644 --- a/internal/service/email.go +++ b/internal/service/email.go @@ -1,6 +1,11 @@ package service -import "context" +import ( + "context" + "log/slog" + + "github.com/resend/resend-go/v2" +) type EmailParams struct { From string @@ -13,6 +18,34 @@ type EmailClient interface { SendWithContext(ctx context.Context, params EmailParams) (string, error) } +type ResendClient struct { + client *resend.Client +} + +func NewResendClient(apiKey string) *ResendClient { + var client *resend.Client + if apiKey != "" { + client = resend.NewClient(apiKey) + } else { + slog.Warn("cannot initialize Resend client with empty api key") + return nil + } + return &ResendClient{client: client} +} + +func (c *ResendClient) SendWithContext(ctx context.Context, params EmailParams) (string, error) { + res, err := c.client.Emails.SendWithContext(ctx, &resend.SendEmailRequest{ + From: params.From, + To: params.To, + Subject: params.Subject, + Text: params.Text, + }) + if err != nil { + return "", err + } + return res.Id, nil +} + type EmailService struct { client EmailClient fromEmail string