Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions bindings/twilio/sendgrid/metadata.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,7 @@ metadata:
required: false
description: "The dynamic template ID"
example: "your-template-id"
- name: trackingSettings
required: false
description: "JSON object controlling SendGrid email tracking, using SendGrid API field names (click_tracking, open_tracking, subscription_tracking, ganalytics)"
example: '{"click_tracking":{"enable":true,"enable_text":true},"open_tracking":{"enable":true},"subscription_tracking":{"enable":false},"ganalytics":{"enable":true,"utm_source":"transactional-email","utm_medium":"email"}}'
39 changes: 38 additions & 1 deletion bindings/twilio/sendgrid/sendgrid.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,10 @@ type sendGridMetadata struct {
EmailBcc string `mapstructure:"emailBcc"`
DynamicTemplateData string `mapstructure:"dynamicTemplateData"`
DynamicTemplateID string `mapstructure:"dynamicTemplateId"`
TrackingSettings string `mapstructure:"trackingSettings"`

dynamicTemplateDataCache map[string]any // Cache the unmarshalled dynamic template data
dynamicTemplateDataCache map[string]any // Cache the unmarshalled dynamic template data
trackingSettingsCache *mail.TrackingSettings // Cache the unmarshalled tracking settings
}

// Wrapper to help decode SendGrid API errors.
Expand Down Expand Up @@ -89,6 +91,15 @@ func (sg *SendGrid) parseMetadata(meta bindings.Metadata) (sendGridMetadata, err
}
}

// Cache the unmarshalled tracking settings if present
if sgMeta.TrackingSettings != "" {
trackingSettings, trackingError := UnmarshalTrackingSettings(sgMeta.TrackingSettings)
if trackingError != nil {
return sgMeta, trackingError
}
sgMeta.trackingSettingsCache = trackingSettings
}

return sgMeta, nil
}

Expand Down Expand Up @@ -212,6 +223,18 @@ func (sg *SendGrid) Invoke(ctx context.Context, req *bindings.InvokeRequest) (*b
templateData = sg.metadata.dynamicTemplateDataCache
}

// Build email tracking settings, this is optional
var trackingSettings *mail.TrackingSettings
if req.Metadata["trackingSettings"] != "" {
var trackingError error
trackingSettings, trackingError = UnmarshalTrackingSettings(req.Metadata["trackingSettings"])
if trackingError != nil {
return nil, trackingError
}
} else if sg.metadata.trackingSettingsCache != nil {
trackingSettings = sg.metadata.trackingSettingsCache
}
Comment on lines +226 to +236

// Email body is held in req.Data, after we tidy it up a bit
emailBody, err := strconv.Unquote(string(req.Data))
if err != nil {
Expand Down Expand Up @@ -240,6 +263,9 @@ func (sg *SendGrid) Invoke(ctx context.Context, req *bindings.InvokeRequest) (*b
if templateData != nil {
personalization.DynamicTemplateData = templateData
}
if trackingSettings != nil {
email.SetTrackingSettings(trackingSettings)
}

email.AddPersonalizations(personalization)

Expand Down Expand Up @@ -284,6 +310,17 @@ func UnmarshalDynamicTemplateData(jsonString string, result *map[string]any) err
return nil
}

// Function that unmarshals a tracking settings JSON string, using the SendGrid API field names
// (click_tracking, open_tracking, subscription_tracking, ganalytics).
func UnmarshalTrackingSettings(jsonString string) (*mail.TrackingSettings, error) {
trackingSettings := &mail.TrackingSettings{}
err := json.Unmarshal([]byte(jsonString), trackingSettings)
if err != nil {
return nil, fmt.Errorf("error from SendGrid binding, tracking settings is not valid JSON: %w", err)
Comment thread
chadncampbell marked this conversation as resolved.
Outdated
}
return trackingSettings, nil
}

func Close() error {
return nil
}
80 changes: 80 additions & 0 deletions bindings/twilio/sendgrid/sendgrid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,86 @@ func TestParseMetadataWithOptionalNames(t *testing.T) {
})
}

func TestParseMetadataWithTrackingSettings(t *testing.T) {
logger := logger.NewLogger("test")

trackingSettingsJSON := `{
"click_tracking": {"enable": true, "enable_text": true},
"open_tracking": {"enable": true, "substitution_tag": "%open_track_tag%"},
"subscription_tracking": {"enable": false},
"ganalytics": {"enable": true, "utm_source": "transactional-email", "utm_medium": "email", "utm_campaign": "order-delivery"}
}`

t.Run("Has correct tracking settings metadata", func(t *testing.T) {
m := bindings.Metadata{}
m.Properties = map[string]string{
"apiKey": "123",
"emailFrom": "test1@example.net",
"emailTo": "test2@example.net",
"subject": "hello",
"trackingSettings": trackingSettingsJSON,
}
r := SendGrid{logger: logger}
sgMeta, err := r.parseMetadata(m)
require.NoError(t, err)
require.NotNil(t, sgMeta.trackingSettingsCache)
assert.True(t, *sgMeta.trackingSettingsCache.ClickTracking.Enable)
assert.True(t, *sgMeta.trackingSettingsCache.ClickTracking.EnableText)
assert.True(t, *sgMeta.trackingSettingsCache.OpenTracking.Enable)
assert.Equal(t, "%open_track_tag%", sgMeta.trackingSettingsCache.OpenTracking.SubstitutionTag)
assert.False(t, *sgMeta.trackingSettingsCache.SubscriptionTracking.Enable)
assert.True(t, *sgMeta.trackingSettingsCache.GoogleAnalytics.Enable)
assert.Equal(t, "transactional-email", sgMeta.trackingSettingsCache.GoogleAnalytics.CampaignSource)
assert.Equal(t, "email", sgMeta.trackingSettingsCache.GoogleAnalytics.CampaignMedium)
assert.Equal(t, "order-delivery", sgMeta.trackingSettingsCache.GoogleAnalytics.CampaignName)
})

t.Run("Has incorrect tracking settings metadata", func(t *testing.T) {
m := bindings.Metadata{}
m.Properties = map[string]string{
"apiKey": "123",
"emailFrom": "test1@example.net",
"emailTo": "test2@example.net",
"subject": "hello",
"trackingSettings": `{"wrong"}`,
}
r := SendGrid{logger: logger}
_, err := r.parseMetadata(m)
require.Error(t, err)
})

t.Run("No tracking settings metadata leaves cache nil", func(t *testing.T) {
m := bindings.Metadata{}
m.Properties = map[string]string{
"apiKey": "123",
"emailFrom": "test1@example.net",
"emailTo": "test2@example.net",
"subject": "hello",
}
r := SendGrid{logger: logger}
sgMeta, err := r.parseMetadata(m)
require.NoError(t, err)
assert.Nil(t, sgMeta.trackingSettingsCache)
})
}

// Test UnmarshalTrackingSettings function
func TestUnmarshalTrackingSettings(t *testing.T) {
t.Run("Valid tracking settings JSON", func(t *testing.T) {
trackingSettings, err := UnmarshalTrackingSettings(`{"click_tracking":{"enable":true},"open_tracking":{"enable":false}}`)
require.NoError(t, err)
assert.True(t, *trackingSettings.ClickTracking.Enable)
assert.False(t, *trackingSettings.OpenTracking.Enable)
assert.Nil(t, trackingSettings.SubscriptionTracking)
assert.Nil(t, trackingSettings.GoogleAnalytics)
})

t.Run("Invalid tracking settings JSON", func(t *testing.T) {
_, err := UnmarshalTrackingSettings(`{"wrong"}`)
require.Error(t, err)
})
}

// Test UnmarshalDynamicTemplateData function
func TestUnmarshalDynamicTemplateData(t *testing.T) {
t.Run("Test Template Data", func(t *testing.T) {
Expand Down