Skip to content

Commit 9354b83

Browse files
cstocktonChris Stockton
andauthored
feat: mailer logging (#1805)
This is a quick patch to add basic logging information to the mailer package. I wasn't sure how to only log this information when using the Supabase default mail provider without adding another custom config flag to enable it. --------- Co-authored-by: Chris Stockton <[email protected]>
1 parent 99d6a13 commit 9354b83

File tree

7 files changed

+108
-44
lines changed

7 files changed

+108
-44
lines changed

internal/conf/configuration.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -342,14 +342,15 @@ type ProviderConfiguration struct {
342342
}
343343

344344
type SMTPConfiguration struct {
345-
MaxFrequency time.Duration `json:"max_frequency" split_words:"true"`
346-
Host string `json:"host"`
347-
Port int `json:"port,omitempty" default:"587"`
348-
User string `json:"user"`
349-
Pass string `json:"pass,omitempty"`
350-
AdminEmail string `json:"admin_email" split_words:"true"`
351-
SenderName string `json:"sender_name" split_words:"true"`
352-
Headers string `json:"headers"`
345+
MaxFrequency time.Duration `json:"max_frequency" split_words:"true"`
346+
Host string `json:"host"`
347+
Port int `json:"port,omitempty" default:"587"`
348+
User string `json:"user"`
349+
Pass string `json:"pass,omitempty"`
350+
AdminEmail string `json:"admin_email" split_words:"true"`
351+
SenderName string `json:"sender_name" split_words:"true"`
352+
Headers string `json:"headers"`
353+
LoggingEnabled bool `json:"logging_enabled" split_words:"true" default:"false"`
353354

354355
fromAddress string `json:"-"`
355356
normalizedHeaders map[string][]string `json:"-"`

internal/conf/configuration_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,12 @@ func TestGlobal(t *testing.T) {
2323
os.Setenv("API_EXTERNAL_URL", "http://localhost:9999")
2424
os.Setenv("GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_URI", "pg-functions://postgres/auth/count_failed_attempts")
2525
os.Setenv("GOTRUE_HOOK_SEND_SMS_SECRETS", "v1,whsec_aWxpa2VzdXBhYmFzZXZlcnltdWNoYW5kaWhvcGV5b3Vkb3Rvbw==")
26+
os.Setenv("GOTRUE_SMTP_HEADERS", `{"X-PM-Metadata-project-ref":["project_ref"],"X-SES-Message-Tags":["ses:feedback-id-a=project_ref,ses:feedback-id-b=$messageType"]}`)
27+
os.Setenv("GOTRUE_SMTP_LOGGING_ENABLED", "true")
2628
gc, err := LoadGlobal("")
2729
require.NoError(t, err)
30+
assert.Equal(t, true, gc.SMTP.LoggingEnabled)
31+
assert.Equal(t, "project_ref", gc.SMTP.NormalizedHeaders()["X-PM-Metadata-project-ref"][0])
2832
require.NotNil(t, gc)
2933
assert.Equal(t, "X-Request-ID", gc.API.RequestIDHeader)
3034
assert.Equal(t, "pg-functions://postgres/auth/count_failed_attempts", gc.Hook.MFAVerificationAttempt.URI)

internal/mailer/mailer.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,15 @@ func NewMailer(globalConfig *conf.GlobalConfiguration) Mailer {
4949
mailClient = &noopMailClient{}
5050
} else {
5151
mailClient = &MailmeMailer{
52-
Host: globalConfig.SMTP.Host,
53-
Port: globalConfig.SMTP.Port,
54-
User: globalConfig.SMTP.User,
55-
Pass: globalConfig.SMTP.Pass,
56-
LocalName: u.Hostname(),
57-
From: from,
58-
BaseURL: globalConfig.SiteURL,
59-
Logger: logrus.StandardLogger(),
52+
Host: globalConfig.SMTP.Host,
53+
Port: globalConfig.SMTP.Port,
54+
User: globalConfig.SMTP.User,
55+
Pass: globalConfig.SMTP.Pass,
56+
LocalName: u.Hostname(),
57+
From: from,
58+
BaseURL: globalConfig.SiteURL,
59+
Logger: logrus.StandardLogger(),
60+
MailLogging: globalConfig.SMTP.LoggingEnabled,
6061
}
6162
}
6263

internal/mailer/mailme.go

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,22 @@ const TemplateExpiration = 10 * time.Second
2424

2525
// MailmeMailer lets MailMe send templated mails
2626
type MailmeMailer struct {
27-
From string
28-
Host string
29-
Port int
30-
User string
31-
Pass string
32-
BaseURL string
33-
LocalName string
34-
FuncMap template.FuncMap
35-
cache *TemplateCache
36-
Logger logrus.FieldLogger
27+
From string
28+
Host string
29+
Port int
30+
User string
31+
Pass string
32+
BaseURL string
33+
LocalName string
34+
FuncMap template.FuncMap
35+
cache *TemplateCache
36+
Logger logrus.FieldLogger
37+
MailLogging bool
3738
}
3839

3940
// Mail sends a templated mail. It will try to load the template from a URL, and
4041
// otherwise fall back to the default
41-
func (m *MailmeMailer) Mail(to, subjectTemplate, templateURL, defaultTemplate string, templateData map[string]interface{}, headers map[string][]string) error {
42+
func (m *MailmeMailer) Mail(to, subjectTemplate, templateURL, defaultTemplate string, templateData map[string]interface{}, headers map[string][]string, typ string) error {
4243
if m.FuncMap == nil {
4344
m.FuncMap = map[string]interface{}{}
4445
}
@@ -60,6 +61,7 @@ func (m *MailmeMailer) Mail(to, subjectTemplate, templateURL, defaultTemplate st
6061
if err != nil {
6162
return err
6263
}
64+
6365
body, err := m.MailBody(templateURL, defaultTemplate, templateData)
6466
if err != nil {
6567
return err
@@ -82,8 +84,22 @@ func (m *MailmeMailer) Mail(to, subjectTemplate, templateURL, defaultTemplate st
8284
if m.LocalName != "" {
8385
dial.LocalName = m.LocalName
8486
}
85-
return dial.DialAndSend(mail)
8687

88+
if m.MailLogging {
89+
defer func() {
90+
fields := logrus.Fields{
91+
"event": "mail.send",
92+
"mail_type": typ,
93+
"mail_from": m.From,
94+
"mail_to": to,
95+
}
96+
m.Logger.WithFields(fields).Info("mail.send")
97+
}()
98+
}
99+
if err := dial.DialAndSend(mail); err != nil {
100+
return err
101+
}
102+
return nil
87103
}
88104

89105
type MailTemplate struct {

internal/mailer/noop.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66

77
type noopMailClient struct{}
88

9-
func (m *noopMailClient) Mail(to, subjectTemplate, templateURL, defaultTemplate string, templateData map[string]interface{}, headers map[string][]string) error {
9+
func (m *noopMailClient) Mail(to, subjectTemplate, templateURL, defaultTemplate string, templateData map[string]interface{}, headers map[string][]string, typ string) error {
1010
if to == "" {
1111
return errors.New("to field cannot be empty")
1212
}

internal/mailer/template.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
)
1313

1414
type MailClient interface {
15-
Mail(string, string, string, string, map[string]interface{}, map[string][]string) error
15+
Mail(string, string, string, string, map[string]interface{}, map[string][]string, string) error
1616
}
1717

1818
// TemplateMailer will send mail and use templates from the site for easy mail styling
@@ -151,6 +151,7 @@ func (m *TemplateMailer) InviteMail(r *http.Request, user *models.User, otp, ref
151151
defaultInviteMail,
152152
data,
153153
m.Headers("invite"),
154+
"invite",
154155
)
155156
}
156157

@@ -182,6 +183,7 @@ func (m *TemplateMailer) ConfirmationMail(r *http.Request, user *models.User, ot
182183
defaultConfirmationMail,
183184
data,
184185
m.Headers("confirm"),
186+
"confirm",
185187
)
186188
}
187189

@@ -201,6 +203,7 @@ func (m *TemplateMailer) ReauthenticateMail(r *http.Request, user *models.User,
201203
defaultReauthenticateMail,
202204
data,
203205
m.Headers("reauthenticate"),
206+
"reauthenticate",
204207
)
205208
}
206209

@@ -266,6 +269,7 @@ func (m *TemplateMailer) EmailChangeMail(r *http.Request, user *models.User, otp
266269
defaultEmailChangeMail,
267270
data,
268271
m.Headers("email_change"),
272+
"email_change",
269273
)
270274
}(email.Address, email.Otp, email.TokenHash, email.Template)
271275
}
@@ -307,6 +311,7 @@ func (m *TemplateMailer) RecoveryMail(r *http.Request, user *models.User, otp, r
307311
defaultRecoveryMail,
308312
data,
309313
m.Headers("recovery"),
314+
"recovery",
310315
)
311316
}
312317

@@ -338,6 +343,7 @@ func (m *TemplateMailer) MagicLinkMail(r *http.Request, user *models.User, otp,
338343
defaultMagicLinkMail,
339344
data,
340345
m.Headers("magiclink"),
346+
"magiclink",
341347
)
342348
}
343349

@@ -350,6 +356,7 @@ func (m TemplateMailer) Send(user *models.User, subject, body string, data map[s
350356
body,
351357
data,
352358
m.Headers("other"),
359+
"other",
353360
)
354361
}
355362

internal/mailer/template_test.go

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,58 @@ import (
88
)
99

1010
func TestTemplateHeaders(t *testing.T) {
11-
mailer := TemplateMailer{
12-
Config: &conf.GlobalConfiguration{
13-
SMTP: conf.SMTPConfiguration{
14-
Headers: `{"X-Test-A": ["test-a", "test-b"], "X-Test-B": ["test-c", "abc $messageType"]}`,
11+
cases := []struct {
12+
from string
13+
typ string
14+
exp map[string][]string
15+
}{
16+
{
17+
from: `{"x-supabase-project-ref": ["abcjrhohrqmvcpjpsyzc"]}`,
18+
typ: "OTHER-TYPE",
19+
exp: map[string][]string{
20+
"x-supabase-project-ref": {"abcjrhohrqmvcpjpsyzc"},
1521
},
1622
},
17-
}
1823

19-
require.NoError(t, mailer.Config.SMTP.Validate())
24+
{
25+
from: `{"X-Test-A": ["test-a", "test-b"], "X-Test-B": ["test-c", "abc $messageType"]}`,
26+
typ: "TEST-MESSAGE-TYPE",
27+
exp: map[string][]string{
28+
"X-Test-A": {"test-a", "test-b"},
29+
"X-Test-B": {"test-c", "abc TEST-MESSAGE-TYPE"},
30+
},
31+
},
2032

21-
require.Equal(t, mailer.Headers("TEST-MESSAGE-TYPE"), map[string][]string{
22-
"X-Test-A": {"test-a", "test-b"},
23-
"X-Test-B": {"test-c", "abc TEST-MESSAGE-TYPE"},
24-
})
33+
{
34+
from: `{"X-Test-A": ["test-a", "test-b"], "X-Test-B": ["test-c", "abc $messageType"]}`,
35+
typ: "OTHER-TYPE",
36+
exp: map[string][]string{
37+
"X-Test-A": {"test-a", "test-b"},
38+
"X-Test-B": {"test-c", "abc OTHER-TYPE"},
39+
},
40+
},
2541

26-
require.Equal(t, mailer.Headers("OTHER-TYPE"), map[string][]string{
27-
"X-Test-A": {"test-a", "test-b"},
28-
"X-Test-B": {"test-c", "abc OTHER-TYPE"},
29-
})
42+
{
43+
from: `{"X-Test-A": ["test-a", "test-b"], "X-Test-B": ["test-c", "abc $messageType"], "x-supabase-project-ref": ["abcjrhohrqmvcpjpsyzc"]}`,
44+
typ: "OTHER-TYPE",
45+
exp: map[string][]string{
46+
"X-Test-A": {"test-a", "test-b"},
47+
"X-Test-B": {"test-c", "abc OTHER-TYPE"},
48+
"x-supabase-project-ref": {"abcjrhohrqmvcpjpsyzc"},
49+
},
50+
},
51+
}
52+
for _, tc := range cases {
53+
mailer := TemplateMailer{
54+
Config: &conf.GlobalConfiguration{
55+
SMTP: conf.SMTPConfiguration{
56+
Headers: tc.from,
57+
},
58+
},
59+
}
60+
require.NoError(t, mailer.Config.SMTP.Validate())
61+
62+
hdrs := mailer.Headers(tc.typ)
63+
require.Equal(t, hdrs, tc.exp)
64+
}
3065
}

0 commit comments

Comments
 (0)