mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Email: Allow configuration of content types for email notifications (#34530)
* Alerting: Allow configuration of content types for email notifications * Fix lint error * Improves email templates * Improve configuration documentation Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> * Improve code comments Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> * Improve configuration documentation Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> * Improve email template * Remove unnecessary predeclaration Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Adds handling for unrecognized content type Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Move utility function outside of util package * Fixes syntax * Remove unused package * Fix lint error * improve email templates * Fix test * Alerting: Allow configuration of content types for email notifications * Fix lint error * Improves email templates * Improve configuration documentation Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> * Improve code comments Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> * Improve configuration documentation Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> * Improve email template * Remove unnecessary predeclaration Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Adds handling for unrecognized content type Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Move utility function outside of util package * Fixes syntax * Remove unused package * Fix lint error * improve email templates * Fix test * Fix comment style Co-authored-by: Ganesh Vernekar <15064823+codesome@users.noreply.github.com> * Fix template formatting * Add test and improve error handling * Fix test * Fix formatting * Fix formatting * Improve documentation and regenerates txt template * Update docs/sources/administration/configuration.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> Co-authored-by: Djairho Geuens <djairho.geuens@ae.be> Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> Co-authored-by: Ganesh Vernekar <15064823+codesome@users.noreply.github.com> Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
This commit is contained in:
parent
cec12676e7
commit
4cadbba686
@ -595,7 +595,8 @@ startTLS_policy =
|
||||
|
||||
[emails]
|
||||
welcome_email_on_sign_up = false
|
||||
templates_pattern = emails/*.html
|
||||
templates_pattern = emails/*.html, emails/*.txt
|
||||
content_types = text/html
|
||||
|
||||
#################################### Logging ##########################
|
||||
[log]
|
||||
|
@ -578,7 +578,8 @@
|
||||
|
||||
[emails]
|
||||
;welcome_email_on_sign_up = false
|
||||
;templates_pattern = emails/*.html
|
||||
;templates_pattern = emails/*.html, emails/*.txt
|
||||
;content_types = text/html
|
||||
|
||||
#################################### Logging ##########################
|
||||
[log]
|
||||
|
@ -909,7 +909,11 @@ Default is `false`.
|
||||
|
||||
### templates_pattern
|
||||
|
||||
Default is `emails/*.html`.
|
||||
Enter a comma separated list of template patterns. Default is `emails/*.html, emails/*.txt`.
|
||||
|
||||
### content_types
|
||||
|
||||
Enter a comma-separated list of content types that should be included in the emails that are sent. List the content types according descending preference, e.g. `text/html, text/plain` for HTML as the most preferred. The order of the parts is significant as the mail clients will use the content type that is supported and most preferred by the sender. Supported content types are `text/html` and `text/plain`. Default is `text/html`.
|
||||
|
||||
<hr>
|
||||
|
||||
|
@ -97,8 +97,9 @@ Content-Type: application/json
|
||||
"user":"root"
|
||||
},
|
||||
"emails":{
|
||||
"templates_pattern":"emails/*.html",
|
||||
"welcome_email_on_sign_up":"false"
|
||||
"templates_pattern":"emails/*.html, emails/*.txt",
|
||||
"welcome_email_on_sign_up":"false",
|
||||
"content_types":"text/html"
|
||||
},
|
||||
"log":{
|
||||
"buffer_len":"10000",
|
||||
|
@ -6,10 +6,9 @@
|
||||
## Tasks
|
||||
|
||||
- npm run build (default task will build new inlines email templates)
|
||||
- npm start (will build on source html or css change)
|
||||
- npm start (builds on source HTML, text, or CSS change)
|
||||
|
||||
## Result
|
||||
|
||||
Assembled email templates will be in `dist/` and final
|
||||
inlined templates will be in `../public/emails/`
|
||||
|
||||
|
@ -2,15 +2,25 @@ module.exports = function () {
|
||||
'use strict';
|
||||
return {
|
||||
options: {
|
||||
layout: 'templates/layouts/default.html',
|
||||
partials: ['templates/partials/*.hbs'],
|
||||
helpers: ['templates/helpers/**/*.js'],
|
||||
data: [],
|
||||
flatten: true,
|
||||
},
|
||||
pages: {
|
||||
html: {
|
||||
options: {
|
||||
layout: 'templates/layouts/default.html',
|
||||
},
|
||||
src: ['templates/*.html'],
|
||||
dest: 'dist/',
|
||||
},
|
||||
txt: {
|
||||
options: {
|
||||
layout: 'templates/layouts/default.txt',
|
||||
ext: '.txt',
|
||||
},
|
||||
src: ['templates/*.txt'],
|
||||
dest: 'dist/',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
main: {
|
||||
html: {
|
||||
options: {
|
||||
verbose: true,
|
||||
removeComments: true,
|
||||
@ -13,4 +13,19 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
},
|
||||
txt: {
|
||||
options: {
|
||||
verbose: true,
|
||||
mode: 'txt',
|
||||
lineLength: 90,
|
||||
},
|
||||
files: [
|
||||
{
|
||||
expand: true, // Enable dynamic expansion.
|
||||
cwd: 'dist', // Src matches are relative to this path.
|
||||
src: ['*.txt'], // Actual patterns to match.
|
||||
dest: '../public/emails/', // Destination path prefix.
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
module.exports = {
|
||||
dist: {
|
||||
overwrite: true,
|
||||
src: ['dist/*.html'],
|
||||
src: ['dist/*.html', 'dist/*.txt'],
|
||||
replacements: [
|
||||
{
|
||||
from: '[[',
|
||||
|
@ -4,6 +4,7 @@ module.exports = {
|
||||
//what are the files that we want to watch
|
||||
'assets/css/*.css',
|
||||
'templates/**/*.html',
|
||||
'templates/**/*.txt',
|
||||
'grunt/*.js',
|
||||
],
|
||||
tasks: ['default'],
|
||||
|
26
emails/templates/alert_notification.txt
Normal file
26
emails/templates/alert_notification.txt
Normal file
@ -0,0 +1,26 @@
|
||||
[[Subject .Subject "[[.Title]]"]]
|
||||
|
||||
[[.Title]]
|
||||
----------------
|
||||
|
||||
[[.Message]]
|
||||
|
||||
[[if ne .Error "" ]]
|
||||
Error message:
|
||||
[[.Error]]
|
||||
[[end]]
|
||||
|
||||
[[if ne .State "ok" ]]
|
||||
[[range .EvalMatches]]
|
||||
Metric name:
|
||||
[[.Metric]]
|
||||
Value:
|
||||
[[.Value]]
|
||||
[[end]]
|
||||
[[end]]
|
||||
|
||||
View your Alert rule:
|
||||
[[.RuleUrl]]"
|
||||
|
||||
Go to the Alerts page:
|
||||
[[.AlertPageUrl]]
|
9
emails/templates/invited_to_org.txt
Normal file
9
emails/templates/invited_to_org.txt
Normal file
@ -0,0 +1,9 @@
|
||||
[[Subject .Subject "[[.InvitedBy]] has added you to the [[.OrgName]] organization"]]
|
||||
|
||||
You have been added to [[.OrgName]]
|
||||
|
||||
[[.InvitedBy]] has added you to the [[.OrgName]] organization in Grafana.
|
||||
Once logged in, [[.OrgName]] will be available in the left side menu, in the dropdown below your username.
|
||||
|
||||
Log in now:
|
||||
[[.AppUrl]]
|
3
emails/templates/layouts/default.txt
Normal file
3
emails/templates/layouts/default.txt
Normal file
@ -0,0 +1,3 @@
|
||||
{{> body }}
|
||||
|
||||
Sent by Grafana v[[.BuildVersion]] (c) 2021 Grafana Labs
|
@ -39,7 +39,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="center">
|
||||
<p>You can also copy/paste this link into your browser directly: <a href="[[.LinkUrl]]">[[.LinkUrl]]</a></p>
|
||||
<p>You can also copy and paste this link into your browser directly: <a href="[[.LinkUrl]]">[[.LinkUrl]]</a></p>
|
||||
</td>
|
||||
<td class="expander"></td>
|
||||
</tr>
|
||||
|
7
emails/templates/new_user_invite.txt
Normal file
7
emails/templates/new_user_invite.txt
Normal file
@ -0,0 +1,7 @@
|
||||
[[Subject .Subject "[[.InvitedBy]] has invited you to join Grafana"]]
|
||||
|
||||
You're invited to join [[.OrgName]]
|
||||
|
||||
You've been invited to join the [[.OrgName]] organization by [[.InvitedBy]]. To accept your invitation and join the team, copy and paste the link below into your browser directly:
|
||||
|
||||
[[.LinkUrl]]
|
38
emails/templates/ng_alert_notification.txt
Normal file
38
emails/templates/ng_alert_notification.txt
Normal file
@ -0,0 +1,38 @@
|
||||
[[Subject .Subject "[[.Title]]"]]
|
||||
|
||||
[[.Title]]
|
||||
----------------
|
||||
|
||||
[[ .Alerts | len ]] alert[[ if gt (len .Alerts) 1 ]]s[[ end ]] for
|
||||
[[ range .GroupLabels.SortedPairs ]]
|
||||
[[ .Name ]] = [[ .Value ]]
|
||||
[[ end ]]
|
||||
[[ if gt (len .Alerts.Firing) 0 ]]([[ .Alerts.Firing | len ]]) Firing[[ end ]]
|
||||
[[ range .Alerts.Firing ]]
|
||||
Labels:
|
||||
[[ range .Labels.SortedPairs ]]
|
||||
[[ .Name ]] = [[ .Value ]]
|
||||
[[ end ]]
|
||||
[[ if gt (len .Annotations) 0 ]]
|
||||
Annotations:
|
||||
[[ end ]]
|
||||
[[ range .Annotations.SortedPairs ]]
|
||||
[[ .Name ]] = [[ .Value ]]
|
||||
[[ end ]]
|
||||
[[ end ]][[ if gt (len .Alerts.Resolved) 0 ]]([[ .Alerts.Resolved | len ]]) Resolved[[ end ]]
|
||||
[[ range .Alerts.Resolved ]]
|
||||
Labels:
|
||||
[[ range .Labels.SortedPairs ]]
|
||||
[[ .Name ]] = [[ .Value ]]
|
||||
[[ end ]]
|
||||
[[ if gt (len .Annotations) 0 ]]
|
||||
Annotations:
|
||||
[[ end ]]
|
||||
[[ range .Annotations.SortedPairs ]]
|
||||
[[ .Name ]] = [[ .Value ]]
|
||||
[[ end ]]
|
||||
[[ end ]]View your Alert rule:
|
||||
[[.RuleUrl]]
|
||||
|
||||
Go to the Alerts page:
|
||||
[[.AlertPageUrl]]
|
6
emails/templates/reset_password.txt
Normal file
6
emails/templates/reset_password.txt
Normal file
@ -0,0 +1,6 @@
|
||||
[[Subject .Subject "Reset your Grafana password - [[.Name]]"]]
|
||||
|
||||
Hi [[.Name]],
|
||||
|
||||
Copy and paste the following link directly in your browser to reset your password within [[.EmailCodeValidHours]] hours.
|
||||
[[.AppUrl]]user/password/reset?code=[[.Code]]
|
9
emails/templates/signup_started.txt
Normal file
9
emails/templates/signup_started.txt
Normal file
@ -0,0 +1,9 @@
|
||||
[[Subject .Subject "Welcome to Grafana, please complete your sign up!"]]
|
||||
|
||||
Complete the signup
|
||||
|
||||
Copy and paste the email verification code:
|
||||
[[.Code]]
|
||||
in the sign up form or use the link below.
|
||||
|
||||
[[.SignUpUrl]]
|
@ -29,7 +29,7 @@
|
||||
<tr>
|
||||
<td class="center">
|
||||
<p>
|
||||
If you are new to Grafana please read the <a href="https://grafana.com/docs/grafana/latest/getting-started/getting-started/">Getting Started</a> guide.
|
||||
If you are new to Grafana, refer to the <a href="https://grafana.com/docs/grafana/latest/getting-started/getting-started/">Getting started with Grafana</a> guide.
|
||||
</p>
|
||||
</td>
|
||||
<td class="expander"></td>
|
||||
|
11
emails/templates/welcome_on_signup.txt
Normal file
11
emails/templates/welcome_on_signup.txt
Normal file
@ -0,0 +1,11 @@
|
||||
[[Subject .Subject "Welcome to Grafana"]]
|
||||
|
||||
Hi [[.Name]],
|
||||
|
||||
Welcome! Ready to start building some beautiful metric and analytic dashboards?
|
||||
|
||||
If you are new to Grafana, refer to the Getting started with Grafana guide on https://grafana.com/docs/grafana/latest/getting-started/getting-started/.
|
||||
|
||||
Thank you for joining our community.
|
||||
|
||||
The Grafana team
|
@ -69,7 +69,7 @@ func AddOrgInvite(c *models.ReqContext, inviteDto dtos.AddInviteForm) response.R
|
||||
if inviteDto.SendEmail && util.IsEmail(inviteDto.LoginOrEmail) {
|
||||
emailCmd := models.SendEmailCommand{
|
||||
To: []string{inviteDto.LoginOrEmail},
|
||||
Template: "new_user_invite.html",
|
||||
Template: "new_user_invite",
|
||||
Data: map[string]interface{}{
|
||||
"Name": util.StringsFallback2(cmd.Name, cmd.Email),
|
||||
"OrgName": c.OrgName,
|
||||
@ -111,7 +111,7 @@ func inviteExistingUserToOrg(c *models.ReqContext, user *models.User, inviteDto
|
||||
if inviteDto.SendEmail && util.IsEmail(user.Email) {
|
||||
emailCmd := models.SendEmailCommand{
|
||||
To: []string{user.Email},
|
||||
Template: "invited_to_org.html",
|
||||
Template: "invited_to_org",
|
||||
Data: map[string]interface{}{
|
||||
"Name": user.NameOrFallback(),
|
||||
"OrgName": c.OrgName,
|
||||
|
@ -11,7 +11,7 @@ type SendEmailAttachFile struct {
|
||||
Content []byte
|
||||
}
|
||||
|
||||
// SendEmailCommand is command for sending emails
|
||||
// SendEmailCommand is the command for sending emails
|
||||
type SendEmailCommand struct {
|
||||
To []string
|
||||
SingleEmail bool
|
||||
@ -24,7 +24,7 @@ type SendEmailCommand struct {
|
||||
AttachedFiles []*SendEmailAttachFile
|
||||
}
|
||||
|
||||
// SendEmailCommandSync is command for sending emails in sync
|
||||
// SendEmailCommandSync is the command for sending emails synchronously
|
||||
type SendEmailCommandSync struct {
|
||||
SendEmailCommand
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ func (en *EmailNotifier) Notify(evalContext *alerting.EvalContext) error {
|
||||
},
|
||||
To: en.Addresses,
|
||||
SingleEmail: en.SingleEmail,
|
||||
Template: "alert_notification.html",
|
||||
Template: "alert_notification",
|
||||
EmbeddedFiles: []string{},
|
||||
},
|
||||
}
|
||||
|
@ -96,7 +96,7 @@ func (en *EmailNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool,
|
||||
},
|
||||
To: en.Addresses,
|
||||
SingleEmail: en.SingleEmail,
|
||||
Template: "ng_alert_notification.html",
|
||||
Template: "ng_alert_notification",
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -80,7 +80,7 @@ func TestEmailNotifier(t *testing.T) {
|
||||
"subject": "[FIRING:1] (AlwaysFiring warning)",
|
||||
"to": []string{"someops@example.com", "somedev@example.com"},
|
||||
"single_email": false,
|
||||
"template": "ng_alert_notification.html",
|
||||
"template": "ng_alert_notification",
|
||||
"data": map[string]interface{}{
|
||||
"Title": "[FIRING:1] (AlwaysFiring warning)",
|
||||
"Message": "[FIRING:1] (AlwaysFiring warning)",
|
||||
|
@ -17,7 +17,7 @@ type Message struct {
|
||||
SingleEmail bool
|
||||
From string
|
||||
Subject string
|
||||
Body string
|
||||
Body map[string]string
|
||||
Info string
|
||||
ReplyTo []string
|
||||
EmbeddedFiles []string
|
||||
|
@ -67,18 +67,7 @@ func (ns *NotificationService) dialAndSend(messages ...*Message) (int, error) {
|
||||
}
|
||||
|
||||
for _, msg := range messages {
|
||||
m := gomail.NewMessage()
|
||||
m.SetHeader("From", msg.From)
|
||||
m.SetHeader("To", msg.To...)
|
||||
m.SetHeader("Subject", msg.Subject)
|
||||
|
||||
ns.setFiles(m, msg)
|
||||
|
||||
for _, replyTo := range msg.ReplyTo {
|
||||
m.SetAddressHeader("Reply-To", replyTo, "")
|
||||
}
|
||||
|
||||
m.SetBody("text/html", msg.Body)
|
||||
m := ns.buildEmail(msg)
|
||||
|
||||
innerError := dialer.DialAndSend(m)
|
||||
emailsSentTotal.Inc()
|
||||
@ -100,6 +89,28 @@ func (ns *NotificationService) dialAndSend(messages ...*Message) (int, error) {
|
||||
return sentEmailsCount, err
|
||||
}
|
||||
|
||||
func (ns *NotificationService) buildEmail(msg *Message) *gomail.Message {
|
||||
m := gomail.NewMessage()
|
||||
m.SetHeader("From", msg.From)
|
||||
m.SetHeader("To", msg.To...)
|
||||
m.SetHeader("Subject", msg.Subject)
|
||||
ns.setFiles(m, msg)
|
||||
for _, replyTo := range msg.ReplyTo {
|
||||
m.SetAddressHeader("Reply-To", replyTo, "")
|
||||
}
|
||||
// loop over content types from settings in reverse order as they are ordered in according to descending
|
||||
// preference while the alternatives should be ordered according to ascending preference
|
||||
for i := len(ns.Cfg.Smtp.ContentTypes) - 1; i >= 0; i-- {
|
||||
if i == len(ns.Cfg.Smtp.ContentTypes)-1 {
|
||||
m.SetBody(ns.Cfg.Smtp.ContentTypes[i], msg.Body[ns.Cfg.Smtp.ContentTypes[i]])
|
||||
} else {
|
||||
m.AddAlternative(ns.Cfg.Smtp.ContentTypes[i], msg.Body[ns.Cfg.Smtp.ContentTypes[i]])
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// setFiles attaches files in various forms
|
||||
func (ns *NotificationService) setFiles(
|
||||
m *gomail.Message,
|
||||
@ -169,19 +180,27 @@ func (ns *NotificationService) buildEmailMessage(cmd *models.SendEmailCommand) (
|
||||
return nil, models.ErrSmtpNotEnabled
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
var err error
|
||||
|
||||
data := cmd.Data
|
||||
if data == nil {
|
||||
data = make(map[string]interface{}, 10)
|
||||
}
|
||||
|
||||
setDefaultTemplateData(data, nil)
|
||||
err = mailTemplates.ExecuteTemplate(&buffer, cmd.Template, data)
|
||||
|
||||
body := make(map[string]string)
|
||||
for _, contentType := range ns.Cfg.Smtp.ContentTypes {
|
||||
fileExtension, err := getFileExtensionByContentType(contentType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var buffer bytes.Buffer
|
||||
err = mailTemplates.ExecuteTemplate(&buffer, cmd.Template+fileExtension, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body[contentType] = buffer.String()
|
||||
}
|
||||
|
||||
subject := cmd.Subject
|
||||
if cmd.Subject == "" {
|
||||
@ -213,7 +232,7 @@ func (ns *NotificationService) buildEmailMessage(cmd *models.SendEmailCommand) (
|
||||
SingleEmail: cmd.SingleEmail,
|
||||
From: addr.String(),
|
||||
Subject: subject,
|
||||
Body: buffer.String(),
|
||||
Body: body,
|
||||
EmbeddedFiles: cmd.EmbeddedFiles,
|
||||
AttachedFiles: buildAttachedFiles(cmd.AttachedFiles),
|
||||
ReplyTo: cmd.ReplyTo,
|
||||
@ -235,3 +254,14 @@ func buildAttachedFiles(
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func getFileExtensionByContentType(contentType string) (string, error) {
|
||||
switch contentType {
|
||||
case "text/html":
|
||||
return ".html", nil
|
||||
case "text/plain":
|
||||
return ".txt", nil
|
||||
default:
|
||||
return "", fmt.Errorf("unrecognized content type %q", contentType)
|
||||
}
|
||||
}
|
||||
|
41
pkg/services/notifications/mailer_test.go
Normal file
41
pkg/services/notifications/mailer_test.go
Normal file
@ -0,0 +1,41 @@
|
||||
package notifications
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBuildMail(t *testing.T) {
|
||||
ns := &NotificationService{
|
||||
Cfg: setting.NewCfg(),
|
||||
}
|
||||
ns.Cfg.Smtp.ContentTypes = []string{"text/html", "text/plain"}
|
||||
|
||||
message := &Message{
|
||||
To: []string{"to@address.com"},
|
||||
From: "from@address.com",
|
||||
Subject: "Some subject",
|
||||
Body: map[string]string{
|
||||
"text/html": "Some HTML body",
|
||||
"text/plain": "Some plain text body",
|
||||
},
|
||||
ReplyTo: []string{"from@address.com"},
|
||||
}
|
||||
|
||||
t.Run("When building email", func(t *testing.T) {
|
||||
email := ns.buildEmail(message)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
_, err := email.WriteTo(buf)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Contains(t, buf.String(), "Some HTML body")
|
||||
assert.Contains(t, buf.String(), "Some plain text body")
|
||||
assert.Less(t, strings.Index(buf.String(), "Some plain text body"), strings.Index(buf.String(), "Some HTML body"))
|
||||
})
|
||||
}
|
@ -19,9 +19,9 @@ import (
|
||||
)
|
||||
|
||||
var mailTemplates *template.Template
|
||||
var tmplResetPassword = "reset_password.html"
|
||||
var tmplSignUpStarted = "signup_started.html"
|
||||
var tmplWelcomeOnSignUp = "welcome_on_signup.html"
|
||||
var tmplResetPassword = "reset_password"
|
||||
var tmplSignUpStarted = "signup_started"
|
||||
var tmplWelcomeOnSignUp = "welcome_on_signup"
|
||||
|
||||
func init() {
|
||||
registry.RegisterService(&NotificationService{})
|
||||
@ -56,11 +56,13 @@ func (ns *NotificationService) Init() error {
|
||||
"Subject": subjectTemplateFunc,
|
||||
})
|
||||
|
||||
templatePattern := filepath.Join(ns.Cfg.StaticRootPath, ns.Cfg.Smtp.TemplatesPattern)
|
||||
for _, pattern := range ns.Cfg.Smtp.TemplatesPatterns {
|
||||
templatePattern := filepath.Join(ns.Cfg.StaticRootPath, pattern)
|
||||
_, err := mailTemplates.ParseGlob(templatePattern)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !util.IsEmail(ns.Cfg.Smtp.FromAddress) {
|
||||
return errors.New("invalid email address for SMTP from_address config")
|
||||
|
@ -16,9 +16,10 @@ func TestNotificationService(t *testing.T) {
|
||||
}
|
||||
ns.Cfg.StaticRootPath = "../../../public/"
|
||||
ns.Cfg.Smtp.Enabled = true
|
||||
ns.Cfg.Smtp.TemplatesPattern = "emails/*.html"
|
||||
ns.Cfg.Smtp.TemplatesPatterns = []string{"emails/*.html", "emails/*.txt"}
|
||||
ns.Cfg.Smtp.FromAddress = "from@address.com"
|
||||
ns.Cfg.Smtp.FromName = "Grafana Admin"
|
||||
ns.Cfg.Smtp.ContentTypes = []string{"text/html", "text/plain"}
|
||||
ns.Bus = bus.New()
|
||||
|
||||
err := ns.Init()
|
||||
@ -29,8 +30,10 @@ func TestNotificationService(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
sentMsg := <-ns.mailQueue
|
||||
assert.Contains(t, sentMsg.Body, "body")
|
||||
assert.Contains(t, sentMsg.Body["text/html"], "body")
|
||||
assert.NotContains(t, sentMsg.Body["text/plain"], "body")
|
||||
assert.Equal(t, "Reset your Grafana password - asd@asd.com", sentMsg.Subject)
|
||||
assert.NotContains(t, sentMsg.Body, "Subject")
|
||||
assert.NotContains(t, sentMsg.Body["text/html"], "Subject")
|
||||
assert.NotContains(t, sentMsg.Body["text/plain"], "Subject")
|
||||
})
|
||||
}
|
||||
|
@ -19,9 +19,10 @@ func TestEmailIntegrationTest(t *testing.T) {
|
||||
ns.Bus = bus.New()
|
||||
ns.Cfg = setting.NewCfg()
|
||||
ns.Cfg.Smtp.Enabled = true
|
||||
ns.Cfg.Smtp.TemplatesPattern = "emails/*.html"
|
||||
ns.Cfg.Smtp.TemplatesPatterns = []string{"emails/*.html", "emails/*.txt"}
|
||||
ns.Cfg.Smtp.FromAddress = "from@address.com"
|
||||
ns.Cfg.Smtp.FromName = "Grafana Admin"
|
||||
ns.Cfg.Smtp.ContentTypes = []string{"text/html", "text/plain"}
|
||||
|
||||
err := ns.Init()
|
||||
So(err, ShouldBeNil)
|
||||
@ -52,7 +53,7 @@ func TestEmailIntegrationTest(t *testing.T) {
|
||||
},
|
||||
},
|
||||
To: []string{"asdf@asdf.com"},
|
||||
Template: "alert_notification.html",
|
||||
Template: "alert_notification",
|
||||
}
|
||||
|
||||
err := ns.sendEmailCommandHandler(cmd)
|
||||
@ -61,7 +62,9 @@ func TestEmailIntegrationTest(t *testing.T) {
|
||||
sentMsg := <-ns.mailQueue
|
||||
So(sentMsg.From, ShouldEqual, "Grafana Admin <from@address.com>")
|
||||
So(sentMsg.To[0], ShouldEqual, "asdf@asdf.com")
|
||||
err = ioutil.WriteFile("../../../tmp/test_email.html", []byte(sentMsg.Body), 0777)
|
||||
err = ioutil.WriteFile("../../../tmp/test_email.html", []byte(sentMsg.Body["text/html"]), 0777)
|
||||
So(err, ShouldBeNil)
|
||||
err = ioutil.WriteFile("../../../tmp/test_email.txt", []byte(sentMsg.Body["text/plain"]), 0777)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
})
|
||||
|
@ -1,5 +1,7 @@
|
||||
package setting
|
||||
|
||||
import "github.com/grafana/grafana/pkg/util"
|
||||
|
||||
type SmtpSettings struct {
|
||||
Enabled bool
|
||||
Host string
|
||||
@ -14,7 +16,8 @@ type SmtpSettings struct {
|
||||
SkipVerify bool
|
||||
|
||||
SendWelcomeEmailOnSignUp bool
|
||||
TemplatesPattern string
|
||||
TemplatesPatterns []string
|
||||
ContentTypes []string
|
||||
}
|
||||
|
||||
func (cfg *Cfg) readSmtpSettings() {
|
||||
@ -33,5 +36,6 @@ func (cfg *Cfg) readSmtpSettings() {
|
||||
|
||||
emails := cfg.Raw.Section("emails")
|
||||
cfg.Smtp.SendWelcomeEmailOnSignUp = emails.Key("welcome_email_on_sign_up").MustBool(false)
|
||||
cfg.Smtp.TemplatesPattern = emails.Key("templates_pattern").MustString("emails/*.html")
|
||||
cfg.Smtp.TemplatesPatterns = util.SplitString(emails.Key("templates_pattern").MustString("emails/*.html, emails/*.txt"))
|
||||
cfg.Smtp.ContentTypes = util.SplitString(emails.Key("content_types").MustString("text/html"))
|
||||
}
|
||||
|
@ -1369,7 +1369,7 @@ var expEmailNotifications = []*models.SendEmailCommandSync{
|
||||
SendEmailCommand: models.SendEmailCommand{
|
||||
To: []string{"test@email.com"},
|
||||
SingleEmail: true,
|
||||
Template: "ng_alert_notification.html",
|
||||
Template: "ng_alert_notification",
|
||||
Subject: "[FIRING:1] EmailAlert ",
|
||||
Data: map[string]interface{}{
|
||||
"Title": "[FIRING:1] EmailAlert ",
|
||||
|
28
public/emails/alert_notification.txt
Normal file
28
public/emails/alert_notification.txt
Normal file
@ -0,0 +1,28 @@
|
||||
{{Subject .Subject "{{.Title}}"}}
|
||||
|
||||
{{.Title}}
|
||||
----------------
|
||||
|
||||
{{.Message}}
|
||||
|
||||
{{if ne .Error "" }}
|
||||
Error message:
|
||||
{{.Error}}
|
||||
{{end}}
|
||||
|
||||
{{if ne .State "ok" }}
|
||||
{{range .EvalMatches}}
|
||||
Metric name:
|
||||
{{.Metric}}
|
||||
Value:
|
||||
{{.Value}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
View your Alert rule:
|
||||
{{.RuleUrl}}"
|
||||
|
||||
Go to the Alerts page:
|
||||
{{.AlertPageUrl}}
|
||||
|
||||
Sent by Grafana v{{.BuildVersion}} (c) 2021 Grafana Labs
|
12
public/emails/invited_to_org.txt
Normal file
12
public/emails/invited_to_org.txt
Normal file
@ -0,0 +1,12 @@
|
||||
{{Subject .Subject "{{.InvitedBy}} has added you to the {{.OrgName}} organization"}}
|
||||
|
||||
You have been added to {{.OrgName}}
|
||||
|
||||
{{.InvitedBy}} has added you to the {{.OrgName}} organization in Grafana.
|
||||
Once logged in, {{.OrgName}} will be available in the left side menu, in the dropdown
|
||||
below your username.
|
||||
|
||||
Log in now:
|
||||
{{.AppUrl}}
|
||||
|
||||
Sent by Grafana v{{.BuildVersion}} (c) 2021 Grafana Labs
|
@ -245,7 +245,7 @@ text-decoration: underline;
|
||||
</tr>
|
||||
<tr style="vertical-align: top; padding: 0;" align="left">
|
||||
<td class="center" style="word-break: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; border-collapse: collapse !important; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-weight: normal; line-height: 19px; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; margin: 0; padding: 0px 0px 10px;" align="center" valign="top">
|
||||
<p style="color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-weight: normal; line-height: 19px; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; margin: 0 0 10px; padding: 0;" align="left">You can also copy/paste this link into your browser directly: <a href="{{.LinkUrl}}" style="color: #E67612; text-decoration: none;">{{.LinkUrl}}</a></p>
|
||||
<p style="color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-weight: normal; line-height: 19px; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; margin: 0 0 10px; padding: 0;" align="left">You can also copy and paste this link into your browser directly: <a href="{{.LinkUrl}}" style="color: #E67612; text-decoration: none;">{{.LinkUrl}}</a></p>
|
||||
</td>
|
||||
<td class="expander" style="word-break: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; border-collapse: collapse !important; visibility: hidden; width: 0px; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-weight: normal; line-height: 19px; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; margin: 0; padding: 0;" align="left" valign="top"></td>
|
||||
</tr>
|
||||
|
11
public/emails/new_user_invite.txt
Normal file
11
public/emails/new_user_invite.txt
Normal file
@ -0,0 +1,11 @@
|
||||
{{Subject .Subject "{{.InvitedBy}} has invited you to join Grafana"}}
|
||||
|
||||
You're invited to join {{.OrgName}}
|
||||
|
||||
You've been invited to join the {{.OrgName}} organization by {{.InvitedBy}}. To accept
|
||||
your invitation and join the team, copy and paste the link below into your browser
|
||||
directly:
|
||||
|
||||
{{.LinkUrl}}
|
||||
|
||||
Sent by Grafana v{{.BuildVersion}} (c) 2021 Grafana Labs
|
41
public/emails/ng_alert_notification.txt
Normal file
41
public/emails/ng_alert_notification.txt
Normal file
@ -0,0 +1,41 @@
|
||||
{{Subject .Subject "{{.Title}}"}}
|
||||
|
||||
{{.Title}}
|
||||
----------------
|
||||
|
||||
{{ .Alerts | len }} alert{{ if gt (len .Alerts) 1 }}s{{ end }} for
|
||||
{{ range .GroupLabels.SortedPairs }}
|
||||
{{ .Name }} = {{ .Value }}
|
||||
{{ end }}
|
||||
{{ if gt (len .Alerts.Firing) 0 }}({{ .Alerts.Firing | len }}) Firing{{ end }}
|
||||
{{ range .Alerts.Firing }}
|
||||
Labels:
|
||||
{{ range .Labels.SortedPairs }}
|
||||
{{ .Name }} = {{ .Value }}
|
||||
{{ end }}
|
||||
{{ if gt (len .Annotations) 0 }}
|
||||
Annotations:
|
||||
{{ end }}
|
||||
{{ range .Annotations.SortedPairs }}
|
||||
{{ .Name }} = {{ .Value }}
|
||||
{{ end }}
|
||||
{{ end }}{{ if gt (len .Alerts.Resolved) 0 }}({{ .Alerts.Resolved | len }}) Resolved{{ end
|
||||
}}
|
||||
{{ range .Alerts.Resolved }}
|
||||
Labels:
|
||||
{{ range .Labels.SortedPairs }}
|
||||
{{ .Name }} = {{ .Value }}
|
||||
{{ end }}
|
||||
{{ if gt (len .Annotations) 0 }}
|
||||
Annotations:
|
||||
{{ end }}
|
||||
{{ range .Annotations.SortedPairs }}
|
||||
{{ .Name }} = {{ .Value }}
|
||||
{{ end }}
|
||||
{{ end }}View your Alert rule:
|
||||
{{.RuleUrl}}
|
||||
|
||||
Go to the Alerts page:
|
||||
{{.AlertPageUrl}}
|
||||
|
||||
Sent by Grafana v{{.BuildVersion}} (c) 2021 Grafana Labs
|
9
public/emails/reset_password.txt
Normal file
9
public/emails/reset_password.txt
Normal file
@ -0,0 +1,9 @@
|
||||
{{Subject .Subject "Reset your Grafana password - {{.Name}}"}}
|
||||
|
||||
Hi {{.Name}},
|
||||
|
||||
Copy and paste the following link directly in your browser to reset your password within
|
||||
{{.EmailCodeValidHours}} hours.
|
||||
{{.AppUrl}}user/password/reset?code={{.Code}}
|
||||
|
||||
Sent by Grafana v{{.BuildVersion}} (c) 2021 Grafana Labs
|
11
public/emails/signup_started.txt
Normal file
11
public/emails/signup_started.txt
Normal file
@ -0,0 +1,11 @@
|
||||
{{Subject .Subject "Welcome to Grafana, please complete your sign up!"}}
|
||||
|
||||
Complete the signup
|
||||
|
||||
Copy and paste the email verification code:
|
||||
{{.Code}}
|
||||
in the sign up form or use the link below.
|
||||
|
||||
{{.SignUpUrl}}
|
||||
|
||||
Sent by Grafana v{{.BuildVersion}} (c) 2021 Grafana Labs
|
@ -235,7 +235,7 @@ text-decoration: underline;
|
||||
<tr style="vertical-align: top; padding: 0;" align="left">
|
||||
<td class="center" style="word-break: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; border-collapse: collapse !important; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-weight: normal; line-height: 19px; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; margin: 0; padding: 0px 0px 10px;" align="center" valign="top">
|
||||
<p style="color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-weight: normal; line-height: 19px; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; margin: 0 0 10px; padding: 0;" align="left">
|
||||
If you are new to Grafana please read the <a href="https://grafana.com/docs/grafana/latest/getting-started/getting-started/" style="color: #E67612; text-decoration: none;">Getting Started</a> guide.
|
||||
If you are new to Grafana, refer to the <a href="https://grafana.com/docs/grafana/latest/getting-started/getting-started/" style="color: #E67612; text-decoration: none;">Getting started with Grafana</a> guide.
|
||||
</p>
|
||||
</td>
|
||||
<td class="expander" style="word-break: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; border-collapse: collapse !important; visibility: hidden; width: 0px; color: #222222; font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; font-weight: normal; line-height: 19px; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; margin: 0; padding: 0;" align="left" valign="top"></td>
|
||||
|
14
public/emails/welcome_on_signup.txt
Normal file
14
public/emails/welcome_on_signup.txt
Normal file
@ -0,0 +1,14 @@
|
||||
{{Subject .Subject "Welcome to Grafana"}}
|
||||
|
||||
Hi {{.Name}},
|
||||
|
||||
Welcome! Ready to start building some beautiful metric and analytic dashboards?
|
||||
|
||||
If you are new to Grafana, refer to the Getting started with Grafana guide on
|
||||
https://grafana.com/docs/grafana/latest/getting-started/getting-started/.
|
||||
|
||||
Thank you for joining our community.
|
||||
|
||||
The Grafana team
|
||||
|
||||
Sent by Grafana v{{.BuildVersion}} (c) 2021 Grafana Labs
|
Loading…
Reference in New Issue
Block a user