/
message.go
155 lines (132 loc) · 4.16 KB
/
message.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 maillist
import (
"bytes"
"database/sql"
"errors"
"fmt"
"html/template"
"strings"
"github.com/sendgrid/sendgrid-go"
"github.com/sendgrid/sendgrid-go/helpers/mail"
)
// Message is a single email. It keeps track of whether the message has been
// sent or not.
type Message struct {
SubscriberID int64 `db:"subscriber_id" validate:"required"`
CampaignID int64 `db:"campaign_id" validate:"required"`
Status string `db:"status" validate:"eq=pending|eq=sent|eq=failed|eq=cancelled"`
CreateTime int64 `db:"create_time" validate:"required"`
}
// InsertMessage inserts a message into the database. It's ID field will be
// updated.
func (s *Session) InsertMessage(m *Message) error {
return s.insert(m)
}
// pendingMessage retrieves a single message that is waiting to be sent
func pendingMessage(s *Session) (*Message, error) {
var m Message
query := fmt.Sprintf("select %s from message where status='pending' limit 1",
s.selectString(&m))
err := s.dbmap.SelectOne(&m, query)
if err == sql.ErrNoRows {
return nil, ErrNotFound
} else if err != nil {
return nil, err
}
return &m, nil
}
// sendMessage sends a single message to it's destination
func (s *Session) sendMessage(m *Message) error {
var email *mail.SGMailV3
var err error
var spam bool
if email, err = buildEmail(s, m); err != nil {
return err
}
if spam, err = s.HasReportedSpam(email.Personalizations[0].To[0].Address); err != nil {
return err
} else if spam {
return nil
}
if s.config.JustPrint {
s.info(string(printEmail(email)))
} else if err = s.send(email); err != nil {
return err
}
if _, err = s.dbmap.Exec("update message set status='sent' where subscriber_id=? and campaign_id=?",
m.SubscriberID, m.CampaignID); err != nil {
return fmt.Errorf("couldn't update message status: %v", err)
}
if err = s.updateCampaignStatus(m.CampaignID); err != nil {
return fmt.Errorf("couldn't update campaign status: %v", err)
}
return nil
}
func (s *Session) send(m *mail.SGMailV3) error {
request := sendgrid.GetRequest(s.config.SendGridAPIKey, "/v3/mail/send", "https://api.sendgrid.com")
request.Method = "POST"
request.Body = mail.GetRequestBody(m)
response, err := sendgrid.API(request)
if err != nil {
s.error(err)
return err
}
if response.StatusCode < 200 || response.StatusCode >= 300 {
err := errors.New(response.Body)
s.error(err)
return err
}
return nil
}
// buildEmail creates a new email in the format expected by sendgrid
func buildEmail(s *Session, m *Message) (*mail.SGMailV3, error) {
sub, err := s.GetSubscriber(m.SubscriberID)
if err != nil {
return nil, fmt.Errorf("couldn't get subscriber: %v", err)
}
campaign, err := s.GetCampaign(m.CampaignID)
if err != nil {
return nil, fmt.Errorf("couldn't get campaign %d: %v", m.CampaignID, err)
}
account, err := s.GetAccount(campaign.AccountID)
if err != nil {
return nil, fmt.Errorf("couldn't get account: %v", err)
}
to := mail.NewEmail(sub.FirstName+" "+sub.LastName,
sub.Email)
subject := campaign.Subject
if s.templates[m.CampaignID] == nil {
t, err := template.New("").Parse(campaign.Body)
if err != nil {
return nil, err
}
s.templates[m.CampaignID] = t
}
var buf bytes.Buffer
token, _ := s.UnsubscribeToken(sub)
bodyStruct := struct {
FirstName, LastName, UnsubscribeURL string
}{sub.FirstName, sub.LastName, s.config.UnsubscribeURL + "/" + token}
if err := s.templates[m.CampaignID].Execute(&buf, &bodyStruct); err != nil {
return nil, err
}
contentType := "text/plain"
if strings.Contains(campaign.Body, "DOCTYPE") {
contentType = "text/html"
}
content := mail.NewContent(contentType, buf.String())
from := mail.NewEmail(account.FirstName+" "+account.LastName,
account.Email)
return mail.NewV3MailInit(from, subject, to, content), nil
}
// printEmail just prints an email to stderr. It is useful for
// debugging/logging
func printEmail(m *mail.SGMailV3) []byte {
s := fmt.Sprintln("Email to send")
s += fmt.Sprintf("To: %s (%s)\n",
m.Personalizations[0].To[0].Address,
m.Personalizations[0].To[0].Name)
s += fmt.Sprintf("From: %s (%s)\n", m.From.Address, m.From.Name)
s += fmt.Sprintf("Subject: %s\nBody: %s\n", m.Subject, m.Content[0].Value)
return []byte(s)
}