mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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>
This commit is contained in:
parent
8a02fa7691
commit
0f0772b629
@ -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,13 +19,25 @@ 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
|
||||||
</div>
|
class="gf-form"
|
||||||
<div class="gf-form">
|
label="Single email"
|
||||||
<span>You can enter multiple email addresses using a ";" separator</span>
|
label-class="width-8"
|
||||||
</div>
|
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.
|
// alert notifications over email.
|
||||||
type EmailNotifier struct {
|
type EmailNotifier struct {
|
||||||
NotifierBase
|
NotifierBase
|
||||||
Addresses []string
|
Addresses []string
|
||||||
log log.Logger
|
SingleEmail bool
|
||||||
|
log log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEmailNotifier is the constructor function
|
// NewEmailNotifier is the constructor function
|
||||||
// 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
|
|||||||
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 {
|
|||||||
"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 {
|
|||||||
// 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 (
|
|||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
gomail "gopkg.in/mail.v2"
|
gomail "gopkg.in/mail.v2"
|
||||||
|
|
||||||
@ -21,16 +22,31 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (ns *NotificationService) send(msg *Message) (int, error) {
|
func (ns *NotificationService) send(msg *Message) (int, error) {
|
||||||
dialer, err := ns.createDialer()
|
messages := []*Message{}
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
if msg.SingleEmail {
|
||||||
|
messages = append(messages, msg)
|
||||||
|
} else {
|
||||||
|
for _, address := range msg.To {
|
||||||
|
copy := *msg
|
||||||
|
copy.To = []string{address}
|
||||||
|
messages = append(messages, ©)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var num int
|
return ns.dialAndSend(messages...)
|
||||||
for _, address := range msg.To {
|
}
|
||||||
|
|
||||||
|
func (ns *NotificationService) dialAndSend(messages ...*Message) (num int, err error) {
|
||||||
|
dialer, err := ns.createDialer()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, msg := range messages {
|
||||||
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) {
|
|||||||
|
|
||||||
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) (
|
|||||||
|
|
||||||
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,
|
|||||||
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,
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user