Commit 0f0772b6 by Marcus Efraimsson Committed by GitHub

Alerting: Adds support for sending a single email to all recipients in…

Alerting: Adds support for sending a single email to all recipients in notification channel (#21091)

When an alert is sent by e-mail, the process sends an e-mail to 
each recipient separately. This PR is a single delivery to all recipients.
For companies that use e-mail extensively, this is necessary in order 
not to overload the sending queue.

Replaces #18013
Fixes #12650

Co-authored-by: Henrique Oliveira <holiiveira@users.noreply.github.com>
parent 8a02fa76
...@@ -14,6 +14,7 @@ type SendEmailAttachFile struct { ...@@ -14,6 +14,7 @@ type SendEmailAttachFile struct {
// SendEmailCommand is command for sending emails // SendEmailCommand is command for sending emails
type SendEmailCommand struct { type SendEmailCommand struct {
To []string To []string
SingleEmail bool
Template string Template string
Subject string Subject string
Data map[string]interface{} Data map[string]interface{}
......
...@@ -19,11 +19,23 @@ func init() { ...@@ -19,11 +19,23 @@ func init() {
Description: "Sends notifications using Grafana server configured SMTP settings", Description: "Sends notifications using Grafana server configured SMTP settings",
Factory: NewEmailNotifier, Factory: NewEmailNotifier,
OptionsTemplate: ` OptionsTemplate: `
<h3 class="page-heading">Email addresses</h3> <h3 class="page-heading">Email settings</h3>
<div class="gf-form"> <div class="gf-form">
<textarea rows="7" class="gf-form-input width-27" required ng-model="ctrl.model.settings.addresses"></textarea> <gf-form-switch
class="gf-form"
label="Single email"
label-class="width-8"
checked="ctrl.model.settings.singleEmail"
tooltip="Send a single email to all recipients">
</gf-form-switch>
</div> </div>
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label width-8">
Addresses
</label>
<textarea rows="7" class="gf-form-input width-27" required ng-model="ctrl.model.settings.addresses"></textarea>
</div>
<div class="gf-form offset-width-8">
<span>You can enter multiple email addresses using a ";" separator</span> <span>You can enter multiple email addresses using a ";" separator</span>
</div> </div>
`, `,
...@@ -35,6 +47,7 @@ func init() { ...@@ -35,6 +47,7 @@ func init() {
type EmailNotifier struct { type EmailNotifier struct {
NotifierBase NotifierBase
Addresses []string Addresses []string
SingleEmail bool
log log.Logger log log.Logger
} }
...@@ -42,6 +55,7 @@ type EmailNotifier struct { ...@@ -42,6 +55,7 @@ type EmailNotifier struct {
// for the EmailNotifier. // for the EmailNotifier.
func NewEmailNotifier(model *models.AlertNotification) (alerting.Notifier, error) { func NewEmailNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
addressesString := model.Settings.Get("addresses").MustString() addressesString := model.Settings.Get("addresses").MustString()
singleEmail := model.Settings.Get("singleEmail").MustBool(false)
if addressesString == "" { if addressesString == "" {
return nil, alerting.ValidationError{Reason: "Could not find addresses in settings"} return nil, alerting.ValidationError{Reason: "Could not find addresses in settings"}
...@@ -53,13 +67,14 @@ func NewEmailNotifier(model *models.AlertNotification) (alerting.Notifier, error ...@@ -53,13 +67,14 @@ func NewEmailNotifier(model *models.AlertNotification) (alerting.Notifier, error
return &EmailNotifier{ return &EmailNotifier{
NotifierBase: NewNotifierBase(model), NotifierBase: NewNotifierBase(model),
Addresses: addresses, Addresses: addresses,
SingleEmail: singleEmail,
log: log.New("alerting.notifier.email"), log: log.New("alerting.notifier.email"),
}, nil }, nil
} }
// Notify sends the alert notification. // Notify sends the alert notification.
func (en *EmailNotifier) Notify(evalContext *alerting.EvalContext) error { func (en *EmailNotifier) Notify(evalContext *alerting.EvalContext) error {
en.log.Info("Sending alert notification to", "addresses", en.Addresses) en.log.Info("Sending alert notification to", "addresses", en.Addresses, "singleEmail", en.SingleEmail)
ruleURL, err := evalContext.GetRuleURL() ruleURL, err := evalContext.GetRuleURL()
if err != nil { if err != nil {
...@@ -89,6 +104,7 @@ func (en *EmailNotifier) Notify(evalContext *alerting.EvalContext) error { ...@@ -89,6 +104,7 @@ func (en *EmailNotifier) Notify(evalContext *alerting.EvalContext) error {
"EvalMatches": evalContext.EvalMatches, "EvalMatches": evalContext.EvalMatches,
}, },
To: en.Addresses, To: en.Addresses,
SingleEmail: en.SingleEmail,
Template: "alert_notification.html", Template: "alert_notification.html",
EmbededFiles: []string{}, EmbededFiles: []string{},
}, },
......
...@@ -14,6 +14,7 @@ type AttachedFile struct { ...@@ -14,6 +14,7 @@ type AttachedFile struct {
// Message is representation of the email message // Message is representation of the email message
type Message struct { type Message struct {
To []string To []string
SingleEmail bool
From string From string
Subject string Subject string
Body string Body string
......
...@@ -12,6 +12,7 @@ import ( ...@@ -12,6 +12,7 @@ import (
"io" "io"
"net" "net"
"strconv" "strconv"
"strings"
gomail "gopkg.in/mail.v2" gomail "gopkg.in/mail.v2"
...@@ -21,16 +22,31 @@ import ( ...@@ -21,16 +22,31 @@ import (
) )
func (ns *NotificationService) send(msg *Message) (int, error) { func (ns *NotificationService) send(msg *Message) (int, error) {
messages := []*Message{}
if msg.SingleEmail {
messages = append(messages, msg)
} else {
for _, address := range msg.To {
copy := *msg
copy.To = []string{address}
messages = append(messages, &copy)
}
}
return ns.dialAndSend(messages...)
}
func (ns *NotificationService) dialAndSend(messages ...*Message) (num int, err error) {
dialer, err := ns.createDialer() dialer, err := ns.createDialer()
if err != nil { if err != nil {
return 0, err return
} }
var num int for _, msg := range messages {
for _, address := range msg.To {
m := gomail.NewMessage() m := gomail.NewMessage()
m.SetHeader("From", msg.From) m.SetHeader("From", msg.From)
m.SetHeader("To", address) m.SetHeader("To", msg.To...)
m.SetHeader("Subject", msg.Subject) m.SetHeader("Subject", msg.Subject)
ns.setFiles(m, msg) ns.setFiles(m, msg)
...@@ -41,16 +57,15 @@ func (ns *NotificationService) send(msg *Message) (int, error) { ...@@ -41,16 +57,15 @@ func (ns *NotificationService) send(msg *Message) (int, error) {
m.SetBody("text/html", msg.Body) m.SetBody("text/html", msg.Body)
e := dialer.DialAndSend(m) if e := dialer.DialAndSend(m); e != nil {
if e != nil { err = errutil.Wrapf(e, "Failed to send notification to email addresses: %s", strings.Join(msg.To, ";"))
err = errutil.Wrapf(e, "Failed to send notification to email address: %s", address)
continue continue
} }
num++ num++
} }
return num, err return
} }
// setFiles attaches files in various forms // setFiles attaches files in various forms
...@@ -150,6 +165,7 @@ func (ns *NotificationService) buildEmailMessage(cmd *models.SendEmailCommand) ( ...@@ -150,6 +165,7 @@ func (ns *NotificationService) buildEmailMessage(cmd *models.SendEmailCommand) (
return &Message{ return &Message{
To: cmd.To, To: cmd.To,
SingleEmail: cmd.SingleEmail,
From: fmt.Sprintf("%s <%s>", ns.Cfg.Smtp.FromName, ns.Cfg.Smtp.FromAddress), From: fmt.Sprintf("%s <%s>", ns.Cfg.Smtp.FromName, ns.Cfg.Smtp.FromAddress),
Subject: subject, Subject: subject,
Body: buffer.String(), Body: buffer.String(),
......
...@@ -123,6 +123,7 @@ func (ns *NotificationService) sendEmailCommandHandlerSync(ctx context.Context, ...@@ -123,6 +123,7 @@ func (ns *NotificationService) sendEmailCommandHandlerSync(ctx context.Context,
Info: cmd.Info, Info: cmd.Info,
Template: cmd.Template, Template: cmd.Template,
To: cmd.To, To: cmd.To,
SingleEmail: cmd.SingleEmail,
EmbededFiles: cmd.EmbededFiles, EmbededFiles: cmd.EmbededFiles,
Subject: cmd.Subject, Subject: cmd.Subject,
}) })
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment