Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 73 additions & 10 deletions alerting/provider/email/email.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ type Config struct {
Port int `yaml:"port"`
To string `yaml:"to"`

// Strings used in the email body and subject
// These fields are optional and will be replaced with default values if not set
TextEmailSubjectTriggered string `yaml:"text-email-subject-triggered,omitempty"`
TextEmailSubjectResolved string `yaml:"text-email-subject-resolved,omitempty"`
TextEmailBodyTriggered string `yaml:"text-email-body-triggered,omitempty"`
TextEmailBodyResolved string `yaml:"text-email-body-resolved,omitempty"`
TextEmailHeader string `yaml:"text-email-header,omitempty"`
TextEmailFooter string `yaml:"text-email-footer,omitempty"`
TextEmailDescription string `yaml:"text-email-description,omitempty"`
TextIncludeConditionResults *bool `yaml:"text-include-condition-results,omitempty"`
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm really not sure about this. I'd almost rather have just the subject and the body, and then have a placeholder for the condition results. This would remove the need for the header, the footer, the description and the bool to include/exclude the condition results.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello! I would like to be able to say hello and display information in the footer in the case of an email alert provider, all fields as optional parameter. And everything is backward compatible. Given that not everyone understands the technical details, I would like to optionally hide the condition results section.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm really not sure about this. I'd almost rather have just the subject and the body, and then have a placeholder for the condition results. This would remove the need for the header, the footer, the description and the bool to include/exclude the condition results.

Sorry, I just understood exactly what you wanted. I'll edit it and report back with the changes.


// ClientConfig is the configuration of the client used to communicate with the provider's target
ClientConfig *client.Config `yaml:"client,omitempty"`
}
Expand Down Expand Up @@ -68,6 +79,30 @@ func (cfg *Config) Merge(override *Config) {
if len(override.To) > 0 {
cfg.To = override.To
}
if len(override.TextEmailSubjectTriggered) > 0 {
cfg.TextEmailSubjectTriggered = override.TextEmailSubjectTriggered
}
if len(override.TextEmailSubjectResolved) > 0 {
cfg.TextEmailSubjectResolved = override.TextEmailSubjectResolved
}
if len(override.TextEmailBodyTriggered) > 0 {
cfg.TextEmailBodyTriggered = override.TextEmailBodyTriggered
}
if len(override.TextEmailBodyResolved) > 0 {
cfg.TextEmailBodyResolved = override.TextEmailBodyResolved
}
if len(override.TextEmailHeader) > 0 {
cfg.TextEmailHeader = override.TextEmailHeader
}
if len(override.TextEmailFooter) > 0 {
cfg.TextEmailFooter = override.TextEmailFooter
}
if len(override.TextEmailDescription) > 0 {
cfg.TextEmailDescription = override.TextEmailDescription
}
if override.TextIncludeConditionResults != nil {
cfg.TextIncludeConditionResults = override.TextIncludeConditionResults
}
}

// AlertProvider is the configuration necessary for sending an alert using SMTP
Expand Down Expand Up @@ -113,7 +148,7 @@ func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, r
} else {
username = cfg.From
}
subject, body := provider.buildMessageSubjectAndBody(ep, alert, result, resolved)
subject, body := provider.buildMessageSubjectAndBody(cfg, ep, alert, result, resolved)
m := gomail.NewMessage()
m.SetHeader("From", cfg.From)
m.SetHeader("To", strings.Split(cfg.To, ",")...)
Expand All @@ -140,17 +175,40 @@ func (provider *AlertProvider) Send(ep *endpoint.Endpoint, alert *alert.Alert, r
}

// buildMessageSubjectAndBody builds the message subject and body
func (provider *AlertProvider) buildMessageSubjectAndBody(ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) (string, string) {
var subject, message string
func (provider *AlertProvider) buildMessageSubjectAndBody(cfg *Config, ep *endpoint.Endpoint, alert *alert.Alert, result *endpoint.Result, resolved bool) (string, string) {
var header, footer, subject, message string
if len(cfg.TextEmailHeader) > 0 {
header = cfg.TextEmailHeader + "\n\n"
}
if len(cfg.TextEmailFooter) > 0 {
footer = "\n\n" + cfg.TextEmailFooter
}
if resolved {
subject = fmt.Sprintf("[%s] Alert resolved", ep.DisplayName())
message = fmt.Sprintf("An alert for %s has been resolved after passing successfully %d time(s) in a row", ep.DisplayName(), alert.SuccessThreshold)
if len(cfg.TextEmailSubjectResolved) > 0 {
subject = strings.Replace(cfg.TextEmailSubjectResolved, "{endpoint}", ep.DisplayName(), 1)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're going to implement placeholders, they should be the same as the other placeholders throughout the configuration. See https://github.com/TwiN/gatus?tab=readme-ov-file#configuring-custom-alerts

[ENDPOINT_NAME] (resolved from endpoints[].name)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello! I've used {endpoint} and {description} placeholders i the Twilio provider, too. Should I unify them?
#1120

} else {
subject = fmt.Sprintf("[%s] Alert resolved", ep.DisplayName())
}
if len(cfg.TextEmailBodyResolved) > 0 {
message = strings.Replace(strings.Replace(cfg.TextEmailBodyResolved, "{endpoint}", ep.DisplayName(), 1), "{threshold}", string(rune(alert.SuccessThreshold)), 1)
} else {
message = fmt.Sprintf("An alert for %s has been resolved after passing successfully %d time(s) in a row", ep.DisplayName(), alert.SuccessThreshold)
}
} else {
subject = fmt.Sprintf("[%s] Alert triggered", ep.DisplayName())
message = fmt.Sprintf("An alert for %s has been triggered due to having failed %d time(s) in a row", ep.DisplayName(), alert.FailureThreshold)
if len(cfg.TextEmailSubjectTriggered) > 0 {
subject = strings.Replace(cfg.TextEmailSubjectTriggered, "{endpoint}", ep.DisplayName(), 1)
} else {
subject = fmt.Sprintf("[%s] Alert triggered", ep.DisplayName())
}
if len(cfg.TextEmailBodyTriggered) > 0 {
message = strings.Replace(strings.Replace(cfg.TextEmailBodyTriggered, "{endpoint}", ep.DisplayName(), 1), "{threshold}", string(rune(alert.FailureThreshold)), 1)
} else {
message = fmt.Sprintf("An alert for %s has been triggered due to having failed %d time(s) in a row", ep.DisplayName(), alert.FailureThreshold)
}
}
var formattedConditionResults string
if len(result.ConditionResults) > 0 {
if len(result.ConditionResults) > 0 && (cfg.TextIncludeConditionResults == nil || *cfg.TextIncludeConditionResults) {
// If the condition results are included, format them
formattedConditionResults = "\n\nCondition results:\n"
for _, conditionResult := range result.ConditionResults {
var prefix string
Expand All @@ -164,9 +222,14 @@ func (provider *AlertProvider) buildMessageSubjectAndBody(ep *endpoint.Endpoint,
}
var description string
if alertDescription := alert.GetDescription(); len(alertDescription) > 0 {
description = "\n\nAlert description: " + alertDescription
if len(cfg.TextEmailDescription) > 0 {
description = "\n\n" + strings.Replace(cfg.TextEmailDescription, "{description}", alertDescription, 1)
} else {
// If no description is provided, use the alert's description}
description = "\n\nAlert description: " + alertDescription
}
}
return subject, message + description + formattedConditionResults
return subject, header + message + description + formattedConditionResults + footer
}

// GetDefaultAlert returns the provider's default alert configuration
Expand Down
1 change: 1 addition & 0 deletions alerting/provider/email/email_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
for _, scenario := range scenarios {
t.Run(scenario.Name, func(t *testing.T) {
subject, body := scenario.Provider.buildMessageSubjectAndBody(
&Config{},
&endpoint.Endpoint{Name: "endpoint-name"},
&scenario.Alert,
&endpoint.Result{
Expand Down