Files

80 lines
2.3 KiB
Go

package email
import (
"context"
"net/http"
"net/mail"
"os"
"strings"
"time"
"git.sendico.io/tech/sxchnge/notifications/model"
merrors "git.sendico.io/tech/sxchnge/shared/errors"
sharedmodel "git.sendico.io/tech/sxchnge/shared/model"
"github.com/go-viper/mapstructure/v2"
)
type Driver string
const (
SendGrid Driver = "sendgrid"
Resend Driver = "resend"
)
type Config = sharedmodel.DriverConfig[Driver]
type Settings struct {
APIKeyEnv string `mapstructure:"api_key_env"`
SenderAddress string `mapstructure:"sender_address"`
SenderName string `mapstructure:"sender_name"`
}
// Outcome describes provider acceptance, never recipient delivery or reading.
type Outcome struct {
State model.State
Code string
RetryAfter time.Duration
}
type Sender interface {
Send(context.Context, string, model.Email) Outcome
}
type client struct {
http *http.Client
key string
settings Settings
endpoint string
}
func New(config Config) (Sender, error) {
var s Settings
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{Result: &s, ErrorUnused: true, TagName: "mapstructure"})
if err != nil {
return nil, merrors.InternalWrap(err, "create driver settings decoder")
}
if err = decoder.Decode(config.Settings); err != nil {
return nil, merrors.InvalidArgument("invalid email driver settings")
}
if !ValidAddress(s.SenderAddress) || strings.ContainsAny(s.SenderName, "\r\n") {
return nil, merrors.InvalidArgument("invalid sender")
}
key := os.Getenv(s.APIKeyEnv)
if s.APIKeyEnv == "" || strings.TrimSpace(key) == "" || strings.ContainsAny(key, "\r\n") {
return nil, merrors.FailedPrecondition("email API key environment variable is missing")
}
c := client{http: &http.Client{Timeout: 20 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}, key: key, settings: s}
switch config.Driver {
case SendGrid:
c.endpoint = "https://api.sendgrid.com/v3/mail/send"
return &sendgrid{c}, nil
case Resend:
c.endpoint = "https://api.resend.com/emails"
return &resend{c}, nil
default:
return nil, merrors.InvalidArgument("unsupported email driver")
}
}
func ValidAddress(s string) bool {
a, err := mail.ParseAddress(s)
return err == nil && a.Address == s && !strings.ContainsAny(s, "\r\n") && len(s) <= 254
}