forked from adueck/mimemailer
-
Notifications
You must be signed in to change notification settings - Fork 0
/
mimemailer.go
256 lines (227 loc) 路 6.05 KB
/
mimemailer.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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
package mimemailer
import (
"bytes"
"crypto/tls"
"errors"
"fmt"
"io"
"net"
"net/mail"
"net/smtp"
"os"
"text/template"
"time"
"jaytaylor.com/html2text"
)
// Config holds the SMTP connection and sending configuration info.
type Config struct {
Host string // SMTP Mail Server Name
Port string // Port to connect to SMTP Server
Username string // Username for SMTP Server Login
Password string // Password for SMTP SERVER Login
SenderName string // Name that will appear in the From: of email
SenderAddress string // Email that will appear in the From: of email
}
// Email carries the content and information needed to send a single email.
type Email struct {
ToAddress string
ToName string
ReplyTo string
Subject string
HTML string
Date time.Time
ListUnsubscribe string // Optional value for List-Unsubscribe header. ie. "<mailto:unsubscribe@example.com?subject=unsubscribe-request>"
}
// For use in the text template in making the email.
type emailInfoForTemplate struct {
From string
Name string
To string
ReplyTo string
Subject string
ListUnsubscribe string
HTMLQP string
TextQP string
Date string
}
// Mailer implements a connection for sending SMTP emails.
type Mailer struct {
Config Config
connected bool
client *smtp.Client
}
var (
// tmpl is used to create the email with headers etc for sending via SMTP
tmpl *template.Template
// Connected is a state holder to see if the client is connected or non
connected = false
)
// NewMailer returns a new instance for connecting and sending mail.
// Note that currently only secure TLS connections are supported.
func NewMailer(c Config) (*Mailer, error) {
newMailer := &Mailer{
Config: c,
}
newMailer.connected = false
return newMailer, nil
}
func init() {
// Create the email template
var err error
tmpl, err = template.New("Email Template").Parse(emailTemplate)
if err != nil {
fmt.Println("Error parsing email template")
os.Exit(1)
}
}
// IsConnected checks if the mailer is connected to an SMTP server.
func (m *Mailer) IsConnected() bool {
if m.connected {
return true
}
return false
}
// Connect connects to SMTP Mail server.
func (m *Mailer) Connect() error {
if m.connected {
return errors.New("mimemailer: smtp client is already connected")
}
auth := smtp.PlainAuth(
"",
m.Config.Username,
m.Config.Password,
m.Config.Host,
)
tlsconfig := &tls.Config{
InsecureSkipVerify: true,
ServerName: m.Config.Host,
}
// connect to client
addr := net.JoinHostPort(m.Config.Host, m.Config.Port)
tlsConn, err := tls.Dial("tcp", addr, tlsconfig)
if err != nil {
return err
}
m.client, err = smtp.NewClient(tlsConn, m.Config.Host)
if err != nil {
return err
}
// do auth
err = m.client.Auth(auth)
if err != nil {
return err
}
// connected successfully
m.connected = true
return nil
}
// Disconnect disconnects from SMTP server.
func (m *Mailer) Disconnect() error {
err := m.client.Quit()
if err != nil {
return err
}
m.connected = false
return nil
}
// SendEmail sends a single email. Note that the client must first be connected to the SMTP server.
func (m *Mailer) SendEmail(email Email) error {
// Get from Address from config
fromAddress := mail.Address{Name: m.Config.SenderName, Address: m.Config.SenderAddress}
// Create the headers and body
message, err := email.make(fromAddress)
if err != nil {
return err
}
// do sending
// NOTE: In error handling, need to do mailClient.Reset() to abort message sending halfway through
// Otherwise you get the Error: nested MAIL command from the SMTP
temporarilyConnected := false
if m.IsConnected() == false {
err = m.Connect()
if err != nil {
return err
}
temporarilyConnected = true
}
err = m.client.Mail(m.Config.SenderAddress)
if err != nil {
_ = m.client.Reset()
return err
}
err = m.client.Rcpt(email.ToAddress)
if err != nil {
_ = m.client.Reset()
return err
}
// send body to smtp server
var w io.WriteCloser
w, err = m.client.Data()
if err != nil {
_ = m.client.Reset()
return err
}
_, err = w.Write(message)
if err != nil {
_ = m.client.Reset()
return err
}
err = w.Close()
if err != nil {
_ = m.client.Reset()
return err
}
if temporarilyConnected == true {
err = m.Disconnect()
if err != nil {
return err
}
}
// mail sent successfully
return nil
}
// make() will return an RFC 2045 compliant version of email and headers ready for sending.
func (email Email) make(fromAddress mail.Address) ([]byte, error) {
// Make a text version of the HTML email body
text, err := html2text.FromString(email.HTML, html2text.Options{PrettyTables: false})
if err != nil {
return nil, err
}
// Prepare Sending Address for header fields
toAddress := mail.Address{Name: email.ToName, Address: email.ToAddress}
// Put the content sections (plaintext and HTML) into Quoted Printable format re: RFC 2045
var textQP string
textQP, err = convertToQuotedPrintable(text)
if err != nil {
return nil, err
}
var HTMLQP string
HTMLQP, err = convertToQuotedPrintable(email.HTML)
if err != nil {
return nil, err
}
// Prepare the struct to pass to the templatea
e := emailInfoForTemplate{
From: fromAddress.String(),
Name: fromAddress.Name,
To: toAddress.String(),
ReplyTo: email.ReplyTo,
ListUnsubscribe: email.ListUnsubscribe,
Subject: email.Subject,
Date: email.Date.Format(time.RFC1123Z),
HTMLQP: HTMLQP,
TextQP: textQP,
}
// Compile the template with current email information
var outputBuffer bytes.Buffer
err = tmpl.Execute(&outputBuffer, e)
if err != nil {
return nil, err
}
// Need to convert all the mixed line endings to CRLF for email
// At this point it is miexed. The templated headers use LF, and the printed-quotable sections use CRLF
mixed := outputBuffer.Bytes()
// Convert to have all CRLF (\r\n) line endings re: RFC 2045
allCRLF := makeAllCRLF(mixed)
return allCRLF, nil
}