chore(coderd/notifications): expand golden file testing for notifications (#15032)

This PR aims to close https://github.com/coder/coder/issues/14913.

It expands the golden files for the notifier to include the entire
payload serialised as JSON.
This commit is contained in:
Sas Swart
2024-10-14 12:34:32 +00:00
committed by GitHub
parent 9c8ecb82a3
commit 208ed1efd7
79 changed files with 2265 additions and 204 deletions
@@ -0,0 +1,55 @@
-- UserAccountCreated
UPDATE notification_templates
SET
body_template = E'Hi {{.UserName}},\n\n' ||
E'New user account **{{.Labels.created_account_name}}** has been created.\n\n' ||
-- Mention the real name of the user who created the account:
E'This new user account was created for **{{.Labels.created_account_user_name}}** by **{{.Labels.account_creator}}**.'
WHERE
id = '4e19c0ac-94e1-4532-9515-d1801aa283b2';
-- UserAccountDeleted
UPDATE notification_templates
SET
body_template = E'Hi {{.UserName}},\n\n' ||
E'User account **{{.Labels.deleted_account_name}}** has been deleted.\n\n' ||
-- Mention the real name of the user who deleted the account:
E'The deleted account belonged to **{{.Labels.deleted_account_user_name}}** and was deleted by **{{.Labels.account_deleter_user_name}}**.'
WHERE
id = 'f44d9314-ad03-4bc8-95d0-5cad491da6b6';
-- UserAccountSuspended
UPDATE notification_templates
SET
body_template = E'Hi {{.UserName}},\n\n' || -- Add a \n
E'User account **{{.Labels.suspended_account_name}}** has been suspended.\n\n' ||
-- Mention the real name of the user who suspended the account:
E'The newly suspended account belongs to **{{.Labels.suspended_account_user_name}}** and was suspended by **{{.Labels.account_suspender_user_name}}**.'
WHERE
id = 'b02ddd82-4733-4d02-a2d7-c36f3598997d';
-- YourAccountSuspended
UPDATE notification_templates
SET
body_template = E'Hi {{.UserName}},\n\n' || -- Add a \n
E'Your account **{{.Labels.suspended_account_name}}** has been suspended by **{{.Labels.account_suspender_user_name}}**.'
WHERE
id = '6a2f0609-9b69-4d36-a989-9f5925b6cbff';
-- UserAccountActivated
UPDATE notification_templates
SET
body_template = E'Hi {{.UserName}},\n\n' || -- Add a \n
E'User account **{{.Labels.activated_account_name}}** has been activated.\n\n' ||
E'The newly activated account belongs to **{{.Labels.activated_account_user_name}}** and was activated by **{{.Labels.account_activator_user_name}}**.'
WHERE
id = '9f5af851-8408-4e73-a7a1-c6502ba46689';
-- YourAccountActivated
UPDATE notification_templates
SET
body_template = E'Hi {{.UserName}},\n\n' || -- Add a \n
E'Your account **{{.Labels.activated_account_name}}** has been activated by **{{.Labels.account_activator_user_name}}**.'
WHERE
id = '1a6a6bea-ee0a-43e2-9e7c-eabdb53730e4';
@@ -0,0 +1,57 @@
-- UserAccountCreated
UPDATE notification_templates
SET
body_template = E'Hi {{.UserName}},\n\n' ||
E'New user account **{{.Labels.created_account_name}}** has been created.\n\n' ||
-- Use the conventional initiator label:
E'This new user account was created for **{{.Labels.created_account_user_name}}** by **{{.Labels.initiator}}**.'
WHERE
id = '4e19c0ac-94e1-4532-9515-d1801aa283b2';
-- UserAccountDeleted
UPDATE notification_templates
SET
body_template = E'Hi {{.UserName}},\n\n' ||
E'User account **{{.Labels.deleted_account_name}}** has been deleted.\n\n' ||
-- Use the conventional initiator label:
E'The deleted account belonged to **{{.Labels.deleted_account_user_name}}** and was deleted by **{{.Labels.initiator}}**.'
WHERE
id = 'f44d9314-ad03-4bc8-95d0-5cad491da6b6';
-- UserAccountSuspended
UPDATE notification_templates
SET
body_template = E'Hi {{.UserName}},\n\n' || -- Add a \n
E'User account **{{.Labels.suspended_account_name}}** has been suspended.\n\n' ||
-- Use the conventional initiator label:
E'The newly suspended account belongs to **{{.Labels.suspended_account_user_name}}** and was suspended by **{{.Labels.initiator}}**.'
WHERE
id = 'b02ddd82-4733-4d02-a2d7-c36f3598997d';
-- YourAccountSuspended
UPDATE notification_templates
SET
body_template = E'Hi {{.UserName}},\n\n' || -- Add a \n
-- Use the conventional initiator label:
E'Your account **{{.Labels.suspended_account_name}}** has been suspended by **{{.Labels.initiator}}**.'
WHERE
id = '6a2f0609-9b69-4d36-a989-9f5925b6cbff';
-- UserAccountActivated
UPDATE notification_templates
SET
body_template = E'Hi {{.UserName}},\n\n' || -- Add a \n
E'User account **{{.Labels.activated_account_name}}** has been activated.\n\n' ||
-- Use the conventional initiator label:
E'The newly activated account belongs to **{{.Labels.activated_account_user_name}}** and was activated by **{{.Labels.initiator}}**.'
WHERE
id = '9f5af851-8408-4e73-a7a1-c6502ba46689';
-- YourAccountActivated
UPDATE notification_templates
SET
body_template = E'Hi {{.UserName}},\n\n' || -- Add a \n
-- Use the conventional initiator label:
E'Your account **{{.Labels.activated_account_name}}** has been activated by **{{.Labels.initiator}}**.'
WHERE
id = '1a6a6bea-ee0a-43e2-9e7c-eabdb53730e4';
+12 -30
View File
@@ -2,11 +2,8 @@ package dispatch_test
import (
"bytes"
"crypto/tls"
_ "embed"
"fmt"
"log"
"net"
"sync"
"testing"
@@ -22,6 +19,7 @@ import (
"github.com/coder/serpent"
"github.com/coder/coder/v2/coderd/notifications/dispatch"
"github.com/coder/coder/v2/coderd/notifications/dispatch/smtptest"
"github.com/coder/coder/v2/coderd/notifications/types"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/testutil"
@@ -47,9 +45,9 @@ func TestSMTP(t *testing.T) {
subject = "This is the subject"
body = "This is the body"
caFile = "fixtures/ca.crt"
certFile = "fixtures/server.crt"
keyFile = "fixtures/server.key"
caFile = "smtptest/fixtures/ca.crt"
certFile = "smtptest/fixtures/server.crt"
keyFile = "smtptest/fixtures/server.key"
)
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true, IgnoredErrorIs: []error{}}).Leveled(slog.LevelDebug)
@@ -125,7 +123,7 @@ func TestSMTP(t *testing.T) {
Auth: codersdk.NotificationsEmailAuthConfig{
Username: username,
PasswordFile: "fixtures/password.txt",
PasswordFile: "smtptest/fixtures/password.txt",
},
},
toAddrs: []string{to},
@@ -341,14 +339,14 @@ func TestSMTP(t *testing.T) {
cfg: codersdk.NotificationsEmailConfig{
TLS: codersdk.NotificationsEmailTLSConfig{
CAFile: caFile,
CertFile: "fixtures/nope.cert",
CertFile: "smtptest/fixtures/nope.cert",
KeyFile: keyFile,
},
},
// not using full error message here since it differs on *nix and Windows:
// *nix: no such file or directory
// Windows: The system cannot find the file specified.
expectedErr: "open fixtures/nope.cert:",
expectedErr: "open smtptest/fixtures/nope.cert:",
retryable: true,
},
{
@@ -358,13 +356,13 @@ func TestSMTP(t *testing.T) {
TLS: codersdk.NotificationsEmailTLSConfig{
CAFile: caFile,
CertFile: certFile,
KeyFile: "fixtures/nope.key",
KeyFile: "smtptest/fixtures/nope.key",
},
},
// not using full error message here since it differs on *nix and Windows:
// *nix: no such file or directory
// Windows: The system cannot find the file specified.
expectedErr: "open fixtures/nope.key:",
expectedErr: "open smtptest/fixtures/nope.key:",
retryable: true,
},
/**
@@ -417,7 +415,7 @@ func TestSMTP(t *testing.T) {
tc.cfg.ForceTLS = serpent.Bool(tc.useTLS)
backend := NewBackend(Config{
backend := smtptest.NewBackend(smtptest.Config{
AuthMechanisms: tc.authMechs,
AcceptedIdentity: tc.cfg.Auth.Identity.String(),
@@ -428,7 +426,7 @@ func TestSMTP(t *testing.T) {
})
// Create a mock SMTP server which conditionally listens for plain or TLS connections.
srv, listen, err := createMockSMTPServer(backend, tc.useTLS)
srv, listen, err := smtptest.CreateMockSMTPServer(backend, tc.useTLS)
require.NoError(t, err)
t.Cleanup(func() {
// We expect that the server has already been closed in the test
@@ -460,7 +458,7 @@ func TestSMTP(t *testing.T) {
// Wait for the server to become pingable.
require.Eventually(t, func() bool {
cl, err := pingClient(listen, tc.useTLS, tc.cfg.TLS.StartTLS.Value())
cl, err := smtptest.PingClient(listen, tc.useTLS, tc.cfg.TLS.StartTLS.Value())
if err != nil {
t.Logf("smtp not yet dialable: %s", err)
return false
@@ -522,19 +520,3 @@ func TestSMTP(t *testing.T) {
})
}
}
func pingClient(listen net.Listener, useTLS bool, startTLS bool) (*smtp.Client, error) {
tlsCfg := &tls.Config{
// nolint:gosec // It's a test.
InsecureSkipVerify: true,
}
switch {
case useTLS:
return smtp.DialTLS(listen.Addr().String(), tlsCfg)
case startTLS:
return smtp.DialStartTLS(listen.Addr().String(), tlsCfg)
default:
return smtp.Dial(listen.Addr().String())
}
}
@@ -1,4 +1,4 @@
package dispatch_test
package smtptest
import (
"crypto/tls"
@@ -162,7 +162,7 @@ func (*Session) Reset() {}
func (*Session) Logout() error { return nil }
// nolint:revive // Yes, useTLS is a control flag.
func createMockSMTPServer(be *Backend, useTLS bool) (*smtp.Server, net.Listener, error) {
func CreateMockSMTPServer(be *Backend, useTLS bool) (*smtp.Server, net.Listener, error) {
// nolint:gosec
tlsCfg := &tls.Config{
GetCertificate: readCert,
@@ -203,3 +203,19 @@ func readCert(_ *tls.ClientHelloInfo) (*tls.Certificate, error) {
return &crt, nil
}
func PingClient(listen net.Listener, useTLS bool, startTLS bool) (*smtp.Client, error) {
tlsCfg := &tls.Config{
// nolint:gosec // It's a test.
InsecureSkipVerify: true,
}
switch {
case useTLS:
return smtp.DialTLS(listen.Addr().String(), tlsCfg)
case startTLS:
return smtp.DialStartTLS(listen.Addr().String(), tlsCfg)
default:
return smtp.Dial(listen.Addr().String())
}
}
+361 -68
View File
@@ -10,11 +10,13 @@ import (
"go/ast"
"go/parser"
"go/token"
"io"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"regexp"
"slices"
"sort"
"strings"
@@ -23,18 +25,16 @@ import (
"testing"
"time"
"golang.org/x/xerrors"
"github.com/coder/quartz"
"github.com/emersion/go-sasl"
"github.com/google/go-cmp/cmp"
"github.com/google/uuid"
smtpmock "github.com/mocktools/go-smtp-mock/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/goleak"
"golang.org/x/xerrors"
"github.com/coder/serpent"
"cdr.dev/slog"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbauthz"
@@ -42,12 +42,14 @@ import (
"github.com/coder/coder/v2/coderd/database/dbtestutil"
"github.com/coder/coder/v2/coderd/notifications"
"github.com/coder/coder/v2/coderd/notifications/dispatch"
"github.com/coder/coder/v2/coderd/notifications/render"
"github.com/coder/coder/v2/coderd/notifications/dispatch/smtptest"
"github.com/coder/coder/v2/coderd/notifications/types"
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/coderd/util/syncmap"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/testutil"
"github.com/coder/quartz"
"github.com/coder/serpent"
)
// updateGoldenFiles is a flag that can be set to update golden files.
@@ -691,6 +693,16 @@ func TestNotificationTemplates_Golden(t *testing.T) {
t.Skip("This test requires postgres; it relies on the notification templates added by migrations in the database")
}
const (
username = "bob"
password = "🤫"
hello = "localhost"
from = "system@coder.com"
hint = "run \"DB=ci make update-golden-files\" and commit the changes"
)
tests := []struct {
name string
id uuid.UUID
@@ -700,7 +712,9 @@ func TestNotificationTemplates_Golden(t *testing.T) {
name: "TemplateWorkspaceDeleted",
id: notifications.TemplateWorkspaceDeleted,
payload: types.MessagePayload{
UserName: "Bobby",
UserName: "Bobby",
UserEmail: "bobby@coder.com",
UserUsername: "bobby",
Labels: map[string]string{
"name": "bobby-workspace",
"reason": "autodeleted due to dormancy",
@@ -712,7 +726,9 @@ func TestNotificationTemplates_Golden(t *testing.T) {
name: "TemplateWorkspaceAutobuildFailed",
id: notifications.TemplateWorkspaceAutobuildFailed,
payload: types.MessagePayload{
UserName: "Bobby",
UserName: "Bobby",
UserEmail: "bobby@coder.com",
UserUsername: "bobby",
Labels: map[string]string{
"name": "bobby-workspace",
"reason": "autostart",
@@ -723,7 +739,9 @@ func TestNotificationTemplates_Golden(t *testing.T) {
name: "TemplateWorkspaceDormant",
id: notifications.TemplateWorkspaceDormant,
payload: types.MessagePayload{
UserName: "Bobby",
UserName: "Bobby",
UserEmail: "bobby@coder.com",
UserUsername: "bobby",
Labels: map[string]string{
"name": "bobby-workspace",
"reason": "breached the template's threshold for inactivity",
@@ -737,7 +755,9 @@ func TestNotificationTemplates_Golden(t *testing.T) {
name: "TemplateWorkspaceAutoUpdated",
id: notifications.TemplateWorkspaceAutoUpdated,
payload: types.MessagePayload{
UserName: "Bobby",
UserName: "Bobby",
UserEmail: "bobby@coder.com",
UserUsername: "bobby",
Labels: map[string]string{
"name": "bobby-workspace",
"template_version_name": "1.0",
@@ -749,7 +769,9 @@ func TestNotificationTemplates_Golden(t *testing.T) {
name: "TemplateWorkspaceMarkedForDeletion",
id: notifications.TemplateWorkspaceMarkedForDeletion,
payload: types.MessagePayload{
UserName: "Bobby",
UserName: "Bobby",
UserEmail: "bobby@coder.com",
UserUsername: "bobby",
Labels: map[string]string{
"name": "bobby-workspace",
"reason": "template updated to new dormancy policy",
@@ -762,11 +784,13 @@ func TestNotificationTemplates_Golden(t *testing.T) {
name: "TemplateUserAccountCreated",
id: notifications.TemplateUserAccountCreated,
payload: types.MessagePayload{
UserName: "Bobby",
UserName: "Bobby",
UserEmail: "bobby@coder.com",
UserUsername: "bobby",
Labels: map[string]string{
"created_account_name": "bobby",
"created_account_user_name": "William Tables",
"account_creator": "rob",
"initiator": "rob",
},
},
},
@@ -774,11 +798,13 @@ func TestNotificationTemplates_Golden(t *testing.T) {
name: "TemplateUserAccountDeleted",
id: notifications.TemplateUserAccountDeleted,
payload: types.MessagePayload{
UserName: "Bobby",
UserName: "Bobby",
UserEmail: "bobby@coder.com",
UserUsername: "bobby",
Labels: map[string]string{
"deleted_account_name": "bobby",
"deleted_account_user_name": "william tables",
"account_deleter_user_name": "rob",
"deleted_account_user_name": "William Tables",
"initiator": "rob",
},
},
},
@@ -786,11 +812,13 @@ func TestNotificationTemplates_Golden(t *testing.T) {
name: "TemplateUserAccountSuspended",
id: notifications.TemplateUserAccountSuspended,
payload: types.MessagePayload{
UserName: "Bobby",
UserName: "Bobby",
UserEmail: "bobby@coder.com",
UserUsername: "bobby",
Labels: map[string]string{
"suspended_account_name": "bobby",
"suspended_account_user_name": "william tables",
"account_suspender_user_name": "rob",
"suspended_account_user_name": "William Tables",
"initiator": "rob",
},
},
},
@@ -798,11 +826,13 @@ func TestNotificationTemplates_Golden(t *testing.T) {
name: "TemplateUserAccountActivated",
id: notifications.TemplateUserAccountActivated,
payload: types.MessagePayload{
UserName: "Bobby",
UserName: "Bobby",
UserEmail: "bobby@coder.com",
UserUsername: "bobby",
Labels: map[string]string{
"activated_account_name": "bobby",
"activated_account_user_name": "william tables",
"account_activator_user_name": "rob",
"activated_account_user_name": "William Tables",
"initiator": "rob",
},
},
},
@@ -810,10 +840,12 @@ func TestNotificationTemplates_Golden(t *testing.T) {
name: "TemplateYourAccountSuspended",
id: notifications.TemplateYourAccountSuspended,
payload: types.MessagePayload{
UserName: "Bobby",
UserName: "Bobby",
UserEmail: "bobby@coder.com",
UserUsername: "bobby",
Labels: map[string]string{
"suspended_account_name": "bobby",
"account_suspender_user_name": "rob",
"suspended_account_name": "bobby",
"initiator": "rob",
},
},
},
@@ -821,10 +853,12 @@ func TestNotificationTemplates_Golden(t *testing.T) {
name: "TemplateYourAccountActivated",
id: notifications.TemplateYourAccountActivated,
payload: types.MessagePayload{
UserName: "Bobby",
UserName: "Bobby",
UserEmail: "bobby@coder.com",
UserUsername: "bobby",
Labels: map[string]string{
"activated_account_name": "bobby",
"account_activator_user_name": "rob",
"activated_account_name": "bobby",
"initiator": "rob",
},
},
},
@@ -832,7 +866,9 @@ func TestNotificationTemplates_Golden(t *testing.T) {
name: "TemplateTemplateDeleted",
id: notifications.TemplateTemplateDeleted,
payload: types.MessagePayload{
UserName: "Bobby",
UserName: "Bobby",
UserEmail: "bobby@coder.com",
UserUsername: "bobby",
Labels: map[string]string{
"name": "bobby-template",
"display_name": "Bobby's Template",
@@ -844,7 +880,9 @@ func TestNotificationTemplates_Golden(t *testing.T) {
name: "TemplateWorkspaceManualBuildFailed",
id: notifications.TemplateWorkspaceManualBuildFailed,
payload: types.MessagePayload{
UserName: "Bobby",
UserName: "Bobby",
UserEmail: "bobby@coder.com",
UserUsername: "bobby",
Labels: map[string]string{
"name": "bobby-workspace",
"template_name": "bobby-template",
@@ -860,7 +898,9 @@ func TestNotificationTemplates_Golden(t *testing.T) {
name: "TemplateWorkspaceBuildsFailedReport",
id: notifications.TemplateWorkspaceBuildsFailedReport,
payload: types.MessagePayload{
UserName: "Bobby",
UserName: "Bobby",
UserEmail: "bobby@coder.com",
UserUsername: "bobby",
Labels: map[string]string{
"template_name": "bobby-first-template",
"template_display_name": "Bobby First Template",
@@ -911,7 +951,9 @@ func TestNotificationTemplates_Golden(t *testing.T) {
name: "TemplateUserRequestedOneTimePasscode",
id: notifications.TemplateUserRequestedOneTimePasscode,
payload: types.MessagePayload{
UserName: "Bobby",
UserName: "Bobby",
UserEmail: "bobby@coder.com",
UserUsername: "bobby",
Labels: map[string]string{
"one_time_passcode": "fad9020b-6562-4cdb-87f1-0486f1bea415",
},
@@ -919,6 +961,7 @@ func TestNotificationTemplates_Golden(t *testing.T) {
},
}
// We must have a test case for every notification_template. This is enforced below:
allTemplates, err := enumerateAllTemplates(t)
require.NoError(t, err)
for _, name := range allTemplates {
@@ -938,53 +981,303 @@ func TestNotificationTemplates_Golden(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
_, _, sql := dbtestutil.NewDBWithSQLDB(t)
t.Run("smtp", func(t *testing.T) {
t.Parallel()
var (
titleTmpl string
bodyTmpl string
)
err := sql.
QueryRow("SELECT title_template, body_template FROM notification_templates WHERE id = $1 LIMIT 1", tc.id).
Scan(&titleTmpl, &bodyTmpl)
require.NoError(t, err, "failed to query body template for template:", tc.id)
// Spin up the DB
db, logger, user := func() (*database.Store, *slog.Logger, *codersdk.User) {
adminClient, _, api := coderdtest.NewWithAPI(t, nil)
db := api.Database
firstUser := coderdtest.CreateFirstUser(t, adminClient)
title, err := render.GoTemplate(titleTmpl, tc.payload, defaultHelpers())
require.NotContainsf(t, title, render.NoValue, "template %q is missing a label value", tc.name)
require.NoError(t, err, "failed to render notification title template")
require.NotEmpty(t, title, "title should not be empty")
_, user := coderdtest.CreateAnotherUserMutators(
t,
adminClient,
firstUser.OrganizationID,
[]rbac.RoleIdentifier{rbac.RoleUserAdmin()},
func(r *codersdk.CreateUserRequestWithOrgs) {
r.Username = tc.payload.UserUsername
r.Email = tc.payload.UserEmail
r.Name = tc.payload.UserName
},
)
return &db, &api.Logger, &user
}()
body, err := render.GoTemplate(bodyTmpl, tc.payload, defaultHelpers())
require.NoError(t, err, "failed to render notification body template")
require.NotEmpty(t, body, "body should not be empty")
// nolint:gocritic // Unit test.
ctx := dbauthz.AsSystemRestricted(testutil.Context(t, testutil.WaitSuperLong))
partialName := strings.Split(t.Name(), "/")[1]
bodyGoldenFile := filepath.Join("testdata", "rendered-templates", partialName+"-body.md.golden")
titleGoldenFile := filepath.Join("testdata", "rendered-templates", partialName+"-title.md.golden")
// smtp config shared between client and server
smtpConfig := codersdk.NotificationsEmailConfig{
Hello: hello,
From: from,
if *updateGoldenFiles {
err = os.MkdirAll(filepath.Dir(bodyGoldenFile), 0o755)
require.NoError(t, err, "want no error creating golden file directory")
err = os.WriteFile(bodyGoldenFile, []byte(body), 0o600)
require.NoError(t, err, "want no error writing body golden file")
err = os.WriteFile(titleGoldenFile, []byte(title), 0o600)
require.NoError(t, err, "want no error writing title golden file")
return
}
Auth: codersdk.NotificationsEmailAuthConfig{
Username: username,
Password: password,
},
}
const hint = "run \"DB=ci make update-golden-files\" and commit the changes"
// Spin up the mock SMTP server
backend := smtptest.NewBackend(smtptest.Config{
AuthMechanisms: []string{sasl.Login},
wantBody, err := os.ReadFile(bodyGoldenFile)
require.NoError(t, err, fmt.Sprintf("missing golden notification body file. %s", hint))
wantTitle, err := os.ReadFile(titleGoldenFile)
require.NoError(t, err, fmt.Sprintf("missing golden notification title file. %s", hint))
AcceptedIdentity: smtpConfig.Auth.Identity.String(),
AcceptedUsername: username,
AcceptedPassword: password,
})
require.Equal(t, string(wantBody), body, fmt.Sprintf("rendered template body does not match golden file. If this is expected, %s", hint))
require.Equal(t, string(wantTitle), title, fmt.Sprintf("rendered template title does not match golden file. If this is expected, %s", hint))
// Create a mock SMTP server which conditionally listens for plain or TLS connections.
srv, listen, err := smtptest.CreateMockSMTPServer(backend, false)
require.NoError(t, err)
t.Cleanup(func() {
err := srv.Shutdown(ctx)
require.NoError(t, err)
})
var hp serpent.HostPort
require.NoError(t, hp.Set(listen.Addr().String()))
smtpConfig.Smarthost = hp
// Start mock SMTP server in the background.
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
assert.NoError(t, srv.Serve(listen))
}()
// Wait for the server to become pingable.
require.Eventually(t, func() bool {
cl, err := smtptest.PingClient(listen, false, smtpConfig.TLS.StartTLS.Value())
if err != nil {
t.Logf("smtp not yet dialable: %s", err)
return false
}
if err = cl.Noop(); err != nil {
t.Logf("smtp not yet noopable: %s", err)
return false
}
if err = cl.Close(); err != nil {
t.Logf("smtp didn't close properly: %s", err)
return false
}
return true
}, testutil.WaitShort, testutil.IntervalFast)
smtpCfg := defaultNotificationsConfig(database.NotificationMethodSmtp)
smtpCfg.SMTP = smtpConfig
smtpManager, err := notifications.NewManager(
smtpCfg,
*db,
defaultHelpers(),
createMetrics(),
logger.Named("manager"),
)
require.NoError(t, err)
smtpManager.Run(ctx)
notificationCfg := defaultNotificationsConfig(database.NotificationMethodSmtp)
smtpEnqueuer, err := notifications.NewStoreEnqueuer(
notificationCfg,
*db,
defaultHelpers(),
logger.Named("enqueuer"),
quartz.NewReal(),
)
require.NoError(t, err)
_, err = smtpEnqueuer.EnqueueWithData(
ctx,
user.ID,
tc.id,
tc.payload.Labels,
tc.payload.Data,
user.Username,
user.ID,
)
require.NoError(t, err)
// Wait for the message to be fetched
var msg *smtptest.Message
require.Eventually(t, func() bool {
msg = backend.LastMessage()
return msg != nil && len(msg.Contents) > 0
}, testutil.WaitShort, testutil.IntervalFast)
body := normalizeGoldenEmail([]byte(msg.Contents))
err = smtpManager.Stop(ctx)
require.NoError(t, err)
partialName := strings.Split(t.Name(), "/")[1]
goldenFile := filepath.Join("testdata", "rendered-templates", "smtp", partialName+".html.golden")
if *updateGoldenFiles {
err = os.MkdirAll(filepath.Dir(goldenFile), 0o755)
require.NoError(t, err, "want no error creating golden file directory")
err = os.WriteFile(goldenFile, body, 0o600)
require.NoError(t, err, "want no error writing body golden file")
return
}
wantBody, err := os.ReadFile(goldenFile)
require.NoError(t, err, fmt.Sprintf("missing golden notification body file. %s", hint))
require.Empty(
t,
cmp.Diff(wantBody, body),
fmt.Sprintf("golden file mismatch: %s. If this is expected, %s. (-want +got). ", goldenFile, hint),
)
})
t.Run("webhook", func(t *testing.T) {
t.Parallel()
// Spin up the DB
db, logger, user := func() (*database.Store, *slog.Logger, *codersdk.User) {
adminClient, _, api := coderdtest.NewWithAPI(t, nil)
db := api.Database
firstUser := coderdtest.CreateFirstUser(t, adminClient)
_, user := coderdtest.CreateAnotherUserMutators(
t,
adminClient,
firstUser.OrganizationID,
[]rbac.RoleIdentifier{rbac.RoleUserAdmin()},
func(r *codersdk.CreateUserRequestWithOrgs) {
r.Username = tc.payload.UserUsername
r.Email = tc.payload.UserEmail
r.Name = tc.payload.UserName
},
)
return &db, &api.Logger, &user
}()
// nolint:gocritic // Unit test.
ctx := dbauthz.AsSystemRestricted(testutil.Context(t, testutil.WaitSuperLong))
// Spin up the mock webhook server
var body []byte
var readErr error
var webhookReceived bool
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
body, readErr = io.ReadAll(r.Body)
webhookReceived = true
}))
t.Cleanup(server.Close)
endpoint, err := url.Parse(server.URL)
require.NoError(t, err)
webhookCfg := defaultNotificationsConfig(database.NotificationMethodWebhook)
webhookCfg.Webhook = codersdk.NotificationsWebhookConfig{
Endpoint: *serpent.URLOf(endpoint),
}
webhookManager, err := notifications.NewManager(
webhookCfg,
*db,
defaultHelpers(),
createMetrics(),
logger.Named("manager"),
)
require.NoError(t, err)
webhookManager.Run(ctx)
httpEnqueuer, err := notifications.NewStoreEnqueuer(
defaultNotificationsConfig(database.NotificationMethodWebhook),
*db,
defaultHelpers(),
logger.Named("enqueuer"),
quartz.NewReal(),
)
require.NoError(t, err)
_, err = httpEnqueuer.EnqueueWithData(
ctx,
user.ID,
tc.id,
tc.payload.Labels,
tc.payload.Data,
user.Username,
user.ID,
)
require.NoError(t, err)
require.Eventually(t, func() bool {
return webhookReceived
}, testutil.WaitShort, testutil.IntervalFast)
require.NoError(t, err)
// Handle the body that was read in the http server here.
// We need to do it here because we can't call require.* in a separate goroutine, such as the http server handler
require.NoError(t, readErr)
var prettyJSON bytes.Buffer
err = json.Indent(&prettyJSON, body, "", " ")
require.NoError(t, err)
content := normalizeGoldenWebhook(prettyJSON.Bytes())
partialName := strings.Split(t.Name(), "/")[1]
goldenFile := filepath.Join("testdata", "rendered-templates", "webhook", partialName+".json.golden")
if *updateGoldenFiles {
err = os.MkdirAll(filepath.Dir(goldenFile), 0o755)
require.NoError(t, err, "want no error creating golden file directory")
err = os.WriteFile(goldenFile, content, 0o600)
require.NoError(t, err, "want no error writing body golden file")
return
}
wantBody, err := os.ReadFile(goldenFile)
require.NoError(t, err, fmt.Sprintf("missing golden notification body file. %s", hint))
require.Equal(t, wantBody, content, fmt.Sprintf("smtp notification does not match golden file. If this is expected, %s", hint))
})
})
}
}
func normalizeGoldenEmail(content []byte) []byte {
const (
constantDate = "Fri, 11 Oct 2024 09:03:06 +0000"
constantMessageID = "02ee4935-73be-4fa1-a290-ff9999026b13@blush-whale-48"
constantBoundary = "bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4"
)
dateRegex := regexp.MustCompile(`Date: .+`)
messageIDRegex := regexp.MustCompile(`Message-Id: .+`)
boundaryRegex := regexp.MustCompile(`boundary=([0-9a-zA-Z]+)`)
submatches := boundaryRegex.FindSubmatch(content)
if len(submatches) == 0 {
return content
}
boundary := submatches[1]
content = dateRegex.ReplaceAll(content, []byte("Date: "+constantDate))
content = messageIDRegex.ReplaceAll(content, []byte("Message-Id: "+constantMessageID))
content = bytes.ReplaceAll(content, boundary, []byte(constantBoundary))
return content
}
func normalizeGoldenWebhook(content []byte) []byte {
const constantUUID = "00000000-0000-0000-0000-000000000000"
uuidRegex := regexp.MustCompile(`[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}`)
content = uuidRegex.ReplaceAll(content, []byte(constantUUID))
return content
}
// TestDisabledBeforeEnqueue ensures that notifications cannot be enqueued once a user has disabled that notification template
func TestDisabledBeforeEnqueue(t *testing.T) {
t.Parallel()
@@ -1,5 +0,0 @@
Hi Bobby,
The template **bobby-template** was deleted by **rob**.
The template's display name was **Bobby's Template**.
@@ -1 +0,0 @@
Template "bobby-template" deleted
@@ -1,5 +0,0 @@
Hi Bobby,
User account **bobby** has been activated.
The newly activated account belongs to **william tables** and was activated by **rob**.
@@ -1 +0,0 @@
User account "bobby" activated
@@ -1,5 +0,0 @@
Hi Bobby,
New user account **bobby** has been created.
This new user account was created for **William Tables** by **rob**.
@@ -1 +0,0 @@
User account "bobby" created
@@ -1,5 +0,0 @@
Hi Bobby,
User account **bobby** has been deleted.
The deleted account belonged to **william tables** and was deleted by **rob**.
@@ -1 +0,0 @@
User account "bobby" deleted
@@ -1,5 +0,0 @@
Hi Bobby,
User account **bobby** has been suspended.
The newly suspended account belongs to **william tables** and was suspended by **rob**.
@@ -1 +0,0 @@
User account "bobby" suspended
@@ -1,7 +0,0 @@
Hi Bobby,
A request to reset the password for your Coder account has been made. Your one-time passcode is:
**fad9020b-6562-4cdb-87f1-0486f1bea415**
If you did not request to reset your password, you can ignore this message.
@@ -1 +0,0 @@
Your One-Time Passcode for Coder.
@@ -1,5 +0,0 @@
Hi Bobby,
Your workspace **bobby-workspace** has been updated automatically to the latest template version (1.0).
Reason for update: **template now includes catnip**.
@@ -1 +0,0 @@
Workspace "bobby-workspace" updated automatically
@@ -1,5 +0,0 @@
Hi Bobby,
Automatic build of your workspace **bobby-workspace** failed.
The specified reason was "**autostart**".
@@ -1 +0,0 @@
Workspace "bobby-workspace" autobuild failed
@@ -1,17 +0,0 @@
Hi Bobby,
Template **Bobby First Template** has failed to build 4/55 times over the last week.
**Report:**
**bobby-template-version-1** failed 3 times:
* [mtojek / workspace-1 / #1234](http://test.com/@mtojek/workspace-1/builds/1234)
* [johndoe / my-workspace-3 / #5678](http://test.com/@johndoe/my-workspace-3/builds/5678)
* [jack / workwork / #774](http://test.com/@jack/workwork/builds/774)
**bobby-template-version-2** failed 1 time:
* [ben / cool-workspace / #8888](http://test.com/@ben/cool-workspace/builds/8888)
We recommend reviewing these issues to ensure future builds are successful.
@@ -1 +0,0 @@
Workspace builds failed for template "Bobby First Template"
@@ -1,5 +0,0 @@
Hi Bobby,
Your workspace **bobby-workspace** was deleted.
The specified reason was "**autodeleted due to dormancy (autobuild)**".
@@ -1 +0,0 @@
Workspace "bobby-workspace" deleted
@@ -1,5 +0,0 @@
Hi Bobby,
Your workspace **bobby-workspace** has been marked as [**dormant**](https://coder.com/docs/templates/schedule#dormancy-threshold-enterprise) because of breached the template's threshold for inactivity.
Dormant workspaces are [automatically deleted](https://coder.com/docs/templates/schedule#dormancy-auto-deletion-enterprise) after 24 hours of inactivity.
To prevent deletion, use your workspace with the link below.
@@ -1 +0,0 @@
Workspace "bobby-workspace" marked as dormant
@@ -1,5 +0,0 @@
Hi Bobby,
A manual build of the workspace **bobby-workspace** using the template **bobby-template** failed (version: **bobby-template-version**).
The template's display name was **William's Template**. The workspace build was initiated by **joe**.
@@ -1 +0,0 @@
Workspace "bobby-workspace" manual build failed
@@ -1,4 +0,0 @@
Hi Bobby,
Your workspace **bobby-workspace** has been marked for **deletion** after 24 hours of [dormancy](https://coder.com/docs/templates/schedule#dormancy-auto-deletion-enterprise) because of template updated to new dormancy policy.
To prevent deletion, use your workspace with the link below.
@@ -1 +0,0 @@
Workspace "bobby-workspace" marked for deletion
@@ -1,3 +0,0 @@
Hi Bobby,
Your account **bobby** has been activated by **rob**.
@@ -1 +0,0 @@
Your account "bobby" has been activated
@@ -1,3 +0,0 @@
Hi Bobby,
Your account **bobby** has been suspended by **rob**.
@@ -1 +0,0 @@
Your account "bobby" has been suspended
@@ -0,0 +1,83 @@
From: system@coder.com
To: bobby@coder.com
Subject: Template "bobby-template" deleted
Message-Id: 02ee4935-73be-4fa1-a290-ff9999026b13@blush-whale-48
Date: Fri, 11 Oct 2024 09:03:06 +0000
Content-Type: multipart/alternative; boundary=bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
MIME-Version: 1.0
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=UTF-8
Hi Bobby,
The template bobby-template was deleted by rob.
The template's display name was Bobby's Template.
View templates: http://test.com/templates
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
Content-Transfer-Encoding: quoted-printable
Content-Type: text/html; charset=UTF-8
<!doctype html>
<html lang=3D"en">
<head>
<meta charset=3D"UTF-8" />
<meta name=3D"viewport" content=3D"width=3Ddevice-width, initial-scale=
=3D1.0" />
<title>Template "bobby-template" deleted</title>
</head>
<body style=3D"margin: 0; padding: 0; font-family: -apple-system, system-=
ui, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarel=
l', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; color: #020617=
; background: #f8fafc;">
<div style=3D"max-width: 600px; margin: 20px auto; padding: 60px; borde=
r: 1px solid #e2e8f0; border-radius: 8px; background-color: #fff; text-alig=
n: left; font-size: 14px; line-height: 1.5;">
<div style=3D"text-align: center;">
<img src=3D"https://coder.com/coder-logo-horizontal.png" alt=3D"Cod=
er Logo" style=3D"height: 40px;" />
</div>
<h1 style=3D"text-align: center; font-size: 24px; font-weight: 400; m=
argin: 8px 0 32px; line-height: 1.5;">
Template "bobby-template" deleted
</h1>
<div style=3D"line-height: 1.5;">
<p>Hi Bobby,</p>
<p>The template <strong>bobby-template</strong> was deleted by <strong>rob<=
/strong>.</p>
<p>The template&rsquo;s display name was <strong>Bobby&rsquo;s Template</st=
rong>.</p>
</div>
<div style=3D"text-align: center; margin-top: 32px;">
=20
<a href=3D"http://test.com/templates" style=3D"display: inline-bloc=
k; padding: 13px 24px; background-color: #020617; color: #f8fafc; text-deco=
ration: none; border-radius: 8px; margin: 0 4px;">
View templates
</a>
=20
</div>
<div style=3D"border-top: 1px solid #e2e8f0; color: #475569; font-siz=
e: 12px; margin-top: 64px; padding-top: 24px; line-height: 1.6;">
<p>&copy;&nbsp;2024&nbsp;Coder. All rights reserved&nbsp;-&nbsp;<a =
href=3D"http://test.com" style=3D"color: #2563eb; text-decoration: none;">h=
ttp://test.com</a></p>
<p><a href=3D"http://test.com/settings/notifications" style=3D"colo=
r: #2563eb; text-decoration: none;">Click here to manage your notification =
settings</a></p>
<p><a href=3D"http://test.com/settings/notifications?disabled=3D29a=
09665-2a4c-403f-9648-54301670e7be" style=3D"color: #2563eb; text-decoration=
: none;">Stop receiving emails like this</a></p>
</div>
</div>
</body>
</html>
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4--
@@ -0,0 +1,84 @@
From: system@coder.com
To: bobby@coder.com
Subject: User account "bobby" activated
Message-Id: 02ee4935-73be-4fa1-a290-ff9999026b13@blush-whale-48
Date: Fri, 11 Oct 2024 09:03:06 +0000
Content-Type: multipart/alternative; boundary=bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
MIME-Version: 1.0
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=UTF-8
Hi Bobby,
User account bobby has been activated.
The newly activated account belongs to William Tables and was activated by =
rob.
View accounts: http://test.com/deployment/users?filter=3Dstatus%3Aactive
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
Content-Transfer-Encoding: quoted-printable
Content-Type: text/html; charset=UTF-8
<!doctype html>
<html lang=3D"en">
<head>
<meta charset=3D"UTF-8" />
<meta name=3D"viewport" content=3D"width=3Ddevice-width, initial-scale=
=3D1.0" />
<title>User account "bobby" activated</title>
</head>
<body style=3D"margin: 0; padding: 0; font-family: -apple-system, system-=
ui, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarel=
l', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; color: #020617=
; background: #f8fafc;">
<div style=3D"max-width: 600px; margin: 20px auto; padding: 60px; borde=
r: 1px solid #e2e8f0; border-radius: 8px; background-color: #fff; text-alig=
n: left; font-size: 14px; line-height: 1.5;">
<div style=3D"text-align: center;">
<img src=3D"https://coder.com/coder-logo-horizontal.png" alt=3D"Cod=
er Logo" style=3D"height: 40px;" />
</div>
<h1 style=3D"text-align: center; font-size: 24px; font-weight: 400; m=
argin: 8px 0 32px; line-height: 1.5;">
User account "bobby" activated
</h1>
<div style=3D"line-height: 1.5;">
<p>Hi Bobby,</p>
<p>User account <strong>bobby</strong> has been activated.</p>
<p>The newly activated account belongs to <strong>William Tables</strong> a=
nd was activated by <strong>rob</strong>.</p>
</div>
<div style=3D"text-align: center; margin-top: 32px;">
=20
<a href=3D"http://test.com/deployment/users?filter=3Dstatus%3Aactiv=
e" style=3D"display: inline-block; padding: 13px 24px; background-color: #0=
20617; color: #f8fafc; text-decoration: none; border-radius: 8px; margin: 0=
4px;">
View accounts
</a>
=20
</div>
<div style=3D"border-top: 1px solid #e2e8f0; color: #475569; font-siz=
e: 12px; margin-top: 64px; padding-top: 24px; line-height: 1.6;">
<p>&copy;&nbsp;2024&nbsp;Coder. All rights reserved&nbsp;-&nbsp;<a =
href=3D"http://test.com" style=3D"color: #2563eb; text-decoration: none;">h=
ttp://test.com</a></p>
<p><a href=3D"http://test.com/settings/notifications" style=3D"colo=
r: #2563eb; text-decoration: none;">Click here to manage your notification =
settings</a></p>
<p><a href=3D"http://test.com/settings/notifications?disabled=3D9f5=
af851-8408-4e73-a7a1-c6502ba46689" style=3D"color: #2563eb; text-decoration=
: none;">Stop receiving emails like this</a></p>
</div>
</div>
</body>
</html>
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4--
@@ -0,0 +1,83 @@
From: system@coder.com
To: bobby@coder.com
Subject: User account "bobby" created
Message-Id: 02ee4935-73be-4fa1-a290-ff9999026b13@blush-whale-48
Date: Fri, 11 Oct 2024 09:03:06 +0000
Content-Type: multipart/alternative; boundary=bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
MIME-Version: 1.0
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=UTF-8
Hi Bobby,
New user account bobby has been created.
This new user account was created for William Tables by rob.
View accounts: http://test.com/deployment/users?filter=3Dstatus%3Aactive
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
Content-Transfer-Encoding: quoted-printable
Content-Type: text/html; charset=UTF-8
<!doctype html>
<html lang=3D"en">
<head>
<meta charset=3D"UTF-8" />
<meta name=3D"viewport" content=3D"width=3Ddevice-width, initial-scale=
=3D1.0" />
<title>User account "bobby" created</title>
</head>
<body style=3D"margin: 0; padding: 0; font-family: -apple-system, system-=
ui, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarel=
l', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; color: #020617=
; background: #f8fafc;">
<div style=3D"max-width: 600px; margin: 20px auto; padding: 60px; borde=
r: 1px solid #e2e8f0; border-radius: 8px; background-color: #fff; text-alig=
n: left; font-size: 14px; line-height: 1.5;">
<div style=3D"text-align: center;">
<img src=3D"https://coder.com/coder-logo-horizontal.png" alt=3D"Cod=
er Logo" style=3D"height: 40px;" />
</div>
<h1 style=3D"text-align: center; font-size: 24px; font-weight: 400; m=
argin: 8px 0 32px; line-height: 1.5;">
User account "bobby" created
</h1>
<div style=3D"line-height: 1.5;">
<p>Hi Bobby,</p>
<p>New user account <strong>bobby</strong> has been created.</p>
<p>This new user account was created for <strong>William Tables</strong> by=
<strong>rob</strong>.</p>
</div>
<div style=3D"text-align: center; margin-top: 32px;">
=20
<a href=3D"http://test.com/deployment/users?filter=3Dstatus%3Aactiv=
e" style=3D"display: inline-block; padding: 13px 24px; background-color: #0=
20617; color: #f8fafc; text-decoration: none; border-radius: 8px; margin: 0=
4px;">
View accounts
</a>
=20
</div>
<div style=3D"border-top: 1px solid #e2e8f0; color: #475569; font-siz=
e: 12px; margin-top: 64px; padding-top: 24px; line-height: 1.6;">
<p>&copy;&nbsp;2024&nbsp;Coder. All rights reserved&nbsp;-&nbsp;<a =
href=3D"http://test.com" style=3D"color: #2563eb; text-decoration: none;">h=
ttp://test.com</a></p>
<p><a href=3D"http://test.com/settings/notifications" style=3D"colo=
r: #2563eb; text-decoration: none;">Click here to manage your notification =
settings</a></p>
<p><a href=3D"http://test.com/settings/notifications?disabled=3D4e1=
9c0ac-94e1-4532-9515-d1801aa283b2" style=3D"color: #2563eb; text-decoration=
: none;">Stop receiving emails like this</a></p>
</div>
</div>
</body>
</html>
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4--
@@ -0,0 +1,83 @@
From: system@coder.com
To: bobby@coder.com
Subject: User account "bobby" deleted
Message-Id: 02ee4935-73be-4fa1-a290-ff9999026b13@blush-whale-48
Date: Fri, 11 Oct 2024 09:03:06 +0000
Content-Type: multipart/alternative; boundary=bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
MIME-Version: 1.0
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=UTF-8
Hi Bobby,
User account bobby has been deleted.
The deleted account belonged to William Tables and was deleted by rob.
View accounts: http://test.com/deployment/users?filter=3Dstatus%3Aactive
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
Content-Transfer-Encoding: quoted-printable
Content-Type: text/html; charset=UTF-8
<!doctype html>
<html lang=3D"en">
<head>
<meta charset=3D"UTF-8" />
<meta name=3D"viewport" content=3D"width=3Ddevice-width, initial-scale=
=3D1.0" />
<title>User account "bobby" deleted</title>
</head>
<body style=3D"margin: 0; padding: 0; font-family: -apple-system, system-=
ui, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarel=
l', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; color: #020617=
; background: #f8fafc;">
<div style=3D"max-width: 600px; margin: 20px auto; padding: 60px; borde=
r: 1px solid #e2e8f0; border-radius: 8px; background-color: #fff; text-alig=
n: left; font-size: 14px; line-height: 1.5;">
<div style=3D"text-align: center;">
<img src=3D"https://coder.com/coder-logo-horizontal.png" alt=3D"Cod=
er Logo" style=3D"height: 40px;" />
</div>
<h1 style=3D"text-align: center; font-size: 24px; font-weight: 400; m=
argin: 8px 0 32px; line-height: 1.5;">
User account "bobby" deleted
</h1>
<div style=3D"line-height: 1.5;">
<p>Hi Bobby,</p>
<p>User account <strong>bobby</strong> has been deleted.</p>
<p>The deleted account belonged to <strong>William Tables</strong> and was =
deleted by <strong>rob</strong>.</p>
</div>
<div style=3D"text-align: center; margin-top: 32px;">
=20
<a href=3D"http://test.com/deployment/users?filter=3Dstatus%3Aactiv=
e" style=3D"display: inline-block; padding: 13px 24px; background-color: #0=
20617; color: #f8fafc; text-decoration: none; border-radius: 8px; margin: 0=
4px;">
View accounts
</a>
=20
</div>
<div style=3D"border-top: 1px solid #e2e8f0; color: #475569; font-siz=
e: 12px; margin-top: 64px; padding-top: 24px; line-height: 1.6;">
<p>&copy;&nbsp;2024&nbsp;Coder. All rights reserved&nbsp;-&nbsp;<a =
href=3D"http://test.com" style=3D"color: #2563eb; text-decoration: none;">h=
ttp://test.com</a></p>
<p><a href=3D"http://test.com/settings/notifications" style=3D"colo=
r: #2563eb; text-decoration: none;">Click here to manage your notification =
settings</a></p>
<p><a href=3D"http://test.com/settings/notifications?disabled=3Df44=
d9314-ad03-4bc8-95d0-5cad491da6b6" style=3D"color: #2563eb; text-decoration=
: none;">Stop receiving emails like this</a></p>
</div>
</div>
</body>
</html>
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4--
@@ -0,0 +1,85 @@
From: system@coder.com
To: bobby@coder.com
Subject: User account "bobby" suspended
Message-Id: 02ee4935-73be-4fa1-a290-ff9999026b13@blush-whale-48
Date: Fri, 11 Oct 2024 09:03:06 +0000
Content-Type: multipart/alternative; boundary=bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
MIME-Version: 1.0
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=UTF-8
Hi Bobby,
User account bobby has been suspended.
The newly suspended account belongs to William Tables and was suspended by =
rob.
View suspended accounts: http://test.com/deployment/users?filter=3Dstatus%3=
Asuspended
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
Content-Transfer-Encoding: quoted-printable
Content-Type: text/html; charset=UTF-8
<!doctype html>
<html lang=3D"en">
<head>
<meta charset=3D"UTF-8" />
<meta name=3D"viewport" content=3D"width=3Ddevice-width, initial-scale=
=3D1.0" />
<title>User account "bobby" suspended</title>
</head>
<body style=3D"margin: 0; padding: 0; font-family: -apple-system, system-=
ui, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarel=
l', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; color: #020617=
; background: #f8fafc;">
<div style=3D"max-width: 600px; margin: 20px auto; padding: 60px; borde=
r: 1px solid #e2e8f0; border-radius: 8px; background-color: #fff; text-alig=
n: left; font-size: 14px; line-height: 1.5;">
<div style=3D"text-align: center;">
<img src=3D"https://coder.com/coder-logo-horizontal.png" alt=3D"Cod=
er Logo" style=3D"height: 40px;" />
</div>
<h1 style=3D"text-align: center; font-size: 24px; font-weight: 400; m=
argin: 8px 0 32px; line-height: 1.5;">
User account "bobby" suspended
</h1>
<div style=3D"line-height: 1.5;">
<p>Hi Bobby,</p>
<p>User account <strong>bobby</strong> has been suspended.</p>
<p>The newly suspended account belongs to <strong>William Tables</strong> a=
nd was suspended by <strong>rob</strong>.</p>
</div>
<div style=3D"text-align: center; margin-top: 32px;">
=20
<a href=3D"http://test.com/deployment/users?filter=3Dstatus%3Asuspe=
nded" style=3D"display: inline-block; padding: 13px 24px; background-color:=
#020617; color: #f8fafc; text-decoration: none; border-radius: 8px; margin=
: 0 4px;">
View suspended accounts
</a>
=20
</div>
<div style=3D"border-top: 1px solid #e2e8f0; color: #475569; font-siz=
e: 12px; margin-top: 64px; padding-top: 24px; line-height: 1.6;">
<p>&copy;&nbsp;2024&nbsp;Coder. All rights reserved&nbsp;-&nbsp;<a =
href=3D"http://test.com" style=3D"color: #2563eb; text-decoration: none;">h=
ttp://test.com</a></p>
<p><a href=3D"http://test.com/settings/notifications" style=3D"colo=
r: #2563eb; text-decoration: none;">Click here to manage your notification =
settings</a></p>
<p><a href=3D"http://test.com/settings/notifications?disabled=3Db02=
ddd82-4733-4d02-a2d7-c36f3598997d" style=3D"color: #2563eb; text-decoration=
: none;">Stop receiving emails like this</a></p>
</div>
</div>
</body>
</html>
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4--
@@ -0,0 +1,80 @@
From: system@coder.com
To: bobby@coder.com
Subject: Your One-Time Passcode for Coder.
Message-Id: 02ee4935-73be-4fa1-a290-ff9999026b13@blush-whale-48
Date: Fri, 11 Oct 2024 09:03:06 +0000
Content-Type: multipart/alternative; boundary=bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
MIME-Version: 1.0
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=UTF-8
Hi Bobby,
A request to reset the password for your Coder account has been made. Your =
one-time passcode is:
fad9020b-6562-4cdb-87f1-0486f1bea415
If you did not request to reset your password, you can ignore this message.
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
Content-Transfer-Encoding: quoted-printable
Content-Type: text/html; charset=UTF-8
<!doctype html>
<html lang=3D"en">
<head>
<meta charset=3D"UTF-8" />
<meta name=3D"viewport" content=3D"width=3Ddevice-width, initial-scale=
=3D1.0" />
<title>Your One-Time Passcode for Coder.</title>
</head>
<body style=3D"margin: 0; padding: 0; font-family: -apple-system, system-=
ui, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarel=
l', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; color: #020617=
; background: #f8fafc;">
<div style=3D"max-width: 600px; margin: 20px auto; padding: 60px; borde=
r: 1px solid #e2e8f0; border-radius: 8px; background-color: #fff; text-alig=
n: left; font-size: 14px; line-height: 1.5;">
<div style=3D"text-align: center;">
<img src=3D"https://coder.com/coder-logo-horizontal.png" alt=3D"Cod=
er Logo" style=3D"height: 40px;" />
</div>
<h1 style=3D"text-align: center; font-size: 24px; font-weight: 400; m=
argin: 8px 0 32px; line-height: 1.5;">
Your One-Time Passcode for Coder.
</h1>
<div style=3D"line-height: 1.5;">
<p>Hi Bobby,</p>
<p>A request to reset the password for your Coder account has been made. Yo=
ur one-time passcode is:</p>
<p><strong>fad9020b-6562-4cdb-87f1-0486f1bea415</strong></p>
<p>If you did not request to reset your password, you can ignore this messa=
ge.</p>
</div>
<div style=3D"text-align: center; margin-top: 32px;">
=20
</div>
<div style=3D"border-top: 1px solid #e2e8f0; color: #475569; font-siz=
e: 12px; margin-top: 64px; padding-top: 24px; line-height: 1.6;">
<p>&copy;&nbsp;2024&nbsp;Coder. All rights reserved&nbsp;-&nbsp;<a =
href=3D"http://test.com" style=3D"color: #2563eb; text-decoration: none;">h=
ttp://test.com</a></p>
<p><a href=3D"http://test.com/settings/notifications" style=3D"colo=
r: #2563eb; text-decoration: none;">Click here to manage your notification =
settings</a></p>
<p><a href=3D"http://test.com/settings/notifications?disabled=3D62f=
86a30-2330-4b61-a26d-311ff3b608cf" style=3D"color: #2563eb; text-decoration=
: none;">Stop receiving emails like this</a></p>
</div>
</div>
</body>
</html>
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4--
@@ -0,0 +1,83 @@
From: system@coder.com
To: bobby@coder.com
Subject: Workspace "bobby-workspace" updated automatically
Message-Id: 02ee4935-73be-4fa1-a290-ff9999026b13@blush-whale-48
Date: Fri, 11 Oct 2024 09:03:06 +0000
Content-Type: multipart/alternative; boundary=bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
MIME-Version: 1.0
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=UTF-8
Hi Bobby,
Your workspace bobby-workspace has been updated automatically to the latest=
template version (1.0).
Reason for update: template now includes catnip.
View workspace: http://test.com/@bobby/bobby-workspace
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
Content-Transfer-Encoding: quoted-printable
Content-Type: text/html; charset=UTF-8
<!doctype html>
<html lang=3D"en">
<head>
<meta charset=3D"UTF-8" />
<meta name=3D"viewport" content=3D"width=3Ddevice-width, initial-scale=
=3D1.0" />
<title>Workspace "bobby-workspace" updated automatically</title>
</head>
<body style=3D"margin: 0; padding: 0; font-family: -apple-system, system-=
ui, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarel=
l', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; color: #020617=
; background: #f8fafc;">
<div style=3D"max-width: 600px; margin: 20px auto; padding: 60px; borde=
r: 1px solid #e2e8f0; border-radius: 8px; background-color: #fff; text-alig=
n: left; font-size: 14px; line-height: 1.5;">
<div style=3D"text-align: center;">
<img src=3D"https://coder.com/coder-logo-horizontal.png" alt=3D"Cod=
er Logo" style=3D"height: 40px;" />
</div>
<h1 style=3D"text-align: center; font-size: 24px; font-weight: 400; m=
argin: 8px 0 32px; line-height: 1.5;">
Workspace "bobby-workspace" updated automatically
</h1>
<div style=3D"line-height: 1.5;">
<p>Hi Bobby,</p>
<p>Your workspace <strong>bobby-workspace</strong> has been updated automat=
ically to the latest template version (1.0).</p>
<p>Reason for update: <strong>template now includes catnip</strong>.</p>
</div>
<div style=3D"text-align: center; margin-top: 32px;">
=20
<a href=3D"http://test.com/@bobby/bobby-workspace" style=3D"display=
: inline-block; padding: 13px 24px; background-color: #020617; color: #f8fa=
fc; text-decoration: none; border-radius: 8px; margin: 0 4px;">
View workspace
</a>
=20
</div>
<div style=3D"border-top: 1px solid #e2e8f0; color: #475569; font-siz=
e: 12px; margin-top: 64px; padding-top: 24px; line-height: 1.6;">
<p>&copy;&nbsp;2024&nbsp;Coder. All rights reserved&nbsp;-&nbsp;<a =
href=3D"http://test.com" style=3D"color: #2563eb; text-decoration: none;">h=
ttp://test.com</a></p>
<p><a href=3D"http://test.com/settings/notifications" style=3D"colo=
r: #2563eb; text-decoration: none;">Click here to manage your notification =
settings</a></p>
<p><a href=3D"http://test.com/settings/notifications?disabled=3Dc34=
a0c09-0704-4cac-bd1c-0c0146811c2b" style=3D"color: #2563eb; text-decoration=
: none;">Stop receiving emails like this</a></p>
</div>
</div>
</body>
</html>
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4--
@@ -0,0 +1,82 @@
From: system@coder.com
To: bobby@coder.com
Subject: Workspace "bobby-workspace" autobuild failed
Message-Id: 02ee4935-73be-4fa1-a290-ff9999026b13@blush-whale-48
Date: Fri, 11 Oct 2024 09:03:06 +0000
Content-Type: multipart/alternative; boundary=bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
MIME-Version: 1.0
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=UTF-8
Hi Bobby,
Automatic build of your workspace bobby-workspace failed.
The specified reason was "autostart".
View workspace: http://test.com/@bobby/bobby-workspace
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
Content-Transfer-Encoding: quoted-printable
Content-Type: text/html; charset=UTF-8
<!doctype html>
<html lang=3D"en">
<head>
<meta charset=3D"UTF-8" />
<meta name=3D"viewport" content=3D"width=3Ddevice-width, initial-scale=
=3D1.0" />
<title>Workspace "bobby-workspace" autobuild failed</title>
</head>
<body style=3D"margin: 0; padding: 0; font-family: -apple-system, system-=
ui, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarel=
l', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; color: #020617=
; background: #f8fafc;">
<div style=3D"max-width: 600px; margin: 20px auto; padding: 60px; borde=
r: 1px solid #e2e8f0; border-radius: 8px; background-color: #fff; text-alig=
n: left; font-size: 14px; line-height: 1.5;">
<div style=3D"text-align: center;">
<img src=3D"https://coder.com/coder-logo-horizontal.png" alt=3D"Cod=
er Logo" style=3D"height: 40px;" />
</div>
<h1 style=3D"text-align: center; font-size: 24px; font-weight: 400; m=
argin: 8px 0 32px; line-height: 1.5;">
Workspace "bobby-workspace" autobuild failed
</h1>
<div style=3D"line-height: 1.5;">
<p>Hi Bobby,</p>
<p>Automatic build of your workspace <strong>bobby-workspace</strong> faile=
d.</p>
<p>The specified reason was &ldquo;<strong>autostart</strong>&rdquo;.</p>
</div>
<div style=3D"text-align: center; margin-top: 32px;">
=20
<a href=3D"http://test.com/@bobby/bobby-workspace" style=3D"display=
: inline-block; padding: 13px 24px; background-color: #020617; color: #f8fa=
fc; text-decoration: none; border-radius: 8px; margin: 0 4px;">
View workspace
</a>
=20
</div>
<div style=3D"border-top: 1px solid #e2e8f0; color: #475569; font-siz=
e: 12px; margin-top: 64px; padding-top: 24px; line-height: 1.6;">
<p>&copy;&nbsp;2024&nbsp;Coder. All rights reserved&nbsp;-&nbsp;<a =
href=3D"http://test.com" style=3D"color: #2563eb; text-decoration: none;">h=
ttp://test.com</a></p>
<p><a href=3D"http://test.com/settings/notifications" style=3D"colo=
r: #2563eb; text-decoration: none;">Click here to manage your notification =
settings</a></p>
<p><a href=3D"http://test.com/settings/notifications?disabled=3D381=
df2a9-c0c0-4749-420f-80a9280c66f9" style=3D"color: #2563eb; text-decoration=
: none;">Stop receiving emails like this</a></p>
</div>
</div>
</body>
</html>
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4--
@@ -0,0 +1,126 @@
From: system@coder.com
To: bobby@coder.com
Subject: Workspace builds failed for template "Bobby First Template"
Message-Id: 02ee4935-73be-4fa1-a290-ff9999026b13@blush-whale-48
Date: Fri, 11 Oct 2024 09:03:06 +0000
Content-Type: multipart/alternative; boundary=bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
MIME-Version: 1.0
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=UTF-8
Hi Bobby,
Template Bobby First Template has failed to build 4/55 times over the last =
week.
Report:
bobby-template-version-1 failed 3 times:
mtojek / workspace-1 / #1234 (http://test.com/@mtojek/workspace-1/builds/12=
34)
johndoe / my-workspace-3 / #5678 (http://test.com/@johndoe/my-workspace-3/b=
uilds/5678)
jack / workwork / #774 (http://test.com/@jack/workwork/builds/774)
bobby-template-version-2 failed 1 time:
ben / cool-workspace / #8888 (http://test.com/@ben/cool-workspace/builds/88=
88)
We recommend reviewing these issues to ensure future builds are successful.
View workspaces: http://test.com/workspaces?filter=3Dtemplate%3Abobby-first=
-template
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
Content-Transfer-Encoding: quoted-printable
Content-Type: text/html; charset=UTF-8
<!doctype html>
<html lang=3D"en">
<head>
<meta charset=3D"UTF-8" />
<meta name=3D"viewport" content=3D"width=3Ddevice-width, initial-scale=
=3D1.0" />
<title>Workspace builds failed for template "Bobby First Template"</tit=
le>
</head>
<body style=3D"margin: 0; padding: 0; font-family: -apple-system, system-=
ui, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarel=
l', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; color: #020617=
; background: #f8fafc;">
<div style=3D"max-width: 600px; margin: 20px auto; padding: 60px; borde=
r: 1px solid #e2e8f0; border-radius: 8px; background-color: #fff; text-alig=
n: left; font-size: 14px; line-height: 1.5;">
<div style=3D"text-align: center;">
<img src=3D"https://coder.com/coder-logo-horizontal.png" alt=3D"Cod=
er Logo" style=3D"height: 40px;" />
</div>
<h1 style=3D"text-align: center; font-size: 24px; font-weight: 400; m=
argin: 8px 0 32px; line-height: 1.5;">
Workspace builds failed for template "Bobby First Template"
</h1>
<div style=3D"line-height: 1.5;">
<p>Hi Bobby,</p>
<p>Template <strong>Bobby First Template</strong> has failed to build <sup>=
4</sup>&frasl;<sub>55</sub> times over the last week.</p>
<p><strong>Report:</strong></p>
<p><strong>bobby-template-version-1</strong> failed 3 times:</p>
<ul>
<li><a href=3D"http://test.com/@mtojek/workspace-1/builds/1234">mtojek / wo=
rkspace-1 / #1234</a><br>
</li>
<li><a href=3D"http://test.com/@johndoe/my-workspace-3/builds/5678">johndoe=
/ my-workspace-3 / #5678</a><br>
</li>
<li><a href=3D"http://test.com/@jack/workwork/builds/774">jack / workwork /=
#774</a><br>
</li>
</ul>
<p><strong>bobby-template-version-2</strong> failed 1 time:</p>
<ul>
<li><a href=3D"http://test.com/@ben/cool-workspace/builds/8888">ben / cool-=
workspace / #8888</a><br>
</li>
</ul>
<p>We recommend reviewing these issues to ensure future builds are successf=
ul.</p>
</div>
<div style=3D"text-align: center; margin-top: 32px;">
=20
<a href=3D"http://test.com/workspaces?filter=3Dtemplate%3Abobby-fir=
st-template" style=3D"display: inline-block; padding: 13px 24px; background=
-color: #020617; color: #f8fafc; text-decoration: none; border-radius: 8px;=
margin: 0 4px;">
View workspaces
</a>
=20
</div>
<div style=3D"border-top: 1px solid #e2e8f0; color: #475569; font-siz=
e: 12px; margin-top: 64px; padding-top: 24px; line-height: 1.6;">
<p>&copy;&nbsp;2024&nbsp;Coder. All rights reserved&nbsp;-&nbsp;<a =
href=3D"http://test.com" style=3D"color: #2563eb; text-decoration: none;">h=
ttp://test.com</a></p>
<p><a href=3D"http://test.com/settings/notifications" style=3D"colo=
r: #2563eb; text-decoration: none;">Click here to manage your notification =
settings</a></p>
<p><a href=3D"http://test.com/settings/notifications?disabled=3D34a=
20db2-e9cc-4a93-b0e4-8569699d7a00" style=3D"color: #2563eb; text-decoration=
: none;">Stop receiving emails like this</a></p>
</div>
</div>
</body>
</html>
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4--
@@ -0,0 +1,90 @@
From: system@coder.com
To: bobby@coder.com
Subject: Workspace "bobby-workspace" deleted
Message-Id: 02ee4935-73be-4fa1-a290-ff9999026b13@blush-whale-48
Date: Fri, 11 Oct 2024 09:03:06 +0000
Content-Type: multipart/alternative; boundary=bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
MIME-Version: 1.0
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=UTF-8
Hi Bobby,
Your workspace bobby-workspace was deleted.
The specified reason was "autodeleted due to dormancy (autobuild)".
View workspaces: http://test.com/workspaces
View templates: http://test.com/templates
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
Content-Transfer-Encoding: quoted-printable
Content-Type: text/html; charset=UTF-8
<!doctype html>
<html lang=3D"en">
<head>
<meta charset=3D"UTF-8" />
<meta name=3D"viewport" content=3D"width=3Ddevice-width, initial-scale=
=3D1.0" />
<title>Workspace "bobby-workspace" deleted</title>
</head>
<body style=3D"margin: 0; padding: 0; font-family: -apple-system, system-=
ui, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarel=
l', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; color: #020617=
; background: #f8fafc;">
<div style=3D"max-width: 600px; margin: 20px auto; padding: 60px; borde=
r: 1px solid #e2e8f0; border-radius: 8px; background-color: #fff; text-alig=
n: left; font-size: 14px; line-height: 1.5;">
<div style=3D"text-align: center;">
<img src=3D"https://coder.com/coder-logo-horizontal.png" alt=3D"Cod=
er Logo" style=3D"height: 40px;" />
</div>
<h1 style=3D"text-align: center; font-size: 24px; font-weight: 400; m=
argin: 8px 0 32px; line-height: 1.5;">
Workspace "bobby-workspace" deleted
</h1>
<div style=3D"line-height: 1.5;">
<p>Hi Bobby,</p>
<p>Your workspace <strong>bobby-workspace</strong> was deleted.</p>
<p>The specified reason was &ldquo;<strong>autodeleted due to dormancy (aut=
obuild)</strong>&rdquo;.</p>
</div>
<div style=3D"text-align: center; margin-top: 32px;">
=20
<a href=3D"http://test.com/workspaces" style=3D"display: inline-blo=
ck; padding: 13px 24px; background-color: #020617; color: #f8fafc; text-dec=
oration: none; border-radius: 8px; margin: 0 4px;">
View workspaces
</a>
=20
<a href=3D"http://test.com/templates" style=3D"display: inline-bloc=
k; padding: 13px 24px; background-color: #020617; color: #f8fafc; text-deco=
ration: none; border-radius: 8px; margin: 0 4px;">
View templates
</a>
=20
</div>
<div style=3D"border-top: 1px solid #e2e8f0; color: #475569; font-siz=
e: 12px; margin-top: 64px; padding-top: 24px; line-height: 1.6;">
<p>&copy;&nbsp;2024&nbsp;Coder. All rights reserved&nbsp;-&nbsp;<a =
href=3D"http://test.com" style=3D"color: #2563eb; text-decoration: none;">h=
ttp://test.com</a></p>
<p><a href=3D"http://test.com/settings/notifications" style=3D"colo=
r: #2563eb; text-decoration: none;">Click here to manage your notification =
settings</a></p>
<p><a href=3D"http://test.com/settings/notifications?disabled=3Df51=
7da0b-cdc9-410f-ab89-a86107c420ed" style=3D"color: #2563eb; text-decoration=
: none;">Stop receiving emails like this</a></p>
</div>
</div>
</body>
</html>
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4--
@@ -0,0 +1,90 @@
From: system@coder.com
To: bobby@coder.com
Subject: Workspace "bobby-workspace" marked as dormant
Message-Id: 02ee4935-73be-4fa1-a290-ff9999026b13@blush-whale-48
Date: Fri, 11 Oct 2024 09:03:06 +0000
Content-Type: multipart/alternative; boundary=bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
MIME-Version: 1.0
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=UTF-8
Hi Bobby,
Your workspace bobby-workspace has been marked as dormant (https://coder.co=
m/docs/templates/schedule#dormancy-threshold-enterprise) because of breache=
d the template's threshold for inactivity.
Dormant workspaces are automatically deleted (https://coder.com/docs/templa=
tes/schedule#dormancy-auto-deletion-enterprise) after 24 hours of inactivit=
y.
To prevent deletion, use your workspace with the link below.
View workspace: http://test.com/@bobby/bobby-workspace
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
Content-Transfer-Encoding: quoted-printable
Content-Type: text/html; charset=UTF-8
<!doctype html>
<html lang=3D"en">
<head>
<meta charset=3D"UTF-8" />
<meta name=3D"viewport" content=3D"width=3Ddevice-width, initial-scale=
=3D1.0" />
<title>Workspace "bobby-workspace" marked as dormant</title>
</head>
<body style=3D"margin: 0; padding: 0; font-family: -apple-system, system-=
ui, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarel=
l', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; color: #020617=
; background: #f8fafc;">
<div style=3D"max-width: 600px; margin: 20px auto; padding: 60px; borde=
r: 1px solid #e2e8f0; border-radius: 8px; background-color: #fff; text-alig=
n: left; font-size: 14px; line-height: 1.5;">
<div style=3D"text-align: center;">
<img src=3D"https://coder.com/coder-logo-horizontal.png" alt=3D"Cod=
er Logo" style=3D"height: 40px;" />
</div>
<h1 style=3D"text-align: center; font-size: 24px; font-weight: 400; m=
argin: 8px 0 32px; line-height: 1.5;">
Workspace "bobby-workspace" marked as dormant
</h1>
<div style=3D"line-height: 1.5;">
<p>Hi Bobby,</p>
<p>Your workspace <strong>bobby-workspace</strong> has been marked as <a hr=
ef=3D"https://coder.com/docs/templates/schedule#dormancy-threshold-enterpri=
se"><strong>dormant</strong></a> because of breached the template&rsquo;s t=
hreshold for inactivity.<br>
Dormant workspaces are <a href=3D"https://coder.com/docs/templates/schedule=
#dormancy-auto-deletion-enterprise">automatically deleted</a> after 24 hour=
s of inactivity.<br>
To prevent deletion, use your workspace with the link below.</p>
</div>
<div style=3D"text-align: center; margin-top: 32px;">
=20
<a href=3D"http://test.com/@bobby/bobby-workspace" style=3D"display=
: inline-block; padding: 13px 24px; background-color: #020617; color: #f8fa=
fc; text-decoration: none; border-radius: 8px; margin: 0 4px;">
View workspace
</a>
=20
</div>
<div style=3D"border-top: 1px solid #e2e8f0; color: #475569; font-siz=
e: 12px; margin-top: 64px; padding-top: 24px; line-height: 1.6;">
<p>&copy;&nbsp;2024&nbsp;Coder. All rights reserved&nbsp;-&nbsp;<a =
href=3D"http://test.com" style=3D"color: #2563eb; text-decoration: none;">h=
ttp://test.com</a></p>
<p><a href=3D"http://test.com/settings/notifications" style=3D"colo=
r: #2563eb; text-decoration: none;">Click here to manage your notification =
settings</a></p>
<p><a href=3D"http://test.com/settings/notifications?disabled=3D0ea=
69165-ec14-4314-91f1-69566ac3c5a0" style=3D"color: #2563eb; text-decoration=
: none;">Stop receiving emails like this</a></p>
</div>
</div>
</body>
</html>
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4--
@@ -0,0 +1,86 @@
From: system@coder.com
To: bobby@coder.com
Subject: Workspace "bobby-workspace" manual build failed
Message-Id: 02ee4935-73be-4fa1-a290-ff9999026b13@blush-whale-48
Date: Fri, 11 Oct 2024 09:03:06 +0000
Content-Type: multipart/alternative; boundary=bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
MIME-Version: 1.0
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=UTF-8
Hi Bobby,
A manual build of the workspace bobby-workspace using the template bobby-te=
mplate failed (version: bobby-template-version).
The template's display name was William's Template. The workspace build was=
initiated by joe.
View build: http://test.com/@mrbobby/bobby-workspace/builds/3
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
Content-Transfer-Encoding: quoted-printable
Content-Type: text/html; charset=UTF-8
<!doctype html>
<html lang=3D"en">
<head>
<meta charset=3D"UTF-8" />
<meta name=3D"viewport" content=3D"width=3Ddevice-width, initial-scale=
=3D1.0" />
<title>Workspace "bobby-workspace" manual build failed</title>
</head>
<body style=3D"margin: 0; padding: 0; font-family: -apple-system, system-=
ui, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarel=
l', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; color: #020617=
; background: #f8fafc;">
<div style=3D"max-width: 600px; margin: 20px auto; padding: 60px; borde=
r: 1px solid #e2e8f0; border-radius: 8px; background-color: #fff; text-alig=
n: left; font-size: 14px; line-height: 1.5;">
<div style=3D"text-align: center;">
<img src=3D"https://coder.com/coder-logo-horizontal.png" alt=3D"Cod=
er Logo" style=3D"height: 40px;" />
</div>
<h1 style=3D"text-align: center; font-size: 24px; font-weight: 400; m=
argin: 8px 0 32px; line-height: 1.5;">
Workspace "bobby-workspace" manual build failed
</h1>
<div style=3D"line-height: 1.5;">
<p>Hi Bobby,</p>
<p>A manual build of the workspace <strong>bobby-workspace</strong> using t=
he template <strong>bobby-template</strong> failed (version: <strong>bobby-=
template-version</strong>).</p>
<p>The template&rsquo;s display name was <strong>William&rsquo;s Template</=
strong>. The workspace build was initiated by <strong>joe</strong>.</p>
</div>
<div style=3D"text-align: center; margin-top: 32px;">
=20
<a href=3D"http://test.com/@mrbobby/bobby-workspace/builds/3" style=
=3D"display: inline-block; padding: 13px 24px; background-color: #020617; c=
olor: #f8fafc; text-decoration: none; border-radius: 8px; margin: 0 4px;">
View build
</a>
=20
</div>
<div style=3D"border-top: 1px solid #e2e8f0; color: #475569; font-siz=
e: 12px; margin-top: 64px; padding-top: 24px; line-height: 1.6;">
<p>&copy;&nbsp;2024&nbsp;Coder. All rights reserved&nbsp;-&nbsp;<a =
href=3D"http://test.com" style=3D"color: #2563eb; text-decoration: none;">h=
ttp://test.com</a></p>
<p><a href=3D"http://test.com/settings/notifications" style=3D"colo=
r: #2563eb; text-decoration: none;">Click here to manage your notification =
settings</a></p>
<p><a href=3D"http://test.com/settings/notifications?disabled=3D2fa=
eee0f-26cb-4e96-821c-85ccb9f71513" style=3D"color: #2563eb; text-decoration=
: none;">Stop receiving emails like this</a></p>
</div>
</div>
</body>
</html>
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4--
@@ -0,0 +1,84 @@
From: system@coder.com
To: bobby@coder.com
Subject: Workspace "bobby-workspace" marked for deletion
Message-Id: 02ee4935-73be-4fa1-a290-ff9999026b13@blush-whale-48
Date: Fri, 11 Oct 2024 09:03:06 +0000
Content-Type: multipart/alternative; boundary=bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
MIME-Version: 1.0
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=UTF-8
Hi Bobby,
Your workspace bobby-workspace has been marked for deletion after 24 hours =
of dormancy (https://coder.com/docs/templates/schedule#dormancy-auto-deleti=
on-enterprise) because of template updated to new dormancy policy.
To prevent deletion, use your workspace with the link below.
View workspace: http://test.com/@bobby/bobby-workspace
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
Content-Transfer-Encoding: quoted-printable
Content-Type: text/html; charset=UTF-8
<!doctype html>
<html lang=3D"en">
<head>
<meta charset=3D"UTF-8" />
<meta name=3D"viewport" content=3D"width=3Ddevice-width, initial-scale=
=3D1.0" />
<title>Workspace "bobby-workspace" marked for deletion</title>
</head>
<body style=3D"margin: 0; padding: 0; font-family: -apple-system, system-=
ui, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarel=
l', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; color: #020617=
; background: #f8fafc;">
<div style=3D"max-width: 600px; margin: 20px auto; padding: 60px; borde=
r: 1px solid #e2e8f0; border-radius: 8px; background-color: #fff; text-alig=
n: left; font-size: 14px; line-height: 1.5;">
<div style=3D"text-align: center;">
<img src=3D"https://coder.com/coder-logo-horizontal.png" alt=3D"Cod=
er Logo" style=3D"height: 40px;" />
</div>
<h1 style=3D"text-align: center; font-size: 24px; font-weight: 400; m=
argin: 8px 0 32px; line-height: 1.5;">
Workspace "bobby-workspace" marked for deletion
</h1>
<div style=3D"line-height: 1.5;">
<p>Hi Bobby,</p>
<p>Your workspace <strong>bobby-workspace</strong> has been marked for <str=
ong>deletion</strong> after 24 hours of <a href=3D"https://coder.com/docs/t=
emplates/schedule#dormancy-auto-deletion-enterprise">dormancy</a> because o=
f template updated to new dormancy policy.<br>
To prevent deletion, use your workspace with the link below.</p>
</div>
<div style=3D"text-align: center; margin-top: 32px;">
=20
<a href=3D"http://test.com/@bobby/bobby-workspace" style=3D"display=
: inline-block; padding: 13px 24px; background-color: #020617; color: #f8fa=
fc; text-decoration: none; border-radius: 8px; margin: 0 4px;">
View workspace
</a>
=20
</div>
<div style=3D"border-top: 1px solid #e2e8f0; color: #475569; font-siz=
e: 12px; margin-top: 64px; padding-top: 24px; line-height: 1.6;">
<p>&copy;&nbsp;2024&nbsp;Coder. All rights reserved&nbsp;-&nbsp;<a =
href=3D"http://test.com" style=3D"color: #2563eb; text-decoration: none;">h=
ttp://test.com</a></p>
<p><a href=3D"http://test.com/settings/notifications" style=3D"colo=
r: #2563eb; text-decoration: none;">Click here to manage your notification =
settings</a></p>
<p><a href=3D"http://test.com/settings/notifications?disabled=3D51c=
e2fdf-c9ca-4be1-8d70-628674f9bc42" style=3D"color: #2563eb; text-decoration=
: none;">Stop receiving emails like this</a></p>
</div>
</div>
</body>
</html>
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4--
@@ -0,0 +1,78 @@
From: system@coder.com
To: bobby@coder.com
Subject: Your account "bobby" has been activated
Message-Id: 02ee4935-73be-4fa1-a290-ff9999026b13@blush-whale-48
Date: Fri, 11 Oct 2024 09:03:06 +0000
Content-Type: multipart/alternative; boundary=bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
MIME-Version: 1.0
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=UTF-8
Hi Bobby,
Your account bobby has been activated by rob.
Open Coder: http://test.com
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
Content-Transfer-Encoding: quoted-printable
Content-Type: text/html; charset=UTF-8
<!doctype html>
<html lang=3D"en">
<head>
<meta charset=3D"UTF-8" />
<meta name=3D"viewport" content=3D"width=3Ddevice-width, initial-scale=
=3D1.0" />
<title>Your account "bobby" has been activated</title>
</head>
<body style=3D"margin: 0; padding: 0; font-family: -apple-system, system-=
ui, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarel=
l', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; color: #020617=
; background: #f8fafc;">
<div style=3D"max-width: 600px; margin: 20px auto; padding: 60px; borde=
r: 1px solid #e2e8f0; border-radius: 8px; background-color: #fff; text-alig=
n: left; font-size: 14px; line-height: 1.5;">
<div style=3D"text-align: center;">
<img src=3D"https://coder.com/coder-logo-horizontal.png" alt=3D"Cod=
er Logo" style=3D"height: 40px;" />
</div>
<h1 style=3D"text-align: center; font-size: 24px; font-weight: 400; m=
argin: 8px 0 32px; line-height: 1.5;">
Your account "bobby" has been activated
</h1>
<div style=3D"line-height: 1.5;">
<p>Hi Bobby,</p>
<p>Your account <strong>bobby</strong> has been activated by <strong>rob</s=
trong>.</p>
</div>
<div style=3D"text-align: center; margin-top: 32px;">
=20
<a href=3D"http://test.com" style=3D"display: inline-block; padding=
: 13px 24px; background-color: #020617; color: #f8fafc; text-decoration: no=
ne; border-radius: 8px; margin: 0 4px;">
Open Coder
</a>
=20
</div>
<div style=3D"border-top: 1px solid #e2e8f0; color: #475569; font-siz=
e: 12px; margin-top: 64px; padding-top: 24px; line-height: 1.6;">
<p>&copy;&nbsp;2024&nbsp;Coder. All rights reserved&nbsp;-&nbsp;<a =
href=3D"http://test.com" style=3D"color: #2563eb; text-decoration: none;">h=
ttp://test.com</a></p>
<p><a href=3D"http://test.com/settings/notifications" style=3D"colo=
r: #2563eb; text-decoration: none;">Click here to manage your notification =
settings</a></p>
<p><a href=3D"http://test.com/settings/notifications?disabled=3D1a6=
a6bea-ee0a-43e2-9e7c-eabdb53730e4" style=3D"color: #2563eb; text-decoration=
: none;">Stop receiving emails like this</a></p>
</div>
</div>
</body>
</html>
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4--
@@ -0,0 +1,70 @@
From: system@coder.com
To: bobby@coder.com
Subject: Your account "bobby" has been suspended
Message-Id: 02ee4935-73be-4fa1-a290-ff9999026b13@blush-whale-48
Date: Fri, 11 Oct 2024 09:03:06 +0000
Content-Type: multipart/alternative; boundary=bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
MIME-Version: 1.0
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=UTF-8
Hi Bobby,
Your account bobby has been suspended by rob.
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
Content-Transfer-Encoding: quoted-printable
Content-Type: text/html; charset=UTF-8
<!doctype html>
<html lang=3D"en">
<head>
<meta charset=3D"UTF-8" />
<meta name=3D"viewport" content=3D"width=3Ddevice-width, initial-scale=
=3D1.0" />
<title>Your account "bobby" has been suspended</title>
</head>
<body style=3D"margin: 0; padding: 0; font-family: -apple-system, system-=
ui, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarel=
l', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; color: #020617=
; background: #f8fafc;">
<div style=3D"max-width: 600px; margin: 20px auto; padding: 60px; borde=
r: 1px solid #e2e8f0; border-radius: 8px; background-color: #fff; text-alig=
n: left; font-size: 14px; line-height: 1.5;">
<div style=3D"text-align: center;">
<img src=3D"https://coder.com/coder-logo-horizontal.png" alt=3D"Cod=
er Logo" style=3D"height: 40px;" />
</div>
<h1 style=3D"text-align: center; font-size: 24px; font-weight: 400; m=
argin: 8px 0 32px; line-height: 1.5;">
Your account "bobby" has been suspended
</h1>
<div style=3D"line-height: 1.5;">
<p>Hi Bobby,</p>
<p>Your account <strong>bobby</strong> has been suspended by <strong>rob</s=
trong>.</p>
</div>
<div style=3D"text-align: center; margin-top: 32px;">
=20
</div>
<div style=3D"border-top: 1px solid #e2e8f0; color: #475569; font-siz=
e: 12px; margin-top: 64px; padding-top: 24px; line-height: 1.6;">
<p>&copy;&nbsp;2024&nbsp;Coder. All rights reserved&nbsp;-&nbsp;<a =
href=3D"http://test.com" style=3D"color: #2563eb; text-decoration: none;">h=
ttp://test.com</a></p>
<p><a href=3D"http://test.com/settings/notifications" style=3D"colo=
r: #2563eb; text-decoration: none;">Click here to manage your notification =
settings</a></p>
<p><a href=3D"http://test.com/settings/notifications?disabled=3D6a2=
f0609-9b69-4d36-a989-9f5925b6cbff" style=3D"color: #2563eb; text-decoration=
: none;">Stop receiving emails like this</a></p>
</div>
</div>
</body>
</html>
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4--
@@ -0,0 +1,29 @@
{
"_version": "1.1",
"msg_id": "00000000-0000-0000-0000-000000000000",
"payload": {
"_version": "1.1",
"notification_name": "Template Deleted",
"notification_template_id": "00000000-0000-0000-0000-000000000000",
"user_id": "00000000-0000-0000-0000-000000000000",
"user_email": "bobby@coder.com",
"user_name": "Bobby",
"user_username": "bobby",
"actions": [
{
"label": "View templates",
"url": "http://test.com/templates"
}
],
"labels": {
"display_name": "Bobby's Template",
"initiator": "rob",
"name": "bobby-template"
},
"data": null
},
"title": "Template \"bobby-template\" deleted",
"title_markdown": "Template \"bobby-template\" deleted",
"body": "Hi Bobby,\n\nThe template bobby-template was deleted by rob.\n\nThe template's display name was Bobby's Template.",
"body_markdown": "Hi Bobby,\n\nThe template **bobby-template** was deleted by **rob**.\n\nThe template's display name was **Bobby's Template**."
}
@@ -0,0 +1,29 @@
{
"_version": "1.1",
"msg_id": "00000000-0000-0000-0000-000000000000",
"payload": {
"_version": "1.1",
"notification_name": "User account activated",
"notification_template_id": "00000000-0000-0000-0000-000000000000",
"user_id": "00000000-0000-0000-0000-000000000000",
"user_email": "bobby@coder.com",
"user_name": "Bobby",
"user_username": "bobby",
"actions": [
{
"label": "View accounts",
"url": "http://test.com/deployment/users?filter=status%3Aactive"
}
],
"labels": {
"activated_account_name": "bobby",
"activated_account_user_name": "William Tables",
"initiator": "rob"
},
"data": null
},
"title": "User account \"bobby\" activated",
"title_markdown": "User account \"bobby\" activated",
"body": "Hi Bobby,\n\nUser account bobby has been activated.\n\nThe newly activated account belongs to William Tables and was activated by rob.",
"body_markdown": "Hi Bobby,\n\nUser account **bobby** has been activated.\n\nThe newly activated account belongs to **William Tables** and was activated by **rob**."
}
@@ -0,0 +1,29 @@
{
"_version": "1.1",
"msg_id": "00000000-0000-0000-0000-000000000000",
"payload": {
"_version": "1.1",
"notification_name": "User account created",
"notification_template_id": "00000000-0000-0000-0000-000000000000",
"user_id": "00000000-0000-0000-0000-000000000000",
"user_email": "bobby@coder.com",
"user_name": "Bobby",
"user_username": "bobby",
"actions": [
{
"label": "View accounts",
"url": "http://test.com/deployment/users?filter=status%3Aactive"
}
],
"labels": {
"created_account_name": "bobby",
"created_account_user_name": "William Tables",
"initiator": "rob"
},
"data": null
},
"title": "User account \"bobby\" created",
"title_markdown": "User account \"bobby\" created",
"body": "Hi Bobby,\n\nNew user account bobby has been created.\n\nThis new user account was created for William Tables by rob.",
"body_markdown": "Hi Bobby,\n\nNew user account **bobby** has been created.\n\nThis new user account was created for **William Tables** by **rob**."
}
@@ -0,0 +1,29 @@
{
"_version": "1.1",
"msg_id": "00000000-0000-0000-0000-000000000000",
"payload": {
"_version": "1.1",
"notification_name": "User account deleted",
"notification_template_id": "00000000-0000-0000-0000-000000000000",
"user_id": "00000000-0000-0000-0000-000000000000",
"user_email": "bobby@coder.com",
"user_name": "Bobby",
"user_username": "bobby",
"actions": [
{
"label": "View accounts",
"url": "http://test.com/deployment/users?filter=status%3Aactive"
}
],
"labels": {
"deleted_account_name": "bobby",
"deleted_account_user_name": "William Tables",
"initiator": "rob"
},
"data": null
},
"title": "User account \"bobby\" deleted",
"title_markdown": "User account \"bobby\" deleted",
"body": "Hi Bobby,\n\nUser account bobby has been deleted.\n\nThe deleted account belonged to William Tables and was deleted by rob.",
"body_markdown": "Hi Bobby,\n\nUser account **bobby** has been deleted.\n\nThe deleted account belonged to **William Tables** and was deleted by **rob**."
}
@@ -0,0 +1,29 @@
{
"_version": "1.1",
"msg_id": "00000000-0000-0000-0000-000000000000",
"payload": {
"_version": "1.1",
"notification_name": "User account suspended",
"notification_template_id": "00000000-0000-0000-0000-000000000000",
"user_id": "00000000-0000-0000-0000-000000000000",
"user_email": "bobby@coder.com",
"user_name": "Bobby",
"user_username": "bobby",
"actions": [
{
"label": "View suspended accounts",
"url": "http://test.com/deployment/users?filter=status%3Asuspended"
}
],
"labels": {
"initiator": "rob",
"suspended_account_name": "bobby",
"suspended_account_user_name": "William Tables"
},
"data": null
},
"title": "User account \"bobby\" suspended",
"title_markdown": "User account \"bobby\" suspended",
"body": "Hi Bobby,\n\nUser account bobby has been suspended.\n\nThe newly suspended account belongs to William Tables and was suspended by rob.",
"body_markdown": "Hi Bobby,\n\nUser account **bobby** has been suspended.\n\nThe newly suspended account belongs to **William Tables** and was suspended by **rob**."
}
@@ -0,0 +1,22 @@
{
"_version": "1.1",
"msg_id": "00000000-0000-0000-0000-000000000000",
"payload": {
"_version": "1.1",
"notification_name": "One-Time Passcode",
"notification_template_id": "00000000-0000-0000-0000-000000000000",
"user_id": "00000000-0000-0000-0000-000000000000",
"user_email": "bobby@coder.com",
"user_name": "Bobby",
"user_username": "bobby",
"actions": [],
"labels": {
"one_time_passcode": "00000000-0000-0000-0000-000000000000"
},
"data": null
},
"title": "Your One-Time Passcode for Coder.",
"title_markdown": "Your One-Time Passcode for Coder.",
"body": "Hi Bobby,\n\nA request to reset the password for your Coder account has been made. Your one-time passcode is:\n\n00000000-0000-0000-0000-000000000000\n\nIf you did not request to reset your password, you can ignore this message.",
"body_markdown": "Hi Bobby,\n\nA request to reset the password for your Coder account has been made. Your one-time passcode is:\n\n**00000000-0000-0000-0000-000000000000**\n\nIf you did not request to reset your password, you can ignore this message."
}
@@ -0,0 +1,29 @@
{
"_version": "1.1",
"msg_id": "00000000-0000-0000-0000-000000000000",
"payload": {
"_version": "1.1",
"notification_name": "Workspace Updated Automatically",
"notification_template_id": "00000000-0000-0000-0000-000000000000",
"user_id": "00000000-0000-0000-0000-000000000000",
"user_email": "bobby@coder.com",
"user_name": "Bobby",
"user_username": "bobby",
"actions": [
{
"label": "View workspace",
"url": "http://test.com/@bobby/bobby-workspace"
}
],
"labels": {
"name": "bobby-workspace",
"template_version_message": "template now includes catnip",
"template_version_name": "1.0"
},
"data": null
},
"title": "Workspace \"bobby-workspace\" updated automatically",
"title_markdown": "Workspace \"bobby-workspace\" updated automatically",
"body": "Hi Bobby,\n\nYour workspace bobby-workspace has been updated automatically to the latest template version (1.0).\n\nReason for update: template now includes catnip.",
"body_markdown": "Hi Bobby,\n\nYour workspace **bobby-workspace** has been updated automatically to the latest template version (1.0).\n\nReason for update: **template now includes catnip**."
}
@@ -0,0 +1,28 @@
{
"_version": "1.1",
"msg_id": "00000000-0000-0000-0000-000000000000",
"payload": {
"_version": "1.1",
"notification_name": "Workspace Autobuild Failed",
"notification_template_id": "00000000-0000-0000-0000-000000000000",
"user_id": "00000000-0000-0000-0000-000000000000",
"user_email": "bobby@coder.com",
"user_name": "Bobby",
"user_username": "bobby",
"actions": [
{
"label": "View workspace",
"url": "http://test.com/@bobby/bobby-workspace"
}
],
"labels": {
"name": "bobby-workspace",
"reason": "autostart"
},
"data": null
},
"title": "Workspace \"bobby-workspace\" autobuild failed",
"title_markdown": "Workspace \"bobby-workspace\" autobuild failed",
"body": "Hi Bobby,\n\nAutomatic build of your workspace bobby-workspace failed.\n\nThe specified reason was \"autostart\".",
"body_markdown": "Hi Bobby,\n\nAutomatic build of your workspace **bobby-workspace** failed.\n\nThe specified reason was \"**autostart**\"."
}
@@ -0,0 +1,66 @@
{
"_version": "1.1",
"msg_id": "00000000-0000-0000-0000-000000000000",
"payload": {
"_version": "1.1",
"notification_name": "Report: Workspace Builds Failed For Template",
"notification_template_id": "00000000-0000-0000-0000-000000000000",
"user_id": "00000000-0000-0000-0000-000000000000",
"user_email": "bobby@coder.com",
"user_name": "Bobby",
"user_username": "bobby",
"actions": [
{
"label": "View workspaces",
"url": "http://test.com/workspaces?filter=template%3Abobby-first-template"
}
],
"labels": {
"template_display_name": "Bobby First Template",
"template_name": "bobby-first-template"
},
"data": {
"failed_builds": 4,
"report_frequency": "week",
"template_versions": [
{
"failed_builds": [
{
"build_number": 1234,
"workspace_name": "workspace-1",
"workspace_owner_username": "mtojek"
},
{
"build_number": 5678,
"workspace_name": "my-workspace-3",
"workspace_owner_username": "johndoe"
},
{
"build_number": 774,
"workspace_name": "workwork",
"workspace_owner_username": "jack"
}
],
"failed_count": 3,
"template_version_name": "bobby-template-version-1"
},
{
"failed_builds": [
{
"build_number": 8888,
"workspace_name": "cool-workspace",
"workspace_owner_username": "ben"
}
],
"failed_count": 1,
"template_version_name": "bobby-template-version-2"
}
],
"total_builds": 55
}
},
"title": "Workspace builds failed for template \"Bobby First Template\"",
"title_markdown": "Workspace builds failed for template \"Bobby First Template\"",
"body": "Hi Bobby,\n\nTemplate Bobby First Template has failed to build 4/55 times over the last week.\n\nReport:\n\nbobby-template-version-1 failed 3 times:\n\nmtojek / workspace-1 / #1234 (http://test.com/@mtojek/workspace-1/builds/1234)\njohndoe / my-workspace-3 / #5678 (http://test.com/@johndoe/my-workspace-3/builds/5678)\njack / workwork / #774 (http://test.com/@jack/workwork/builds/774)\n\nbobby-template-version-2 failed 1 time:\n\nben / cool-workspace / #8888 (http://test.com/@ben/cool-workspace/builds/8888)\n\nWe recommend reviewing these issues to ensure future builds are successful.",
"body_markdown": "Hi Bobby,\n\nTemplate **Bobby First Template** has failed to build 4/55 times over the last week.\n\n**Report:**\n\n**bobby-template-version-1** failed 3 times:\n\n* [mtojek / workspace-1 / #1234](http://test.com/@mtojek/workspace-1/builds/1234)\n* [johndoe / my-workspace-3 / #5678](http://test.com/@johndoe/my-workspace-3/builds/5678)\n* [jack / workwork / #774](http://test.com/@jack/workwork/builds/774)\n\n**bobby-template-version-2** failed 1 time:\n\n* [ben / cool-workspace / #8888](http://test.com/@ben/cool-workspace/builds/8888)\n\nWe recommend reviewing these issues to ensure future builds are successful."
}
@@ -0,0 +1,33 @@
{
"_version": "1.1",
"msg_id": "00000000-0000-0000-0000-000000000000",
"payload": {
"_version": "1.1",
"notification_name": "Workspace Deleted",
"notification_template_id": "00000000-0000-0000-0000-000000000000",
"user_id": "00000000-0000-0000-0000-000000000000",
"user_email": "bobby@coder.com",
"user_name": "Bobby",
"user_username": "bobby",
"actions": [
{
"label": "View workspaces",
"url": "http://test.com/workspaces"
},
{
"label": "View templates",
"url": "http://test.com/templates"
}
],
"labels": {
"initiator": "autobuild",
"name": "bobby-workspace",
"reason": "autodeleted due to dormancy"
},
"data": null
},
"title": "Workspace \"bobby-workspace\" deleted",
"title_markdown": "Workspace \"bobby-workspace\" deleted",
"body": "Hi Bobby,\n\nYour workspace bobby-workspace was deleted.\n\nThe specified reason was \"autodeleted due to dormancy (autobuild)\".",
"body_markdown": "Hi Bobby,\n\nYour workspace **bobby-workspace** was deleted.\n\nThe specified reason was \"**autodeleted due to dormancy (autobuild)**\"."
}
@@ -0,0 +1,31 @@
{
"_version": "1.1",
"msg_id": "00000000-0000-0000-0000-000000000000",
"payload": {
"_version": "1.1",
"notification_name": "Workspace Marked as Dormant",
"notification_template_id": "00000000-0000-0000-0000-000000000000",
"user_id": "00000000-0000-0000-0000-000000000000",
"user_email": "bobby@coder.com",
"user_name": "Bobby",
"user_username": "bobby",
"actions": [
{
"label": "View workspace",
"url": "http://test.com/@bobby/bobby-workspace"
}
],
"labels": {
"dormancyHours": "24",
"initiator": "autobuild",
"name": "bobby-workspace",
"reason": "breached the template's threshold for inactivity",
"timeTilDormant": "24 hours"
},
"data": null
},
"title": "Workspace \"bobby-workspace\" marked as dormant",
"title_markdown": "Workspace \"bobby-workspace\" marked as dormant",
"body": "Hi Bobby,\n\nYour workspace bobby-workspace has been marked as dormant (https://coder.com/docs/templates/schedule#dormancy-threshold-enterprise) because of breached the template's threshold for inactivity.\nDormant workspaces are automatically deleted (https://coder.com/docs/templates/schedule#dormancy-auto-deletion-enterprise) after 24 hours of inactivity.\nTo prevent deletion, use your workspace with the link below.",
"body_markdown": "Hi Bobby,\n\nYour workspace **bobby-workspace** has been marked as [**dormant**](https://coder.com/docs/templates/schedule#dormancy-threshold-enterprise) because of breached the template's threshold for inactivity.\nDormant workspaces are [automatically deleted](https://coder.com/docs/templates/schedule#dormancy-auto-deletion-enterprise) after 24 hours of inactivity.\nTo prevent deletion, use your workspace with the link below."
}
@@ -0,0 +1,33 @@
{
"_version": "1.1",
"msg_id": "00000000-0000-0000-0000-000000000000",
"payload": {
"_version": "1.1",
"notification_name": "Workspace Manual Build Failed",
"notification_template_id": "00000000-0000-0000-0000-000000000000",
"user_id": "00000000-0000-0000-0000-000000000000",
"user_email": "bobby@coder.com",
"user_name": "Bobby",
"user_username": "bobby",
"actions": [
{
"label": "View build",
"url": "http://test.com/@mrbobby/bobby-workspace/builds/3"
}
],
"labels": {
"initiator": "joe",
"name": "bobby-workspace",
"template_display_name": "William's Template",
"template_name": "bobby-template",
"template_version_name": "bobby-template-version",
"workspace_build_number": "3",
"workspace_owner_username": "mrbobby"
},
"data": null
},
"title": "Workspace \"bobby-workspace\" manual build failed",
"title_markdown": "Workspace \"bobby-workspace\" manual build failed",
"body": "Hi Bobby,\n\nA manual build of the workspace bobby-workspace using the template bobby-template failed (version: bobby-template-version).\n\nThe template's display name was William's Template. The workspace build was initiated by joe.",
"body_markdown": "Hi Bobby,\n\nA manual build of the workspace **bobby-workspace** using the template **bobby-template** failed (version: **bobby-template-version**).\n\nThe template's display name was **William's Template**. The workspace build was initiated by **joe**."
}
@@ -0,0 +1,30 @@
{
"_version": "1.1",
"msg_id": "00000000-0000-0000-0000-000000000000",
"payload": {
"_version": "1.1",
"notification_name": "Workspace Marked for Deletion",
"notification_template_id": "00000000-0000-0000-0000-000000000000",
"user_id": "00000000-0000-0000-0000-000000000000",
"user_email": "bobby@coder.com",
"user_name": "Bobby",
"user_username": "bobby",
"actions": [
{
"label": "View workspace",
"url": "http://test.com/@bobby/bobby-workspace"
}
],
"labels": {
"dormancyHours": "24",
"name": "bobby-workspace",
"reason": "template updated to new dormancy policy",
"timeTilDormant": "24 hours"
},
"data": null
},
"title": "Workspace \"bobby-workspace\" marked for deletion",
"title_markdown": "Workspace \"bobby-workspace\" marked for deletion",
"body": "Hi Bobby,\n\nYour workspace bobby-workspace has been marked for deletion after 24 hours of dormancy (https://coder.com/docs/templates/schedule#dormancy-auto-deletion-enterprise) because of template updated to new dormancy policy.\nTo prevent deletion, use your workspace with the link below.",
"body_markdown": "Hi Bobby,\n\nYour workspace **bobby-workspace** has been marked for **deletion** after 24 hours of [dormancy](https://coder.com/docs/templates/schedule#dormancy-auto-deletion-enterprise) because of template updated to new dormancy policy.\nTo prevent deletion, use your workspace with the link below."
}
@@ -0,0 +1,28 @@
{
"_version": "1.1",
"msg_id": "00000000-0000-0000-0000-000000000000",
"payload": {
"_version": "1.1",
"notification_name": "Your account has been activated",
"notification_template_id": "00000000-0000-0000-0000-000000000000",
"user_id": "00000000-0000-0000-0000-000000000000",
"user_email": "bobby@coder.com",
"user_name": "Bobby",
"user_username": "bobby",
"actions": [
{
"label": "Open Coder",
"url": "http://test.com"
}
],
"labels": {
"activated_account_name": "bobby",
"initiator": "rob"
},
"data": null
},
"title": "Your account \"bobby\" has been activated",
"title_markdown": "Your account \"bobby\" has been activated",
"body": "Hi Bobby,\n\nYour account bobby has been activated by rob.",
"body_markdown": "Hi Bobby,\n\nYour account **bobby** has been activated by **rob**."
}
@@ -0,0 +1,23 @@
{
"_version": "1.1",
"msg_id": "00000000-0000-0000-0000-000000000000",
"payload": {
"_version": "1.1",
"notification_name": "Your account has been suspended",
"notification_template_id": "00000000-0000-0000-0000-000000000000",
"user_id": "00000000-0000-0000-0000-000000000000",
"user_email": "bobby@coder.com",
"user_name": "Bobby",
"user_username": "bobby",
"actions": [],
"labels": {
"initiator": "rob",
"suspended_account_name": "bobby"
},
"data": null
},
"title": "Your account \"bobby\" has been suspended",
"title_markdown": "Your account \"bobby\" has been suspended",
"body": "Hi Bobby,\n\nYour account bobby has been suspended by rob.",
"body_markdown": "Hi Bobby,\n\nYour account **bobby** has been suspended by **rob**."
}
+4 -4
View File
@@ -604,7 +604,7 @@ func (api *API) deleteUser(rw http.ResponseWriter, r *http.Request) {
map[string]string{
"deleted_account_name": user.Username,
"deleted_account_user_name": user.Name,
"account_deleter_user_name": accountDeleter.Name,
"initiator": accountDeleter.Name,
},
"api-users-delete",
user.ID,
@@ -918,7 +918,7 @@ func (api *API) notifyUserStatusChanged(ctx context.Context, actingUserName stri
labels = map[string]string{
"suspended_account_name": targetUser.Username,
"suspended_account_user_name": targetUser.Name,
"account_suspender_user_name": actingUserName,
"initiator": actingUserName,
}
adminTemplateID = notifications.TemplateUserAccountSuspended
personalTemplateID = notifications.TemplateYourAccountSuspended
@@ -926,7 +926,7 @@ func (api *API) notifyUserStatusChanged(ctx context.Context, actingUserName stri
labels = map[string]string{
"activated_account_name": targetUser.Username,
"activated_account_user_name": targetUser.Name,
"account_activator_user_name": actingUserName,
"initiator": actingUserName,
}
adminTemplateID = notifications.TemplateUserAccountActivated
personalTemplateID = notifications.TemplateYourAccountActivated
@@ -1407,7 +1407,7 @@ func (api *API) CreateUser(ctx context.Context, store database.Store, req Create
map[string]string{
"created_account_name": user.Username,
"created_account_user_name": user.Name,
"account_creator": req.accountCreatorName,
"initiator": req.accountCreatorName,
}, "api-users-create",
user.ID,
); err != nil {
+1 -1
View File
@@ -517,7 +517,7 @@ func TestNotifyDeletedUser(t *testing.T) {
require.Contains(t, notifyEnq.Sent[1].Targets, user.ID)
require.Equal(t, user.Username, notifyEnq.Sent[1].Labels["deleted_account_name"])
require.Equal(t, user.Name, notifyEnq.Sent[1].Labels["deleted_account_user_name"])
require.Equal(t, firstUser.Name, notifyEnq.Sent[1].Labels["account_deleter_user_name"])
require.Equal(t, firstUser.Name, notifyEnq.Sent[1].Labels["initiator"])
})
t.Run("UserAdminNotified", func(t *testing.T) {