diff --git a/pkg/models/notifications.go b/pkg/models/notifications.go index e734dfd8e4a..44abf19fcbe 100644 --- a/pkg/models/notifications.go +++ b/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") -type SendEmailCommand struct { - To []string - Template string - Subject string - Data map[string]interface{} - Info string - EmbededFiles []string +// 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 + ReplyTo []string + EmbededFiles []string + AttachedFiles []*SendEmailAttachFile +} + +// SendEmailCommandSync is command for sending emails in sync type SendEmailCommandSync struct { SendEmailCommand } diff --git a/pkg/services/notifications/email.go b/pkg/services/notifications/email.go index 803f2096b56..aa10a7423c4 100644 --- a/pkg/services/notifications/email.go +++ b/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) { diff --git a/pkg/services/notifications/mailer.go b/pkg/services/notifications/mailer.go index 584914f5ff7..17d0a4c0d57 100644 --- a/pkg/services/notifications/mailer.go +++ b/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 +}