Skip to content

Commit 7483ed2

Browse files
authored
Merge pull request #7 from cruxstack/dev
feat: add sendgrid as an email provider
2 parents 1950ee8 + 0205938 commit 7483ed2

File tree

8 files changed

+116
-8
lines changed

8 files changed

+116
-8
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,13 @@ with the following shape:
139139
"dstAddress": "user@example.org",
140140
"providers": {
141141
// email provider specific data
142+
"sendgrid": {
143+
"subject": "Test Email from Example.org",
144+
"templateId": "your-sendgrid-template-id",
145+
"templateData": {
146+
"code": "123456"
147+
}
148+
},
142149
"ses": {
143150
"templateId": "your-ses-template-id",
144151
"templateData": {

internal/config/config.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ type Config struct {
2020
DebugMode bool
2121
DebugDataPath string
2222
SendGridApiHost string
23+
SendGridEmailVerificationApiKey string
2324
SendGridEmailVerificationEnabled bool
2425
SendGridEmailVerificationAllowlist []string
25-
SendGridApiKey string
26+
SendGridEmailSendApiKey string
2627
}
2728

2829
func New() (*Config, error) {
@@ -37,13 +38,14 @@ func New() (*Config, error) {
3738
AWSConfig: &awscfg,
3839
AppKmsKeyId: os.Getenv("APP_KMS_KEY_ID"),
3940
AppLogLevel: log.InfoLevel,
40-
AppEmailProvider: "ses",
41+
AppEmailProvider: os.Getenv("APP_EMAIL_PROVIDER"),
4142
AppEmailSenderPolicyPath: os.Getenv("APP_EMAIL_SENDER_POLICY_PATH"),
4243
AppSendEnabled: true,
43-
SendGridApiKey: os.Getenv("APP_SENDGRID_API_KEY"),
4444
SendGridApiHost: os.Getenv("APP_SENDGRID_API_HOST"),
45+
SendGridEmailVerificationApiKey: os.Getenv("APP_SENDGRID_EMAIL_VERIFICATION_API_KEY"),
4546
SendGridEmailVerificationAllowlist: []string{},
4647
SendGridEmailVerificationEnabled: os.Getenv("APP_SENDGRID_EMAIL_VERIFICATION_ENABLED") == "true",
48+
SendGridEmailSendApiKey: os.Getenv("APP_SENDGRID_EMAIL_SEND_API_KEY"),
4749
}
4850

4951
// disable send if debug mode by default
@@ -56,6 +58,11 @@ func New() (*Config, error) {
5658
cfg.AppLogLevel = logLevel
5759
}
5860

61+
if cfg.AppEmailProvider == "" || (cfg.AppEmailProvider != "ses" && cfg.AppEmailProvider != "sendgrid") {
62+
log.Warn("fallback to ses for email provider because of unknown provider", "provider", cfg.AppEmailProvider)
63+
cfg.AppEmailProvider = "ses"
64+
}
65+
5966
allowlistStr := strings.TrimSpace(os.Getenv("APP_SENDGRID_EMAIL_VERIFICATION_ALLOWLIST"))
6067
if allowlistStr != "" {
6168
allowlist := strings.Split(allowlistStr, ",")
@@ -75,5 +82,10 @@ func New() (*Config, error) {
7582
log.Warn("KMS_KEY_ID env is deprecated; use APP_KMS_KEY_ID")
7683
}
7784

85+
if cfg.SendGridEmailVerificationApiKey == "" && os.Getenv("APP_SENDGRID_API_KEY") != "" {
86+
cfg.SendGridEmailVerificationApiKey = os.Getenv("APP_SENDGRID_API_KEY")
87+
log.Warn("APP_SENDGRID_API_KEY env is deprecated; use APP_SENDGRID_EMAIL_VERIFICATION_API_KEY")
88+
}
89+
7890
return &cfg, nil
7991
}

internal/providers/provider.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package providers
33
import (
44
"context"
55
"fmt"
6+
"net/mail"
67

78
"github.com/cruxstack/cognito-custom-message-sender-go/internal/config"
89
"github.com/cruxstack/cognito-custom-message-sender-go/internal/types"
@@ -15,7 +16,10 @@ type Provider interface {
1516

1617
func NewProvider(cfg *config.Config) (Provider, error) {
1718
var p Provider
19+
1820
switch cfg.AppEmailProvider {
21+
case "sendgrid":
22+
p = NewSendGridProvider(cfg)
1923
case "ses":
2024
p = NewSESProvider(cfg)
2125
default:
@@ -34,3 +38,11 @@ func MergeTemplateData(base, additional map[string]any) map[string]any {
3438
}
3539
return base
3640
}
41+
42+
func ParseNameAddr(s string) (string, string) {
43+
addr, err := mail.ParseAddress(s)
44+
if err != nil || addr == nil {
45+
return "", s
46+
}
47+
return addr.Name, addr.Address
48+
}

internal/providers/sendgrid.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package providers
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/charmbracelet/log"
8+
"github.com/cruxstack/cognito-custom-message-sender-go/internal/config"
9+
"github.com/cruxstack/cognito-custom-message-sender-go/internal/types"
10+
"github.com/sendgrid/sendgrid-go"
11+
"github.com/sendgrid/sendgrid-go/helpers/mail"
12+
)
13+
14+
type SendGridProvider struct {
15+
Client sendgrid.Client
16+
APIKey string
17+
DryRun bool
18+
}
19+
20+
func NewSendGridProvider(cfg *config.Config) *SendGridProvider {
21+
return &SendGridProvider{
22+
Client: *sendgrid.NewSendClient(cfg.SendGridEmailSendApiKey),
23+
DryRun: !cfg.AppSendEnabled,
24+
}
25+
}
26+
27+
func (p *SendGridProvider) Name() string {
28+
return "sendgrid"
29+
}
30+
31+
func (p *SendGridProvider) Send(ctx context.Context, d *types.EmailData) error {
32+
d.Providers.SendGrid.TemplateData = MergeTemplateData(d.Providers.SendGrid.TemplateData, map[string]any{"code": d.VerificationCode})
33+
34+
srcName, srcAddr := ParseNameAddr(d.SourceAddress)
35+
_, dstAddr := ParseNameAddr(d.DestinationAddress)
36+
37+
if p.DryRun {
38+
return p.SendDryRun(ctx, d)
39+
}
40+
41+
msg := mail.NewV3Mail()
42+
msg.SetFrom(mail.NewEmail(srcName, srcAddr))
43+
msg.SetTemplateID(d.Providers.SendGrid.TemplateID)
44+
45+
data := mail.NewPersonalization()
46+
data.AddTos(mail.NewEmail("", dstAddr))
47+
for k, v := range d.Providers.SendGrid.TemplateData {
48+
data.SetDynamicTemplateData(k, v)
49+
}
50+
msg.AddPersonalizations(data)
51+
52+
resp, err := p.Client.Send(msg)
53+
if err != nil {
54+
return fmt.Errorf("sendgrid api error: %w", err)
55+
}
56+
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
57+
return fmt.Errorf("sendgrid send failed: status=%d body=%s", resp.StatusCode, resp.Body)
58+
}
59+
60+
return nil
61+
}
62+
63+
func (p *SendGridProvider) SendDryRun(ctx context.Context, d *types.EmailData) error {
64+
log.Debug("[DRY-RUN] SendGrid Send",
65+
"templateId", d.Providers.SendGrid.TemplateID,
66+
"templateData", d.Providers.SendGrid.TemplateData,
67+
"srcAddress", d.SourceAddress,
68+
"dstAddress", d.DestinationAddress,
69+
"data.len", len(d.TemplateData),
70+
)
71+
return nil
72+
}

internal/providers/ses.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ func (p *SESProvider) SendDryRun(ctx context.Context, d *types.EmailData) error
6161
}
6262

6363
log.Debug(
64-
"[DRY-RUN] SES SendTemplateEmail:",
65-
"templateID", d.Providers.SES.TemplateID,
64+
"[DRY-RUN] SES Send:",
65+
"templateId", d.Providers.SES.TemplateID,
6666
"templateData", string(dataJSON),
6767
"srcAddress", d.SourceAddress,
6868
"dstAddress", d.DestinationAddress,

internal/sender/sender.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,12 +133,16 @@ func (s *Sender) ParseEmailData(data *types.EmailData) (*types.EmailData, error)
133133
if data.Providers == nil {
134134
data.Providers = &types.EmailProviderMap{}
135135
}
136-
if data.Providers.SES == nil {
136+
if s.Config.AppEmailProvider == "ses" && data.Providers.SES == nil {
137137
data.Providers.SES = &types.EmailProviderData{
138138
TemplateID: data.TemplateID,
139139
TemplateData: data.TemplateData,
140140
}
141141
}
142142

143+
if s.Config.AppEmailProvider == "sendgrid" && data.Providers.SendGrid == nil {
144+
return nil, fmt.Errorf("email provider is sendgrid but email data does not include data for sendgrid provider")
145+
}
146+
143147
return data, nil
144148
}

internal/types/types.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ type EmailData struct {
1010
}
1111

1212
type EmailProviderMap struct {
13-
SES *EmailProviderData `json:"ses,omitempty"`
13+
SendGrid *EmailProviderData `json:"sendgrid,omitempty"`
14+
SES *EmailProviderData `json:"ses,omitempty"`
1415
}
1516

1617
type EmailProviderData struct {

internal/verifier/sendgrid.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,6 @@ func NewSendGridVerifier(cfg *config.Config) (*SendGridEmailVerifier, error) {
9797
return &SendGridEmailVerifier{
9898
Allowlist: cfg.SendGridEmailVerificationAllowlist,
9999
APIHost: cfg.SendGridApiHost,
100-
APIKey: cfg.SendGridApiKey,
100+
APIKey: cfg.SendGridEmailVerificationApiKey,
101101
}, nil
102102
}

0 commit comments

Comments
 (0)