Browse Source

Email: add reply-to and direct attachment (#18715)

* Add support for `Reply-To` header

* Allow direct attachment

Don't have tests yet, but they will follow
Oleg Gaidarenko 6 years ago
parent
commit
c5bca40566

+ 16 - 6
pkg/models/notifications.go

@@ -5,15 +5,25 @@ import "errors"
 var ErrInvalidEmailCode = errors.New("Invalid or expired email code")
 var ErrSmtpNotEnabled = errors.New("SMTP not configured, check your grafana.ini config file's [smtp] section")
 
+// SendEmailAttachFile is a definition of the attached files without path
+type SendEmailAttachFile struct {
+	Name    string
+	Content []byte
+}
+
+// SendEmailCommand is command for sending emails
 type SendEmailCommand struct {
-	To           []string
-	Template     string
-	Subject      string
-	Data         map[string]interface{}
-	Info         string
-	EmbededFiles []string
+	To            []string
+	Template      string
+	Subject       string
+	Data          map[string]interface{}
+	Info          string
+	ReplyTo       []string
+	EmbededFiles  []string
+	AttachedFiles []*SendEmailAttachFile
 }
 
+// SendEmailCommandSync is command for sending emails in sync
 type SendEmailCommandSync struct {
 	SendEmailCommand
 }

+ 15 - 6
pkg/services/notifications/email.go

@@ -5,13 +5,22 @@ import (
 	"github.com/grafana/grafana/pkg/setting"
 )
 
+// AttachedFile is struct representating email attached files
+type AttachedFile struct {
+	Name    string
+	Content []byte
+}
+
+// Message is representation of the email message
 type Message struct {
-	To           []string
-	From         string
-	Subject      string
-	Body         string
-	Info         string
-	EmbededFiles []string
+	To            []string
+	From          string
+	Subject       string
+	Body          string
+	Info          string
+	ReplyTo       []string
+	EmbededFiles  []string
+	AttachedFiles []*AttachedFile
 }
 
 func setDefaultTemplateData(data map[string]interface{}, u *m.User) {

+ 47 - 8
pkg/services/notifications/mailer.go

@@ -9,13 +9,15 @@ import (
 	"crypto/tls"
 	"fmt"
 	"html/template"
+	"io"
 	"net"
 	"strconv"
 
+	gomail "gopkg.in/mail.v2"
+
 	"github.com/grafana/grafana/pkg/models"
 	"github.com/grafana/grafana/pkg/setting"
 	"github.com/grafana/grafana/pkg/util/errutil"
-	gomail "gopkg.in/mail.v2"
 )
 
 func (ns *NotificationService) send(msg *Message) (int, error) {
@@ -30,8 +32,11 @@ func (ns *NotificationService) send(msg *Message) (int, error) {
 		m.SetHeader("From", msg.From)
 		m.SetHeader("To", address)
 		m.SetHeader("Subject", msg.Subject)
-		for _, file := range msg.EmbededFiles {
-			m.Embed(file)
+
+		ns.setFiles(m, msg)
+
+		for _, replyTo := range msg.ReplyTo {
+			m.SetAddressHeader("Reply-To", replyTo, "")
 		}
 
 		m.SetBody("text/html", msg.Body)
@@ -48,6 +53,23 @@ func (ns *NotificationService) send(msg *Message) (int, error) {
 	return num, err
 }
 
+// setFiles attaches files in various forms
+func (ns *NotificationService) setFiles(
+	m *gomail.Message,
+	msg *Message,
+) {
+	for _, file := range msg.EmbededFiles {
+		m.Embed(file)
+	}
+
+	for _, file := range msg.AttachedFiles {
+		m.Attach(file.Name, gomail.SetCopyFunc(func(writer io.Writer) error {
+			_, err := writer.Write(file.Content)
+			return err
+		}))
+	}
+}
+
 func (ns *NotificationService) createDialer() (*gomail.Dialer, error) {
 	host, port, err := net.SplitHostPort(ns.Cfg.Smtp.Host)
 
@@ -127,10 +149,27 @@ func (ns *NotificationService) buildEmailMessage(cmd *models.SendEmailCommand) (
 	}
 
 	return &Message{
-		To:           cmd.To,
-		From:         fmt.Sprintf("%s <%s>", ns.Cfg.Smtp.FromName, ns.Cfg.Smtp.FromAddress),
-		Subject:      subject,
-		Body:         buffer.String(),
-		EmbededFiles: cmd.EmbededFiles,
+		To:            cmd.To,
+		From:          fmt.Sprintf("%s <%s>", ns.Cfg.Smtp.FromName, ns.Cfg.Smtp.FromAddress),
+		Subject:       subject,
+		Body:          buffer.String(),
+		EmbededFiles:  cmd.EmbededFiles,
+		AttachedFiles: buildAttachedFiles(cmd.AttachedFiles),
 	}, nil
 }
+
+// buildAttachedFiles build attached files
+func buildAttachedFiles(
+	attached []*models.SendEmailAttachFile,
+) []*AttachedFile {
+	result := make([]*AttachedFile, 0)
+
+	for _, file := range attached {
+		result = append(result, &AttachedFile{
+			Name:    file.Name,
+			Content: file.Content,
+		})
+	}
+
+	return result
+}