feat(coderd): add company logo when available for email notifications (#14935)

This PR aims to close #14253 

We keep the default behavior using the Coder logo if there's no logo
set.
Otherwise we want to use the logo based on the URL set in appearance.

---------

Co-authored-by: defelmnq <yvincent@coder.com>
This commit is contained in:
Vincent Vielle
2024-10-22 14:06:19 +02:00
committed by GitHub
co-authored by defelmnq
parent c42f487668
commit 297089e944
16 changed files with 289 additions and 52 deletions
+5 -7
View File
@@ -55,15 +55,13 @@ type SMTPHandler struct {
noAuthWarnOnce sync.Once
loginWarnOnce sync.Once
helpers template.FuncMap
}
func NewSMTPHandler(cfg codersdk.NotificationsEmailConfig, helpers template.FuncMap, log slog.Logger) *SMTPHandler {
return &SMTPHandler{cfg: cfg, helpers: helpers, log: log}
func NewSMTPHandler(cfg codersdk.NotificationsEmailConfig, log slog.Logger) *SMTPHandler {
return &SMTPHandler{cfg: cfg, log: log}
}
func (s *SMTPHandler) Dispatcher(payload types.MessagePayload, titleTmpl, bodyTmpl string) (DeliveryFunc, error) {
func (s *SMTPHandler) Dispatcher(payload types.MessagePayload, titleTmpl, bodyTmpl string, helpers template.FuncMap) (DeliveryFunc, error) {
// First render the subject & body into their own discrete strings.
subject, err := markdown.PlaintextFromMarkdown(titleTmpl)
if err != nil {
@@ -79,12 +77,12 @@ func (s *SMTPHandler) Dispatcher(payload types.MessagePayload, titleTmpl, bodyTm
// Then, reuse these strings in the HTML & plain body templates.
payload.Labels["_subject"] = subject
payload.Labels["_body"] = htmlBody
htmlBody, err = render.GoTemplate(htmlTemplate, payload, s.helpers)
htmlBody, err = render.GoTemplate(htmlTemplate, payload, helpers)
if err != nil {
return nil, xerrors.Errorf("render full html template: %w", err)
}
payload.Labels["_body"] = plainBody
plainBody, err = render.GoTemplate(plainTemplate, payload, s.helpers)
plainBody, err = render.GoTemplate(plainTemplate, payload, helpers)
if err != nil {
return nil, xerrors.Errorf("render full plaintext template: %w", err)
}
@@ -8,7 +8,7 @@
<body style="margin: 0; padding: 0; font-family: -apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; color: #020617; background: #f8fafc;">
<div style="max-width: 600px; margin: 20px auto; padding: 60px; border: 1px solid #e2e8f0; border-radius: 8px; background-color: #fff; text-align: left; font-size: 14px; line-height: 1.5;">
<div style="text-align: center;">
<img src="https://coder.com/coder-logo-horizontal.png" alt="Coder Logo" style="height: 40px;" />
<img src="{{ logo_url }}" alt="{{ app_name }} Logo" style="height: 40px;" />
</div>
<h1 style="text-align: center; font-size: 24px; font-weight: 400; margin: 8px 0 32px; line-height: 1.5;">
{{ .Labels._subject }}
+2 -6
View File
@@ -442,11 +442,7 @@ func TestSMTP(t *testing.T) {
require.NoError(t, hp.Set(listen.Addr().String()))
tc.cfg.Smarthost = hp
helpers := map[string]any{
"base_url": func() string { return "http://test.com" },
"current_year": func() string { return "2024" },
}
handler := dispatch.NewSMTPHandler(tc.cfg, helpers, logger.Named("smtp"))
handler := dispatch.NewSMTPHandler(tc.cfg, logger.Named("smtp"))
// Start mock SMTP server in the background.
var wg sync.WaitGroup
@@ -484,7 +480,7 @@ func TestSMTP(t *testing.T) {
Labels: make(map[string]string),
}
dispatchFn, err := handler.Dispatcher(payload, subject, body)
dispatchFn, err := handler.Dispatcher(payload, subject, body, helpers())
require.NoError(t, err)
msgID := uuid.New()
@@ -0,0 +1,10 @@
package dispatch_test
func helpers() map[string]any {
return map[string]any{
"base_url": func() string { return "http://test.com" },
"current_year": func() string { return "2024" },
"logo_url": func() string { return "https://coder.com/coder-logo-horizontal.png" },
"app_name": func() string { return "Coder" },
}
}
+2 -1
View File
@@ -7,6 +7,7 @@ import (
"errors"
"io"
"net/http"
"text/template"
"github.com/google/uuid"
"golang.org/x/xerrors"
@@ -41,7 +42,7 @@ func NewWebhookHandler(cfg codersdk.NotificationsWebhookConfig, log slog.Logger)
return &WebhookHandler{cfg: cfg, log: log, cl: &http.Client{}}
}
func (w *WebhookHandler) Dispatcher(payload types.MessagePayload, titleMarkdown, bodyMarkdown string) (DeliveryFunc, error) {
func (w *WebhookHandler) Dispatcher(payload types.MessagePayload, titleMarkdown, bodyMarkdown string, _ template.FuncMap) (DeliveryFunc, error) {
if w.cfg.Endpoint.String() == "" {
return nil, xerrors.New("webhook endpoint not defined")
}
@@ -141,7 +141,7 @@ func TestWebhook(t *testing.T) {
Endpoint: *serpent.URLOf(endpoint),
}
handler := dispatch.NewWebhookHandler(cfg, logger.With(slog.F("test", tc.name)))
deliveryFn, err := handler.Dispatcher(msgPayload, titleMarkdown, bodyMarkdown)
deliveryFn, err := handler.Dispatcher(msgPayload, titleMarkdown, bodyMarkdown, helpers())
require.NoError(t, err)
retryable, err := deliveryFn(ctx, msgID)