-
Notifications
You must be signed in to change notification settings - Fork 3
/
smtp.go
104 lines (92 loc) · 2.98 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
/*
Copyright (c) JSC iCore.
This source code is licensed under the MIT license found in the
LICENSE file in the root directory of this source tree.
*/
package notifr
import (
"fmt"
"net"
"strings"
"time"
"github.com/domodwyer/mailyak"
strip "github.com/grokify/html-strip-tags-go"
blackfriday "github.com/russross/blackfriday/v2"
)
// SMTPConfig is configuration for SMTP Relay connection.
type SMTPConfig struct {
Host string `envconfig:"host" required:"true" desc:"a host of an SMTP relay"`
Port int `envconfig:"port" default:"587" desc:"a port of an SMTP relay"`
From string `envconfig:"from" desc:"a sender email address"`
Retries []time.Duration `envconfig:"retries" default:"10s,1m,10m" desc:"intervals to retry email sending"`
}
// SMTPSender is a message sender that sends a message by SMTP.
type SMTPSender struct {
SMTPConfig
sendfn func(*mailyak.MailYak) error
}
// NewSMTPSender returns a new SMTPSender.
func NewSMTPSender(cnf SMTPConfig) *SMTPSender {
return &SMTPSender{
SMTPConfig: cnf,
sendfn: func(mail *mailyak.MailYak) error { return mail.Send() },
}
}
// Email header fields (including the Subject field) can be multi-line, with each line recommended to be no more than 78 characters.
// Header fields defined by RFC 5322 (https://tools.ietf.org/html/rfc5322#section-2.2).
// More details about line length limits in the RFC 2822 (https://tools.ietf.org/html/rfc2822#section-2.1.1).
const subjectMaxLen = 78
// Send sends a message by SMTP.
// The method tries to re-send a message when the previous sending failed with a temporary network error.
func (s *SMTPSender) Send(recipients []string, msg Message) error {
// These actions allow to correctly display the tables in the received emails, otherwise, without using CSS, the table frames are not displayed.
css := `<style>table,th,td{border: 1px solid black;} tr:nth-child(even){background-color: grey;}</style>`
md := string(blackfriday.Run([]byte(msg.Text)))
html := `
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<title>Message</title>` +
css +
`</head>
<body>` +
md +
`</body>
</html>
`
mail := mailyak.New(fmt.Sprintf("%s:%d", s.Host, s.Port), nil)
mail.To(recipients...)
if s.From != "" {
mail.From(s.From)
}
subject := msg.Subject
if subject == "" {
plainText := strip.StripTags(md)
for _, line := range strings.Split(plainText, "\n") {
if line != "" {
subject = line
break
}
}
if len(subject) > subjectMaxLen {
subject = subject[:subjectMaxLen]
}
}
mail.Subject(subject)
mail.Plain().Set(msg.Text)
mail.HTML().Set(html)
var err error
for _, n := range s.Retries {
if err = s.sendfn(mail); err == nil {
break
}
if v, ok := err.(net.Error); !(ok && v.Temporary()) {
return err
}
time.Sleep(n)
}
if err != nil {
return err
}
return nil
}