Simplified emailing system and combined mailer and notifications packages

This commit is contained in:
Torkel Ödegaard
2015-06-08 16:51:25 +02:00
parent c8bc0b3bf8
commit 42fc68baa5
6 changed files with 54 additions and 66 deletions
+13 -9
View File
@@ -5,18 +5,23 @@ import (
"github.com/grafana/grafana/pkg/setting"
)
// Create New mail message use MailFrom and MailUser
func newMailMessageFrom(To []string, from, subject, body string) m.SendEmailCommand {
return m.NewSendEmailCommand(To, from, subject, body)
type Message struct {
To []string
From string
Subject string
Body string
Massive bool
Info string
}
// Create New mail message use MailFrom and MailUser
func newMailMessage(To string, subject, body string) m.SendEmailCommand {
return newMailMessageFrom([]string{To}, setting.Smtp.FromAddress, subject, body)
// create mail content
func (m *Message) Content() string {
contentType := "text/html; charset=UTF-8"
content := "From: " + m.From + "\r\nSubject: " + m.Subject + "\r\nContent-Type: " + contentType + "\r\n\r\n" + m.Body
return content
}
func getMailTmplData(u *m.User) map[interface{}]interface{} {
data := make(map[interface{}]interface{}, 10)
func setDefaultTemplateData(data map[string]interface{}, u *m.User) {
data["AppUrl"] = setting.AppUrl
data["BuildVersion"] = setting.BuildVersion
data["BuildStamp"] = setting.BuildStamp
@@ -25,5 +30,4 @@ func getMailTmplData(u *m.User) map[interface{}]interface{} {
if u != nil {
data["Name"] = u.NameOrFallback()
}
return data
}
+194
View File
@@ -0,0 +1,194 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package notifications
import (
"crypto/tls"
"fmt"
"net"
"net/mail"
"net/smtp"
"os"
"strings"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/setting"
)
var mailQueue chan *Message
func initMailQueue() {
mailQueue = make(chan *Message, 10)
setting.Smtp = setting.SmtpSettings{
Host: "smtp.gmail.com:587",
User: "torkel.odegaard@gmail.com",
Password: "peslpwstnnloiksq",
FromAddress: "grafana@grafana.org",
}
go processMailQueue()
}
func processMailQueue() {
for {
select {
case msg := <-mailQueue:
num, err := buildAndSend(msg)
tos := strings.Join(msg.To, "; ")
info := ""
if err != nil {
if len(msg.Info) > 0 {
info = ", info: " + msg.Info
}
log.Error(4, fmt.Sprintf("Async sent email %d succeed, not send emails: %s%s err: %s", num, tos, info, err))
} else {
log.Trace(fmt.Sprintf("Async sent email %d succeed, sent emails: %s%s", num, tos, info))
}
}
}
}
var addToMailQueue = func(msg *Message) {
mailQueue <- msg
}
func sendToSmtpServer(recipients []string, msgContent []byte) error {
host, port, err := net.SplitHostPort(setting.Smtp.Host)
if err != nil {
return err
}
tlsconfig := &tls.Config{
InsecureSkipVerify: setting.Smtp.SkipVerify,
ServerName: host,
}
if setting.Smtp.CertFile != "" {
cert, err := tls.LoadX509KeyPair(setting.Smtp.CertFile, setting.Smtp.KeyFile)
if err != nil {
return err
}
tlsconfig.Certificates = []tls.Certificate{cert}
}
conn, err := net.Dial("tcp", net.JoinHostPort(host, port))
if err != nil {
return err
}
defer conn.Close()
isSecureConn := false
// Start TLS directly if the port ends with 465 (SMTPS protocol)
if strings.HasSuffix(port, "465") {
conn = tls.Client(conn, tlsconfig)
isSecureConn = true
}
client, err := smtp.NewClient(conn, host)
if err != nil {
return err
}
hostname, err := os.Hostname()
if err != nil {
return err
}
if err = client.Hello(hostname); err != nil {
return err
}
// If not using SMTPS, alway use STARTTLS if available
hasStartTLS, _ := client.Extension("STARTTLS")
if !isSecureConn && hasStartTLS {
if err = client.StartTLS(tlsconfig); err != nil {
return err
}
}
canAuth, options := client.Extension("AUTH")
if canAuth && len(setting.Smtp.User) > 0 {
var auth smtp.Auth
if strings.Contains(options, "CRAM-MD5") {
auth = smtp.CRAMMD5Auth(setting.Smtp.User, setting.Smtp.Password)
} else if strings.Contains(options, "PLAIN") {
auth = smtp.PlainAuth("", setting.Smtp.User, setting.Smtp.Password, host)
}
if auth != nil {
if err = client.Auth(auth); err != nil {
return err
}
}
}
if fromAddress, err := mail.ParseAddress(setting.Smtp.FromAddress); err != nil {
return err
} else {
if err = client.Mail(fromAddress.Address); err != nil {
return err
}
}
for _, rec := range recipients {
if err = client.Rcpt(rec); err != nil {
return err
}
}
w, err := client.Data()
if err != nil {
return err
}
if _, err = w.Write([]byte(msgContent)); err != nil {
return err
}
if err = w.Close(); err != nil {
return err
}
return client.Quit()
}
func buildAndSend(msg *Message) (int, error) {
log.Trace("Sending mails to: %s", strings.Join(msg.To, "; "))
// get message body
content := msg.Content()
if len(msg.To) == 0 {
return 0, fmt.Errorf("empty receive emails")
} else if len(msg.Body) == 0 {
return 0, fmt.Errorf("empty email body")
}
if msg.Massive {
// send mail to multiple emails one by one
num := 0
for _, to := range msg.To {
body := []byte("To: " + to + "\r\n" + content)
err := sendToSmtpServer([]string{to}, body)
if err != nil {
return num, err
}
num++
}
return num, nil
} else {
body := []byte("To: " + strings.Join(msg.To, ";") + "\r\n" + content)
// send to multiple emails in one message
err := sendToSmtpServer(msg.To, body)
if err != nil {
return 0, err
} else {
return 1, nil
}
}
}
+23 -12
View File
@@ -16,8 +16,11 @@ var mailTemplates *template.Template
var tmplResetPassword = "reset_password.html"
func Init() error {
initMailQueue()
bus.AddHandler("email", sendResetPasswordEmail)
bus.AddHandler("email", validateResetPasswordCode)
bus.AddHandler("email", sendEmailCommandHandler)
mailTemplates = template.New("name")
mailTemplates.Funcs(template.FuncMap{
@@ -41,26 +44,23 @@ func Init() error {
return nil
}
var dispatchMail = func(cmd *m.SendEmailCommand) error {
return bus.Dispatch(cmd)
}
func subjectTemplateFunc(obj map[string]interface{}, value string) string {
obj["value"] = value
return ""
}
func sendResetPasswordEmail(cmd *m.SendResetPasswordEmailCommand) error {
func sendEmailCommandHandler(cmd *m.SendEmailCommand) error {
var buffer bytes.Buffer
data := cmd.Data
if data == nil {
data = make(map[string]interface{}, 10)
}
var data = getMailTmplData(cmd.User)
code := createUserEmailCode(cmd.User, nil)
data["Code"] = code
setDefaultTemplateData(data, nil)
mailTemplates.ExecuteTemplate(&buffer, cmd.Template, data)
mailTemplates.ExecuteTemplate(&buffer, tmplResetPassword, data)
dispatchMail(&m.SendEmailCommand{
To: []string{cmd.User.Email},
addToMailQueue(&Message{
To: cmd.To,
From: setting.Smtp.FromAddress,
Subject: data["Subject"].(map[string]interface{})["value"].(string),
Body: buffer.String(),
@@ -69,6 +69,17 @@ func sendResetPasswordEmail(cmd *m.SendResetPasswordEmailCommand) error {
return nil
}
func sendResetPasswordEmail(cmd *m.SendResetPasswordEmailCommand) error {
return sendEmailCommandHandler(&m.SendEmailCommand{
To: []string{cmd.User.Email},
Template: tmplResetPassword,
Data: map[string]interface{}{
"Code": createUserEmailCode(cmd.User, nil),
"Name": cmd.User.NameOrFallback(),
},
})
}
func validateResetPasswordCode(query *m.ValidateResetPasswordCodeQuery) error {
login := getLoginForEmailCode(query.Code)
if login == "" {
@@ -20,17 +20,16 @@ func TestNotifications(t *testing.T) {
err := Init()
So(err, ShouldBeNil)
var sentMail *m.SendEmailCommand
dispatchMail = func(cmd *m.SendEmailCommand) error {
sentMail = cmd
return nil
var sentMsg *Message
addToMailQueue = func(msg *Message) {
sentMsg = msg
}
Convey("When sending reset email password", func() {
sendResetPasswordEmail(&m.SendResetPasswordEmailCommand{User: &m.User{Email: "asd@asd.com"}})
So(sentMail.Body, ShouldContainSubstring, "body")
So(sentMail.Subject, ShouldEqual, "Reset your Grafana password")
So(sentMail.Body, ShouldNotContainSubstring, "Subject")
So(sentMsg.Body, ShouldContainSubstring, "body")
So(sentMsg.Subject, ShouldEqual, "Reset your Grafana password")
So(sentMsg.Body, ShouldNotContainSubstring, "Subject")
})
})