Skip to content

Commit

Permalink
feat: mailer logging (#1805)
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
cstockton and Chris Stockton authored Oct 15, 2024
1 parent 99d6a13 commit 9354b83
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 44 deletions.
17 changes: 9 additions & 8 deletions internal/conf/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -342,14 +342,15 @@ type ProviderConfiguration struct {
}

type SMTPConfiguration struct {
MaxFrequency time.Duration `json:"max_frequency" split_words:"true"`
Host string `json:"host"`
Port int `json:"port,omitempty" default:"587"`
User string `json:"user"`
Pass string `json:"pass,omitempty"`
AdminEmail string `json:"admin_email" split_words:"true"`
SenderName string `json:"sender_name" split_words:"true"`
Headers string `json:"headers"`
MaxFrequency time.Duration `json:"max_frequency" split_words:"true"`
Host string `json:"host"`
Port int `json:"port,omitempty" default:"587"`
User string `json:"user"`
Pass string `json:"pass,omitempty"`
AdminEmail string `json:"admin_email" split_words:"true"`
SenderName string `json:"sender_name" split_words:"true"`
Headers string `json:"headers"`
LoggingEnabled bool `json:"logging_enabled" split_words:"true" default:"false"`

fromAddress string `json:"-"`
normalizedHeaders map[string][]string `json:"-"`
Expand Down
4 changes: 4 additions & 0 deletions internal/conf/configuration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,12 @@ func TestGlobal(t *testing.T) {
os.Setenv("API_EXTERNAL_URL", "http://localhost:9999")
os.Setenv("GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_URI", "pg-functions://postgres/auth/count_failed_attempts")
os.Setenv("GOTRUE_HOOK_SEND_SMS_SECRETS", "v1,whsec_aWxpa2VzdXBhYmFzZXZlcnltdWNoYW5kaWhvcGV5b3Vkb3Rvbw==")
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"]}`)
os.Setenv("GOTRUE_SMTP_LOGGING_ENABLED", "true")
gc, err := LoadGlobal("")
require.NoError(t, err)
assert.Equal(t, true, gc.SMTP.LoggingEnabled)
assert.Equal(t, "project_ref", gc.SMTP.NormalizedHeaders()["X-PM-Metadata-project-ref"][0])
require.NotNil(t, gc)
assert.Equal(t, "X-Request-ID", gc.API.RequestIDHeader)
assert.Equal(t, "pg-functions://postgres/auth/count_failed_attempts", gc.Hook.MFAVerificationAttempt.URI)
Expand Down
17 changes: 9 additions & 8 deletions internal/mailer/mailer.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,15 @@ func NewMailer(globalConfig *conf.GlobalConfiguration) Mailer {
mailClient = &noopMailClient{}
} else {
mailClient = &MailmeMailer{
Host: globalConfig.SMTP.Host,
Port: globalConfig.SMTP.Port,
User: globalConfig.SMTP.User,
Pass: globalConfig.SMTP.Pass,
LocalName: u.Hostname(),
From: from,
BaseURL: globalConfig.SiteURL,
Logger: logrus.StandardLogger(),
Host: globalConfig.SMTP.Host,
Port: globalConfig.SMTP.Port,
User: globalConfig.SMTP.User,
Pass: globalConfig.SMTP.Pass,
LocalName: u.Hostname(),
From: from,
BaseURL: globalConfig.SiteURL,
Logger: logrus.StandardLogger(),
MailLogging: globalConfig.SMTP.LoggingEnabled,
}
}

Expand Down
40 changes: 28 additions & 12 deletions internal/mailer/mailme.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,22 @@ const TemplateExpiration = 10 * time.Second

// MailmeMailer lets MailMe send templated mails
type MailmeMailer struct {
From string
Host string
Port int
User string
Pass string
BaseURL string
LocalName string
FuncMap template.FuncMap
cache *TemplateCache
Logger logrus.FieldLogger
From string
Host string
Port int
User string
Pass string
BaseURL string
LocalName string
FuncMap template.FuncMap
cache *TemplateCache
Logger logrus.FieldLogger
MailLogging bool
}

// Mail sends a templated mail. It will try to load the template from a URL, and
// otherwise fall back to the default
func (m *MailmeMailer) Mail(to, subjectTemplate, templateURL, defaultTemplate string, templateData map[string]interface{}, headers map[string][]string) error {
func (m *MailmeMailer) Mail(to, subjectTemplate, templateURL, defaultTemplate string, templateData map[string]interface{}, headers map[string][]string, typ string) error {
if m.FuncMap == nil {
m.FuncMap = map[string]interface{}{}
}
Expand All @@ -60,6 +61,7 @@ func (m *MailmeMailer) Mail(to, subjectTemplate, templateURL, defaultTemplate st
if err != nil {
return err
}

body, err := m.MailBody(templateURL, defaultTemplate, templateData)
if err != nil {
return err
Expand All @@ -82,8 +84,22 @@ func (m *MailmeMailer) Mail(to, subjectTemplate, templateURL, defaultTemplate st
if m.LocalName != "" {
dial.LocalName = m.LocalName
}
return dial.DialAndSend(mail)

if m.MailLogging {
defer func() {
fields := logrus.Fields{
"event": "mail.send",
"mail_type": typ,
"mail_from": m.From,
"mail_to": to,
}
m.Logger.WithFields(fields).Info("mail.send")
}()
}
if err := dial.DialAndSend(mail); err != nil {
return err
}
return nil
}

type MailTemplate struct {
Expand Down
2 changes: 1 addition & 1 deletion internal/mailer/noop.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (

type noopMailClient struct{}

func (m *noopMailClient) Mail(to, subjectTemplate, templateURL, defaultTemplate string, templateData map[string]interface{}, headers map[string][]string) error {
func (m *noopMailClient) Mail(to, subjectTemplate, templateURL, defaultTemplate string, templateData map[string]interface{}, headers map[string][]string, typ string) error {
if to == "" {
return errors.New("to field cannot be empty")
}
Expand Down
9 changes: 8 additions & 1 deletion internal/mailer/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
)

type MailClient interface {
Mail(string, string, string, string, map[string]interface{}, map[string][]string) error
Mail(string, string, string, string, map[string]interface{}, map[string][]string, string) error
}

// TemplateMailer will send mail and use templates from the site for easy mail styling
Expand Down Expand Up @@ -151,6 +151,7 @@ func (m *TemplateMailer) InviteMail(r *http.Request, user *models.User, otp, ref
defaultInviteMail,
data,
m.Headers("invite"),
"invite",
)
}

Expand Down Expand Up @@ -182,6 +183,7 @@ func (m *TemplateMailer) ConfirmationMail(r *http.Request, user *models.User, ot
defaultConfirmationMail,
data,
m.Headers("confirm"),
"confirm",
)
}

Expand All @@ -201,6 +203,7 @@ func (m *TemplateMailer) ReauthenticateMail(r *http.Request, user *models.User,
defaultReauthenticateMail,
data,
m.Headers("reauthenticate"),
"reauthenticate",
)
}

Expand Down Expand Up @@ -266,6 +269,7 @@ func (m *TemplateMailer) EmailChangeMail(r *http.Request, user *models.User, otp
defaultEmailChangeMail,
data,
m.Headers("email_change"),
"email_change",
)
}(email.Address, email.Otp, email.TokenHash, email.Template)
}
Expand Down Expand Up @@ -307,6 +311,7 @@ func (m *TemplateMailer) RecoveryMail(r *http.Request, user *models.User, otp, r
defaultRecoveryMail,
data,
m.Headers("recovery"),
"recovery",
)
}

Expand Down Expand Up @@ -338,6 +343,7 @@ func (m *TemplateMailer) MagicLinkMail(r *http.Request, user *models.User, otp,
defaultMagicLinkMail,
data,
m.Headers("magiclink"),
"magiclink",
)
}

Expand All @@ -350,6 +356,7 @@ func (m TemplateMailer) Send(user *models.User, subject, body string, data map[s
body,
data,
m.Headers("other"),
"other",
)
}

Expand Down
63 changes: 49 additions & 14 deletions internal/mailer/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,58 @@ import (
)

func TestTemplateHeaders(t *testing.T) {
mailer := TemplateMailer{
Config: &conf.GlobalConfiguration{
SMTP: conf.SMTPConfiguration{
Headers: `{"X-Test-A": ["test-a", "test-b"], "X-Test-B": ["test-c", "abc $messageType"]}`,
cases := []struct {
from string
typ string
exp map[string][]string
}{
{
from: `{"x-supabase-project-ref": ["abcjrhohrqmvcpjpsyzc"]}`,
typ: "OTHER-TYPE",
exp: map[string][]string{
"x-supabase-project-ref": {"abcjrhohrqmvcpjpsyzc"},
},
},
}

require.NoError(t, mailer.Config.SMTP.Validate())
{
from: `{"X-Test-A": ["test-a", "test-b"], "X-Test-B": ["test-c", "abc $messageType"]}`,
typ: "TEST-MESSAGE-TYPE",
exp: map[string][]string{
"X-Test-A": {"test-a", "test-b"},
"X-Test-B": {"test-c", "abc TEST-MESSAGE-TYPE"},
},
},

require.Equal(t, mailer.Headers("TEST-MESSAGE-TYPE"), map[string][]string{
"X-Test-A": {"test-a", "test-b"},
"X-Test-B": {"test-c", "abc TEST-MESSAGE-TYPE"},
})
{
from: `{"X-Test-A": ["test-a", "test-b"], "X-Test-B": ["test-c", "abc $messageType"]}`,
typ: "OTHER-TYPE",
exp: map[string][]string{
"X-Test-A": {"test-a", "test-b"},
"X-Test-B": {"test-c", "abc OTHER-TYPE"},
},
},

require.Equal(t, mailer.Headers("OTHER-TYPE"), map[string][]string{
"X-Test-A": {"test-a", "test-b"},
"X-Test-B": {"test-c", "abc OTHER-TYPE"},
})
{
from: `{"X-Test-A": ["test-a", "test-b"], "X-Test-B": ["test-c", "abc $messageType"], "x-supabase-project-ref": ["abcjrhohrqmvcpjpsyzc"]}`,
typ: "OTHER-TYPE",
exp: map[string][]string{
"X-Test-A": {"test-a", "test-b"},
"X-Test-B": {"test-c", "abc OTHER-TYPE"},
"x-supabase-project-ref": {"abcjrhohrqmvcpjpsyzc"},
},
},
}
for _, tc := range cases {
mailer := TemplateMailer{
Config: &conf.GlobalConfiguration{
SMTP: conf.SMTPConfiguration{
Headers: tc.from,
},
},
}
require.NoError(t, mailer.Config.SMTP.Validate())

hdrs := mailer.Headers(tc.typ)
require.Equal(t, hdrs, tc.exp)
}
}

0 comments on commit 9354b83

Please sign in to comment.