forked from aarondl/authboss
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsmtp_mailer.go
More file actions
137 lines (113 loc) · 3.28 KB
/
Copy pathsmtp_mailer.go
File metadata and controls
137 lines (113 loc) · 3.28 KB
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
package defaults
import (
"bytes"
"context"
"fmt"
"math/rand"
"net/smtp"
"strings"
"text/template"
"time"
"github.com/friendsofgo/errors"
"github.com/aarondl/authboss/v3"
)
// NewSMTPMailer creates an SMTP Mailer to send emails with.
// An example usage might be something like:
//
// NewSMTPMailer("smtp.gmail.com",
// smtp.PlainAuth("", "admin@yoursite.com", "password", "smtp.gmail.com"))
func NewSMTPMailer(server string, auth smtp.Auth) *SMTPMailer {
if len(server) == 0 {
panic("SMTP Mailer must be created with a server string.")
}
random := rand.New(rand.NewSource(time.Now().UnixNano()))
return &SMTPMailer{server, auth, random}
}
// SMTPMailer uses smtp to actually send e-mails
type SMTPMailer struct {
Server string
Auth smtp.Auth
rand *rand.Rand
}
// Send an e-mail
func (s SMTPMailer) Send(ctx context.Context, mail authboss.Email) error {
if len(mail.TextBody) == 0 && len(mail.HTMLBody) == 0 {
return errors.New("refusing to send mail without text or html body")
}
buf := &bytes.Buffer{}
data := struct {
Boundary string
Mail authboss.Email
}{
Boundary: s.boundary(),
Mail: mail,
}
err := emailTmpl.Execute(buf, data)
if err != nil {
return err
}
toSend := bytes.Replace(buf.Bytes(), []byte{'\n'}, []byte{'\r', '\n'}, -1)
return smtp.SendMail(s.Server, s.Auth, mail.From, mail.To, toSend)
}
// boundary makes mime boundaries, these are largely useless strings that just
// need to be the same in the mime structure. We choose from the alphabet below
// and create a random string of length 23
// Example:
// 284fad24nao8f4na284f2n4
func (s SMTPMailer) boundary() string {
const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789"
buf := &bytes.Buffer{}
for i := 0; i < 23; i++ {
buf.WriteByte(alphabet[s.rand.Int()%len(alphabet)])
}
return buf.String()
}
func namedAddress(name, address string) string {
if len(name) == 0 {
return address
}
return fmt.Sprintf("%s <%s>", name, address)
}
func namedAddresses(names, addresses []string) string {
if len(names) == 0 {
return strings.Join(addresses, ", ")
}
buf := &bytes.Buffer{}
first := true
for i, address := range addresses {
if first {
first = false
} else {
buf.WriteString(", ")
}
buf.WriteString(namedAddress(names[i], address))
}
return buf.String()
}
var emailTmpl = template.Must(template.New("email").Funcs(template.FuncMap{
"join": strings.Join,
"namedAddress": namedAddress,
"namedAddresses": namedAddresses,
}).Parse(`To: {{namedAddresses .Mail.ToNames .Mail.To}}{{if .Mail.Cc}}
Cc: {{namedAddresses .Mail.CcNames .Mail.Cc}}{{end}}{{if .Mail.Bcc}}
Bcc: {{namedAddresses .Mail.BccNames .Mail.Bcc}}{{end}}
From: {{namedAddress .Mail.FromName .Mail.From}}
Subject: {{.Mail.Subject}}{{if .Mail.ReplyTo}}
Reply-To: {{namedAddress .Mail.ReplyToName .Mail.ReplyTo}}{{end}}
MIME-Version: 1.0
Content-Type: multipart/alternative; boundary="==============={{.Boundary}}=="
Content-Transfer-Encoding: 7bit
{{if .Mail.TextBody -}}
--==============={{.Boundary}}==
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 7bit
{{.Mail.TextBody}}
{{end -}}
{{if .Mail.HTMLBody -}}
--==============={{.Boundary}}==
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: 7bit
{{.Mail.HTMLBody}}
{{end -}}
--==============={{.Boundary}}==--
`))