-
Notifications
You must be signed in to change notification settings - Fork 2
/
smtp.go
153 lines (120 loc) · 3.47 KB
/
smtp.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
package smtp
import (
"bytes"
"context"
"crypto/tls"
"flag"
"fmt"
"io"
"net/smtp"
"strings"
"sync"
"github.com/ViBiOh/flags"
"github.com/ViBiOh/httputils/v4/pkg/telemetry"
mailer_metric "github.com/ViBiOh/mailer/pkg/metric"
"github.com/ViBiOh/mailer/pkg/model"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/trace"
)
var bufferPool = sync.Pool{
New: func() any {
return bytes.NewBuffer(nil)
},
}
type Service struct {
auth smtp.Auth
tracer trace.Tracer
address string
host string
}
type Config struct {
Address string
Username string
Password string
Host string
}
func Flags(fs *flag.FlagSet, prefix string) *Config {
var config Config
flags.New("Address", "Address").Prefix(prefix).DocPrefix("smtp").StringVar(fs, &config.Address, "localhost:25", nil)
flags.New("Username", "Plain Auth Username").Prefix(prefix).DocPrefix("smtp").StringVar(fs, &config.Username, "", nil)
flags.New("Password", "Plain Auth Password").Prefix(prefix).DocPrefix("smtp").StringVar(fs, &config.Password, "", nil)
flags.New("Host", "Plain Auth host").Prefix(prefix).DocPrefix("smtp").StringVar(fs, &config.Host, "localhost", nil)
return &config
}
func New(config *Config, meterProvider metric.MeterProvider, tracerProvider trace.TracerProvider) Service {
var auth smtp.Auth
if len(config.Username) > 0 {
auth = smtp.PlainAuth("", config.Username, config.Password, config.Host)
}
mailer_metric.Create(meterProvider, "mailer.smtp")
service := Service{
address: config.Address,
auth: auth,
host: config.Host,
}
if tracerProvider != nil {
service.tracer = tracerProvider.Tracer("smtp")
}
return service
}
func (s Service) Send(ctx context.Context, mail model.Mail) error {
var err error
_, end := telemetry.StartSpan(ctx, s.tracer, "send")
defer end(&err)
body := bufferPool.Get().(*bytes.Buffer)
defer bufferPool.Put(body)
body.Reset()
fmt.Fprintf(body, "From: %s <%s>\r\n", mail.Sender, mail.From)
fmt.Fprintf(body, "To: %s\r\n", strings.Join(mail.To, ","))
fmt.Fprintf(body, "Subject: %s\r\n", mail.Subject)
body.WriteString("Content-Type: text/html; charset=\"utf-8\"\r\n")
body.WriteString("\r\n")
if _, err = io.Copy(body, mail.Content); err != nil {
return fmt.Errorf("read mail content: %w", err)
}
body.WriteString("\r\n")
err = SendMail(s.address, s.host, s.auth, mail.From, mail.To, body.Bytes())
if err != nil {
mailer_metric.Increase(ctx, "smtp", "error")
} else {
mailer_metric.Increase(ctx, "smtp", "success")
}
return err
}
func SendMail(addr, host string, auth smtp.Auth, from string, to []string, body []byte) error {
smtpConn, err := tls.Dial("tcp", addr, &tls.Config{
ServerName: host,
})
if err != nil {
return fmt.Errorf("dial: %w", err)
}
smtpClient, err := smtp.NewClient(smtpConn, host)
if err != nil {
return fmt.Errorf("client: %w", err)
}
defer smtpClient.Close()
if auth != nil {
if err = smtpClient.Auth(auth); err != nil {
return fmt.Errorf("auth: %w", err)
}
}
if err = smtpClient.Mail(from); err != nil {
return fmt.Errorf("mail: %w", err)
}
for _, recipient := range to {
if err = smtpClient.Rcpt(recipient); err != nil {
return fmt.Errorf("recipient `%s`: %w", recipient, err)
}
}
writer, err := smtpClient.Data()
if err != nil {
return fmt.Errorf("data: %w", err)
}
if _, err = writer.Write(body); err != nil {
return fmt.Errorf("write: %w", err)
}
if err = writer.Close(); err != nil {
return fmt.Errorf("close: %w", err)
}
return smtpClient.Quit()
}