Skip to content

refactor: refactor notification email templates #14208

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Aug 9, 2024
Merged
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
Next Next commit
Update template email
  • Loading branch information
BrunoQuaresma committed Aug 7, 2024
commit 898014c8a650390b403e75e7fea64a0c5b689705
3 changes: 2 additions & 1 deletion cli/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -992,6 +992,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
)
if experiments.Enabled(codersdk.ExperimentNotifications) {
cfg := options.DeploymentValues.Notifications
accessUrl := options.DeploymentValues.AccessURL.Value().String()
metrics := notifications.NewMetrics(options.PrometheusRegistry)

// The enqueuer is responsible for enqueueing notifications to the given store.
Expand All @@ -1004,7 +1005,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
// The notification manager is responsible for:
// - creating notifiers and managing their lifecycles (notifiers are responsible for dequeueing/sending notifications)
// - keeping the store updated with status updates
notificationsManager, err = notifications.NewManager(cfg, options.Database, metrics, logger.Named("notifications.manager"))
notificationsManager, err = notifications.NewManager(cfg, options.Database, accessUrl, metrics, logger.Named("notifications.manager"))
if err != nil {
return xerrors.Errorf("failed to instantiate notification manager: %w", err)
}
Expand Down
11 changes: 7 additions & 4 deletions coderd/notifications/dispatch/smtp.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,15 @@ var (
// NOTE: auth and TLS is currently *not* enabled in this initial thin slice.
// TODO: implement DKIM/SPF/DMARC? https://github.com/emersion/go-msgauth
type SMTPHandler struct {
cfg codersdk.NotificationsEmailConfig
log slog.Logger
cfg codersdk.NotificationsEmailConfig
log slog.Logger
accessURL string

loginWarnOnce sync.Once
}

func NewSMTPHandler(cfg codersdk.NotificationsEmailConfig, log slog.Logger) *SMTPHandler {
return &SMTPHandler{cfg: cfg, log: log}
func NewSMTPHandler(cfg codersdk.NotificationsEmailConfig, accessURL string, log slog.Logger) *SMTPHandler {
return &SMTPHandler{cfg: cfg, accessURL: accessURL, log: log}
}

func (s *SMTPHandler) Dispatcher(payload types.MessagePayload, titleTmpl, bodyTmpl string) (DeliveryFunc, error) {
Expand All @@ -75,6 +76,8 @@ 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
payload.Labels["_year"] = time.Now().Format("2006")
payload.Labels["_accessURL"] = s.accessURL
htmlBody, err = render.GoTemplate(htmlTemplate, payload, nil)
if err != nil {
return nil, xerrors.Errorf("render full html template: %w", err)
Expand Down
107 changes: 87 additions & 20 deletions coderd/notifications/dispatch/smtp/html.gotmpl
Original file line number Diff line number Diff line change
@@ -1,27 +1,94 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{ .Labels._subject }}</title>
</head>
<body style="font-family: Arial, sans-serif; background-color: #1d1d20; margin: 0; padding: 0;">
<div style="max-width: 600px; margin: 20px auto; background-color: #3f556d; border: 1px solid #34495E; padding: 20px; border-radius: 8px;">
<div style="text-align: center; padding: 20px 0; border-bottom: 1px solid #34495E;">
<img width="215" height="47" src="https://coder.com/logo-wide-white.png"/>
</div>
<div style="padding: 20px; color: #ECF0F1; line-height: 1.6;">
<h1 style="color: #ECF0F1;">{{ .Labels._subject }}</h1>
{{ .Labels._body }}
<style type="text/css">
body {
font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI",
"Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
"Helvetica Neue", sans-serif;
margin: 0;
padding: 0;
color: #000;
}

.card {
max-width: 600px;
margin: 20px auto;
border: 1px solid #eaeaea;
padding: 60px;
border-radius: 8px;
line-height: 1.5;
font-size: 14px;
}

.logo {
width: 60px;
}

.title {
text-align: center;
font-weight: 400;
font-size: 24px;
margin: 8px 0 32px;
line-height: 1.5;
}

.actions {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
margin-top: 32px;
}

.action {
display: inline-block;
padding: 13px 24px;
background-color: #000;
color: #ffffff;
text-decoration: none;
border-radius: 8px;
}

.footer {
border-top: 1px solid #eaeaea;
color: #666;
font-size: 12px;
margin-top: 64px;
padding-top: 24px;
line-height: 1.6;
}

.link {
color: #067df7;
text-decoration: none;
}
</style>
</head>
<body>
<div class="card">
<img class="logo" src="https://coder.com/coder-logo-300x300.png" />
<h1 class="title">{{ .Labels._subject }}</h1>
{{ .Labels._body }}

<div class="actions">
{{ range $action := .Actions }}
<a href="{{ $action.URL }}" style="display: inline-block; padding: 10px 20px; background-color: #3D74DB; color: #ffffff; text-decoration: none; border-radius: 4px; margin-top: 20px;">{{ $action.Label }}</a><br>
<a href="{{ $action.URL }}" class="action">{{ $action.Label }}</a>
{{ end }}
</div>
<div style="text-align: center; padding: 10px 0; border-top: 1px solid #34495E; margin-top: 20px; color: #BDC3C7;">
</div>

<div class="footer">
<!-- TODO: dynamic copyright -->
&copy; 2024 Coder. All rights reserved.
<p>&copy; 2024 Coder. All rights reserved.</p>
<p>
<a href="" class="link"
>Click here to manage your notification settings</a
>
</p>
</div>
</div>
</div>
</body>
</html>
</body>
</html>
8 changes: 4 additions & 4 deletions coderd/notifications/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ type Manager struct {
//
// helpers is a map of template helpers which are used to customize notification messages to use global settings like
// access URL etc.
func NewManager(cfg codersdk.NotificationsConfig, store Store, metrics *Metrics, log slog.Logger) (*Manager, error) {
func NewManager(cfg codersdk.NotificationsConfig, store Store, host string, metrics *Metrics, log slog.Logger) (*Manager, error) {
// TODO(dannyk): add the ability to use multiple notification methods.
var method database.NotificationMethod
if err := method.Scan(cfg.Method.String()); err != nil {
Expand Down Expand Up @@ -93,14 +93,14 @@ func NewManager(cfg codersdk.NotificationsConfig, store Store, metrics *Metrics,
stop: make(chan any),
done: make(chan any),

handlers: defaultHandlers(cfg, log),
handlers: defaultHandlers(cfg, host, log),
}, nil
}

// defaultHandlers builds a set of known handlers; panics if any error occurs as these handlers should be valid at compile time.
func defaultHandlers(cfg codersdk.NotificationsConfig, log slog.Logger) map[database.NotificationMethod]Handler {
func defaultHandlers(cfg codersdk.NotificationsConfig, accessURL string, log slog.Logger) map[database.NotificationMethod]Handler {
return map[database.NotificationMethod]Handler{
database.NotificationMethodSmtp: dispatch.NewSMTPHandler(cfg.SMTP, log.Named("dispatcher.smtp")),
database.NotificationMethodSmtp: dispatch.NewSMTPHandler(cfg.SMTP, accessURL, log.Named("dispatcher.smtp")),
database.NotificationMethodWebhook: dispatch.NewWebhookHandler(cfg.Webhook, log.Named("dispatcher.webhook")),
}
}
Expand Down