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 {
// SendEmailCommand is command for sending emails
type SendEmailCommand struct {
To []string
SingleEmail bool
Template string
Subject string
Data map[string]interface{}
......
......@@ -19,13 +19,25 @@ func init() {
Description: "Sends notifications using Grafana server configured SMTP settings",
Factory: NewEmailNotifier,
OptionsTemplate: `
<h3 class="page-heading">Email addresses</h3>
<div class="gf-form">
<textarea rows="7" class="gf-form-input width-27" required ng-model="ctrl.model.settings.addresses"></textarea>
</div>
<div class="gf-form">
<span>You can enter multiple email addresses using a ";" separator</span>
</div>
<h3 class="page-heading">Email settings</h3>
<div class="gf-form">
<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 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>
</div>
`,
})
}
......@@ -34,14 +46,16 @@ func init() {
// alert notifications over email.
type EmailNotifier struct {
NotifierBase
Addresses []string
log log.Logger
Addresses []string
SingleEmail bool
log log.Logger
}
// NewEmailNotifier is the constructor function
// for the EmailNotifier.
func NewEmailNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
addressesString := model.Settings.Get("addresses").MustString()
singleEmail := model.Settings.Get("singleEmail").MustBool(false)
if addressesString == "" {
return nil, alerting.ValidationError{Reason: "Could not find addresses in settings"}
......@@ -53,13 +67,14 @@ func NewEmailNotifier(model *models.AlertNotification) (alerting.Notifier, error
return &EmailNotifier{
NotifierBase: NewNotifierBase(model),
Addresses: addresses,
SingleEmail: singleEmail,
log: log.New("alerting.notifier.email"),
}, nil
}
// Notify sends the alert notification.
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()
if err != nil {
......@@ -89,6 +104,7 @@ func (en *EmailNotifier) Notify(evalContext *alerting.EvalContext) error {
"EvalMatches": evalContext.EvalMatches,
},
To: en.Addresses,
SingleEmail: en.SingleEmail,
Template: "alert_notification.html",
EmbededFiles: []string{},
},
......
......@@ -14,6 +14,7 @@ type AttachedFile struct {
// Message is representation of the email message
type Message struct {
To []string
SingleEmail bool
From string
Subject string
Body string
......
......@@ -12,6 +12,7 @@ import (
"io"
"net"
"strconv"
"strings"
gomail "gopkg.in/mail.v2"
......@@ -21,16 +22,31 @@ import (
)
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()
if err != nil {
return 0, err
return
}
var num int
for _, address := range msg.To {
for _, msg := range messages {
m := gomail.NewMessage()
m.SetHeader("From", msg.From)
m.SetHeader("To", address)
m.SetHeader("To", msg.To...)
m.SetHeader("Subject", msg.Subject)
ns.setFiles(m, msg)
......@@ -41,16 +57,15 @@ func (ns *NotificationService) send(msg *Message) (int, error) {
m.SetBody("text/html", msg.Body)
e := dialer.DialAndSend(m)
if e != nil {
err = errutil.Wrapf(e, "Failed to send notification to email address: %s", address)
if e := dialer.DialAndSend(m); e != nil {
err = errutil.Wrapf(e, "Failed to send notification to email addresses: %s", strings.Join(msg.To, ";"))
continue
}
num++
}
return num, err
return
}
// setFiles attaches files in various forms
......@@ -150,6 +165,7 @@ func (ns *NotificationService) buildEmailMessage(cmd *models.SendEmailCommand) (
return &Message{
To: cmd.To,
SingleEmail: cmd.SingleEmail,
From: fmt.Sprintf("%s <%s>", ns.Cfg.Smtp.FromName, ns.Cfg.Smtp.FromAddress),
Subject: subject,
Body: buffer.String(),
......
......@@ -123,6 +123,7 @@ func (ns *NotificationService) sendEmailCommandHandlerSync(ctx context.Context,
Info: cmd.Info,
Template: cmd.Template,
To: cmd.To,
SingleEmail: cmd.SingleEmail,
EmbededFiles: cmd.EmbededFiles,
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