/
sender.go
155 lines (126 loc) · 3.48 KB
/
sender.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package mail
import (
"errors"
"gopkg.in/gomail.v2"
"github.com/authgear/authgear-server/pkg/lib/config"
"github.com/authgear/authgear-server/pkg/util/log"
)
var ErrMissingSMTPConfiguration = errors.New("mail: configuration is missing")
type SendOptions struct {
Sender string
ReplyTo string
Subject string
Recipient string
TextBody string
HTMLBody string
}
type Logger struct{ *log.Logger }
func NewLogger(lf *log.Factory) Logger { return Logger{lf.New("mail-sender")} }
type Sender struct {
Logger Logger
DevMode config.DevMode
GomailDialer *gomail.Dialer
FeatureTestModeEmailSuppressed config.FeatureTestModeEmailSuppressed
TestModeEmailConfig *config.TestModeEmailConfig
}
func NewGomailDialer(smtp *config.SMTPServerCredentials) *gomail.Dialer {
if smtp != nil {
dialer := gomail.NewDialer(smtp.Host, smtp.Port, smtp.Username, smtp.Password)
switch smtp.Mode {
case config.SMTPModeNormal:
// gomail will infer according to port
case config.SMTPModeSSL:
dialer.SSL = true
}
return dialer
}
return nil
}
type updateGomailMessageFunc func(opts *SendOptions, msg *gomail.Message) error
func (s *Sender) Send(opts SendOptions) (err error) {
if s.FeatureTestModeEmailSuppressed {
s.testModeSend(opts)
return nil
}
if s.TestModeEmailConfig.Enabled {
if r, ok := s.TestModeEmailConfig.MatchTarget(opts.Recipient); ok && r.Suppressed {
s.testModeSend(opts)
return nil
}
}
if s.DevMode {
s.Logger.
WithField("recipient", opts.Recipient).
WithField("body", opts.TextBody).
WithField("sender", opts.Sender).
WithField("subject", opts.Subject).
WithField("reply_to", opts.ReplyTo).
Warn("skip sending email in development mode")
return nil
}
if s.GomailDialer == nil {
err = ErrMissingSMTPConfiguration
return
}
message := gomail.NewMessage()
funcs := []updateGomailMessageFunc{
s.applyFrom,
applyTo,
s.applyReplyTo,
s.applySubject,
applyTextBody,
applyHTMLBody,
}
for _, f := range funcs {
if err = f(&opts, message); err != nil {
return
}
}
err = s.GomailDialer.DialAndSend(message)
if err != nil {
return err
}
return nil
}
func (s *Sender) applyFrom(opts *SendOptions, message *gomail.Message) error {
message.SetHeader("From", opts.Sender)
return nil
}
func applyTo(opts *SendOptions, message *gomail.Message) error {
if opts.Recipient == "" {
return errors.New("mail: recipient address is missing")
}
message.SetHeader("To", opts.Recipient)
return nil
}
func (s *Sender) applyReplyTo(opts *SendOptions, message *gomail.Message) error {
message.SetHeader("Reply-To", opts.ReplyTo)
return nil
}
func (s *Sender) applySubject(opts *SendOptions, message *gomail.Message) error {
message.SetHeader("Subject", opts.Subject)
return nil
}
func applyTextBody(opts *SendOptions, message *gomail.Message) error {
if opts.TextBody == "" {
return errors.New("mail: text body is missing")
}
message.SetBody("text/plain", opts.TextBody)
return nil
}
func applyHTMLBody(opts *SendOptions, message *gomail.Message) error {
if opts.HTMLBody == "" {
return nil
}
message.AddAlternative("text/html", opts.HTMLBody)
return nil
}
func (s *Sender) testModeSend(opts SendOptions) {
s.Logger.
WithField("recipient", opts.Recipient).
WithField("body", opts.TextBody).
WithField("sender", opts.Sender).
WithField("subject", opts.Subject).
WithField("reply_to", opts.ReplyTo).
Warn("sending email is suppressed by test mode")
}