remove resend and use custom email client
This commit is contained in:
parent
7b9391b62f
commit
9fe6a6beb1
6 changed files with 180 additions and 74 deletions
|
|
@ -14,7 +14,11 @@ JWT_EXPIRY=168h
|
|||
|
||||
MAILER_SMTP_HOST=
|
||||
MAILER_SMTP_PORT=
|
||||
MAILER_IMAP_HOST=
|
||||
MAILER_IMAP_PORT=
|
||||
MAILER_USERNAME=
|
||||
MAILER_PASSWORD=
|
||||
MAILER_EMAIL_FROM=
|
||||
MAILER_ENVELOPE_FROM=
|
||||
MAILER_SUPPORT_EMAIL=
|
||||
MAILER_SUPPORT_ENVELOPE_FROM=
|
||||
|
|
|
|||
8
go.mod
8
go.mod
|
|
@ -6,12 +6,13 @@ require (
|
|||
github.com/Oudwins/tailwind-merge-go v0.2.1
|
||||
github.com/a-h/templ v0.3.960
|
||||
github.com/alexedwards/argon2id v1.0.0
|
||||
github.com/emersion/go-imap v1.2.1
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2
|
||||
github.com/jackc/pgx/v5 v5.7.6
|
||||
github.com/jmoiron/sqlx v1.4.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/pressly/goose/v3 v3.26.0
|
||||
github.com/resend/resend-go/v2 v2.28.0
|
||||
github.com/wneessen/go-mail v0.7.2
|
||||
modernc.org/sqlite v1.40.1
|
||||
)
|
||||
|
||||
|
|
@ -28,6 +29,7 @@ require (
|
|||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/elastic/go-sysinfo v1.15.4 // indirect
|
||||
github.com/elastic/go-windows v1.0.2 // indirect
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/go-faster/city v1.0.1 // indirect
|
||||
|
|
@ -70,9 +72,9 @@ require (
|
|||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
|
||||
golang.org/x/mod v0.27.0 // indirect
|
||||
golang.org/x/net v0.43.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
golang.org/x/sys v0.36.0 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
golang.org/x/text v0.29.0 // indirect
|
||||
golang.org/x/tools v0.36.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect
|
||||
google.golang.org/grpc v1.62.1 // indirect
|
||||
|
|
|
|||
18
go.sum
18
go.sum
|
|
@ -59,6 +59,12 @@ github.com/elastic/go-sysinfo v1.15.4/go.mod h1:ZBVXmqS368dOn/jvijV/zHLfakWTYHBZ
|
|||
github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU=
|
||||
github.com/elastic/go-windows v1.0.2 h1:yoLLsAsV5cfg9FLhZ9EXZ2n2sQFKeDYrHenkcivY4vI=
|
||||
github.com/elastic/go-windows v1.0.2/go.mod h1:bGcDpBzXgYSqM0Gx3DM4+UxFj300SZLixie9u9ixLM8=
|
||||
github.com/emersion/go-imap v1.2.1 h1:+s9ZjMEjOB8NzZMVTM3cCenz2JrQIGGo5j1df19WjTA=
|
||||
github.com/emersion/go-imap v1.2.1/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5NsmKFSejY=
|
||||
github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4=
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
|
|
@ -191,8 +197,6 @@ 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=
|
||||
|
|
@ -218,6 +222,8 @@ github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d h1:
|
|||
github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d/go.mod h1:l8xTsYB90uaVdMHXMCxKKLSgw5wLYBwBKKefNIUnm9s=
|
||||
github.com/vertica/vertica-sql-go v1.3.3 h1:fL+FKEAEy5ONmsvya2WH5T8bhkvY27y/Ik3ReR2T+Qw=
|
||||
github.com/vertica/vertica-sql-go v1.3.3/go.mod h1:jnn2GFuv+O2Jcjktb7zyc4Utlbu9YVqpHH/lx63+1M4=
|
||||
github.com/wneessen/go-mail v0.7.2 h1:xxPnhZ6IZLSgxShebmZ6DPKh1b6OJcoHfzy7UjOkzS8=
|
||||
github.com/wneessen/go-mail v0.7.2/go.mod h1:+TkW6QP3EVkgTEqHtVmnAE/1MRhmzb8Y9/W3pweuS+k=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
|
||||
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
|
||||
|
|
@ -291,8 +297,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
|
@ -322,8 +328,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
|
|
|
|||
|
|
@ -29,13 +29,13 @@ func New(cfg *config.Config) (*App, error) {
|
|||
return nil, fmt.Errorf("failed to run migrations: %w", err)
|
||||
}
|
||||
|
||||
emailClient := service.NewResendClient(cfg.ResendKey)
|
||||
emailClient := service.NewEmailClient(cfg.MailerSMTPHost, cfg.MailerSMTPPort, cfg.MailerIMAPHost, cfg.MailerIMAPPort, cfg.MailerUsername, cfg.MailerPassword)
|
||||
|
||||
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")
|
||||
emailService := service.NewEmailService(emailClient, cfg.MailerEmailFrom, cfg.MailerEnvelopeFrom, cfg.MailerSupportFrom, cfg.MailerSupportEnvelopeFrom, cfg.AppURL, cfg.AppName, cfg.AppEnv == "development")
|
||||
|
||||
return &App{
|
||||
Cfg: cfg,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package config
|
|||
import (
|
||||
"log/slog"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
|
|
@ -22,12 +23,16 @@ type Config struct {
|
|||
JWTSecret string
|
||||
JWTExpiry time.Duration
|
||||
|
||||
MailerSMTPHost string
|
||||
MailerSMTPPort string
|
||||
MailerUsername string
|
||||
MailerPassword string
|
||||
MailerEmailFrom string
|
||||
MailerEnvelopeFrom string
|
||||
MailerSMTPHost string
|
||||
MailerSMTPPort int
|
||||
MailerIMAPHost string
|
||||
MailerIMAPPort int
|
||||
MailerUsername string
|
||||
MailerPassword string
|
||||
MailerEmailFrom string
|
||||
MailerEnvelopeFrom string
|
||||
MailerSupportFrom string
|
||||
MailerSupportEnvelopeFrom string
|
||||
}
|
||||
|
||||
func Load() *Config {
|
||||
|
|
@ -50,12 +55,16 @@ func Load() *Config {
|
|||
JWTSecret: envRequired("JWT_SECRET"),
|
||||
JWTExpiry: envDuration("JWT_EXPIRY", 168*time.Hour), // 7 days default
|
||||
|
||||
MailerSMTPHost: envString("MAILER_SMTP_HOST", ""),
|
||||
MailerSMTPPort: envString("MAILER_SMTP_PORT", ""),
|
||||
MailerUsername: envString("MAILER_USERNAME", ""),
|
||||
MailerPassword: envString("MAILER_PASSWORD", ""),
|
||||
MailerEmailFrom: envString("MAILER_EMAIL_FROM", ""),
|
||||
MailerEnvelopeFrom: envString("MAILER_ENVELOPE_FROM", ""),
|
||||
MailerSMTPHost: envString("MAILER_SMTP_HOST", ""),
|
||||
MailerSMTPPort: envInt("MAILER_SMTP_PORT", 587),
|
||||
MailerIMAPHost: envString("MAILER_IMAP_HOST", ""),
|
||||
MailerIMAPPort: envInt("MAILER_IMAP_PORT", 993),
|
||||
MailerUsername: envString("MAILER_USERNAME", ""),
|
||||
MailerPassword: envString("MAILER_PASSWORD", ""),
|
||||
MailerEmailFrom: envString("MAILER_EMAIL_FROM", ""),
|
||||
MailerEnvelopeFrom: envString("MAILER_ENVELOPE_FROM", ""),
|
||||
MailerSupportFrom: envString("MAILER_SUPPORT_EMAIL_FROM", ""),
|
||||
MailerSupportEnvelopeFrom: envString("MAILER_SUPPORT_ENVELOPE_FROM", ""),
|
||||
}
|
||||
|
||||
return cfg
|
||||
|
|
@ -89,6 +98,19 @@ func envString(key, def string) string {
|
|||
return value
|
||||
}
|
||||
|
||||
func envInt(key string, def int) int {
|
||||
value, exists := os.LookupEnv(key)
|
||||
if !exists {
|
||||
return def
|
||||
}
|
||||
i, err := strconv.ParseInt(value, 10, 32)
|
||||
if err != nil {
|
||||
slog.Warn("config invalid integer, using default", "key", key, "value", value, "default", def)
|
||||
return def
|
||||
}
|
||||
return int(i)
|
||||
}
|
||||
|
||||
func envDuration(key string, def time.Duration) time.Duration {
|
||||
value, ok := os.LookupEnv(key)
|
||||
if !ok || value == "" {
|
||||
|
|
|
|||
|
|
@ -1,75 +1,151 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/resend/resend-go/v2"
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/client"
|
||||
"github.com/wneessen/go-mail"
|
||||
)
|
||||
|
||||
type EmailParams struct {
|
||||
From string
|
||||
To []string
|
||||
Bcc []string
|
||||
Cc []string
|
||||
ReplyTo string
|
||||
Subject string
|
||||
Text string
|
||||
Html string
|
||||
From string
|
||||
EnvelopeFrom string
|
||||
To []string
|
||||
Bcc []string
|
||||
Cc []string
|
||||
ReplyTo string
|
||||
Subject string
|
||||
Text string
|
||||
Html string
|
||||
}
|
||||
|
||||
type EmailClient interface {
|
||||
SendWithContext(ctx context.Context, params *EmailParams) (string, error)
|
||||
type EmailClient struct {
|
||||
smtpHost string
|
||||
smtpPort int
|
||||
imapHost string
|
||||
imapPort int
|
||||
username string
|
||||
password string
|
||||
}
|
||||
|
||||
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
|
||||
func NewEmailClient(smtpHost string, smtpPort int, imapHost string, imapPort int, username, password string) *EmailClient {
|
||||
return &EmailClient{
|
||||
smtpHost: smtpHost,
|
||||
smtpPort: smtpPort,
|
||||
imapHost: imapHost,
|
||||
imapPort: imapPort,
|
||||
username: username,
|
||||
password: password,
|
||||
}
|
||||
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,
|
||||
Bcc: params.Bcc,
|
||||
Cc: params.Cc,
|
||||
ReplyTo: params.ReplyTo,
|
||||
Subject: params.Subject,
|
||||
Text: params.Text,
|
||||
Html: params.Html,
|
||||
})
|
||||
func (nc *EmailClient) SendWithContext(ctx context.Context, params *EmailParams) (string, error) {
|
||||
m := mail.NewMsg()
|
||||
m.From(params.From)
|
||||
m.EnvelopeFrom(params.EnvelopeFrom)
|
||||
m.To(params.To...)
|
||||
m.Subject(params.Subject)
|
||||
m.SetBodyString(mail.TypeTextPlain, params.Text)
|
||||
m.SetBodyString(mail.TypeTextHTML, params.Html)
|
||||
m.ReplyTo(params.ReplyTo)
|
||||
m.SetDate()
|
||||
m.SetMessageID()
|
||||
|
||||
msgID := m.GetMessageID()
|
||||
|
||||
var msgBuffer bytes.Buffer
|
||||
if _, err := m.WriteTo(&msgBuffer); err != nil {
|
||||
return "", fmt.Errorf("failed to buffer message: %w", err)
|
||||
}
|
||||
|
||||
smtpClient, err := nc.connectToSMTP()
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", fmt.Errorf("failed to connect to SMTP server: %w", err)
|
||||
}
|
||||
return res.Id, nil
|
||||
|
||||
err = smtpClient.DialAndSendWithContext(ctx, m)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to send email: %w", err)
|
||||
}
|
||||
|
||||
imapClient, err := nc.connectToIMAP()
|
||||
if err != nil {
|
||||
slog.Error("failed to establish connection with IMAP server", "error", err)
|
||||
return msgID, nil
|
||||
}
|
||||
defer imapClient.Logout()
|
||||
|
||||
flags := []string{imap.SeenFlag}
|
||||
|
||||
folderName := "Sent"
|
||||
|
||||
literal := bytes.NewReader(msgBuffer.Bytes())
|
||||
|
||||
err = imapClient.Append(folderName, flags, time.Now(), literal)
|
||||
if err != nil {
|
||||
slog.Error("IMAP append failed", "error", err)
|
||||
}
|
||||
|
||||
return msgID, nil
|
||||
}
|
||||
|
||||
func (nc *EmailClient) connectToSMTP() (*mail.Client, error) {
|
||||
smtpClient, err := mail.NewClient(
|
||||
nc.smtpHost,
|
||||
mail.WithPort(nc.smtpPort),
|
||||
mail.WithSMTPAuth(mail.SMTPAuthPlain),
|
||||
mail.WithUsername(nc.username),
|
||||
mail.WithPassword(nc.password),
|
||||
mail.WithTLSPolicy(mail.TLSMandatory),
|
||||
)
|
||||
return smtpClient, err
|
||||
}
|
||||
|
||||
func (nc *EmailClient) connectToIMAP() (*client.Client, error) {
|
||||
var c *client.Client
|
||||
var err error
|
||||
|
||||
addr := fmt.Sprintf("%s:%d", nc.imapHost, nc.imapPort)
|
||||
|
||||
c, err = client.DialTLS(addr, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = c.Login(nc.username, nc.password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
type EmailService struct {
|
||||
client EmailClient
|
||||
fromEmail string
|
||||
isDev bool
|
||||
appURL string
|
||||
appName string
|
||||
client *EmailClient
|
||||
fromEmail string
|
||||
fromEnvelope string
|
||||
supportEmail string
|
||||
supportEnvelope string
|
||||
isDev bool
|
||||
appURL string
|
||||
appName string
|
||||
}
|
||||
|
||||
func NewEmailService(client EmailClient, fromEmail, appURL, appName string, isDev bool) *EmailService {
|
||||
func NewEmailService(client *EmailClient, fromEmail, fromEnvelope, supportEmail, supportEnvelope, appURL, appName string, isDev bool) *EmailService {
|
||||
return &EmailService{
|
||||
client: client,
|
||||
fromEmail: fromEmail,
|
||||
isDev: isDev,
|
||||
appURL: appURL,
|
||||
appName: appName,
|
||||
client: client,
|
||||
fromEmail: fromEmail,
|
||||
fromEnvelope: fromEnvelope,
|
||||
supportEmail: supportEmail,
|
||||
supportEnvelope: supportEnvelope,
|
||||
isDev: isDev,
|
||||
appURL: appURL,
|
||||
appName: appName,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -82,10 +158,6 @@ func (s *EmailService) SendMagicLinkEmail(email, token, name string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
if s.client == nil {
|
||||
return fmt.Errorf("email service not configured (missing RESEND_API_KEY)")
|
||||
}
|
||||
|
||||
params := &EmailParams{
|
||||
From: s.fromEmail,
|
||||
To: []string{email},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue