From 868e5ba0018d7f03725cd41a1b8df28a50bb3112 Mon Sep 17 00:00:00 2001 From: Yared Yemane Date: Fri, 22 May 2026 02:12:45 -0700 Subject: [PATCH] Apply Yimaru Academy branding to email template seeds Adds branded HTML layout matching the admin portal purple palette, updates 000066 seeds, and adds 000067 migration to refresh existing template rows. Co-authored-by: Cursor --- db/migrations/000066_email_templates.up.sql | 142 ++++++++++++++-- ...0067_branded_email_template_seeds.down.sql | 1 + ...000067_branded_email_template_seeds.up.sql | 156 ++++++++++++++++++ .../emailtemplates/brand_templates.go | 78 +++++++++ internal/services/emailtemplates/defaults.go | 60 +++---- 5 files changed, 394 insertions(+), 43 deletions(-) create mode 100644 db/migrations/000067_branded_email_template_seeds.down.sql create mode 100644 db/migrations/000067_branded_email_template_seeds.up.sql create mode 100644 internal/services/emailtemplates/brand_templates.go diff --git a/db/migrations/000066_email_templates.up.sql b/db/migrations/000066_email_templates.up.sql index 370cd13..0055b76 100644 --- a/db/migrations/000066_email_templates.up.sql +++ b/db/migrations/000066_email_templates.up.sql @@ -20,9 +20,38 @@ VALUES ( 'otp', 'One-Time Password', - 'Yimaru - One Time Password', - 'Welcome to Yimaru Online Learning Platform{{if .FirstName}}, {{.FirstName}}{{end}}. Your OTP is {{.OTP}}. It expires in {{.ExpiresMinutes}} minutes. Please do not share it with anyone.', - '

Welcome to Yimaru Online Learning Platform{{if .FirstName}}, {{.FirstName}}{{end}}.

Your one-time password is {{.OTP}}.

It expires in {{.ExpiresMinutes}} minutes. Please do not share it with anyone.

', + 'Yimaru Academy — Your verification code', + 'Yimaru Academy{{if .FirstName}}, {{.FirstName}}{{end}} + +Your verification code is {{.OTP}}. +It expires in {{.ExpiresMinutes}} minutes. + +Please do not share this code with anyone.', + $otp_html$ + +Yimaru Academy + + +
+ + + + +
+

Yimaru Academy

+

Online Learning Platform

+
+

Your verification code

+

{{if .FirstName}}Hi {{.FirstName}}, {{end}}use the code below to continue signing in to Yimaru Academy.

+
+

One-time password

+

{{.OTP}}

+

Expires in {{.ExpiresMinutes}} minutes

+
+

If you did not request this code, you can safely ignore this email.

+
+

© 2026 Yimaru Academy · All rights reserved

+
$otp_html$, '["OTP", "FirstName", "ExpiresMinutes"]'::jsonb, TRUE, 'ACTIVE' @@ -30,9 +59,33 @@ VALUES ( 'invitation', 'User Invitation', - 'You are invited to join Yimaru', - 'Hi{{if .FirstName}} {{.FirstName}}{{end}}, you have been invited{{if .InviterName}} by {{.InviterName}}{{end}} to join Yimaru Online Learning Platform. Accept your invitation: {{.InviteLink}}', - '

Hi{{if .FirstName}} {{.FirstName}}{{end}},

You have been invited{{if .InviterName}} by {{.InviterName}}{{end}} to join Yimaru Online Learning Platform.

Accept your invitation

', + 'You are invited to Yimaru Academy', + 'Hi{{if .FirstName}} {{.FirstName}}{{end}}, + +You have been invited{{if .InviterName}} by {{.InviterName}}{{end}} to join Yimaru Academy. + +Accept your invitation: {{.InviteLink}}', + $invite_html$ + +Yimaru Academy + + +
+ + + + +
+

Yimaru Academy

+

Online Learning Platform

+
+

You’re invited

+

{{if .FirstName}}Hi {{.FirstName}}, {{end}}you have been invited{{if .InviterName}} by {{.InviterName}}{{end}} to join Yimaru Academy.

+

Accept invitation

+

Or copy this link: {{.InviteLink}}

+
+

© 2026 Yimaru Academy · All rights reserved

+
$invite_html$, '["FirstName", "InviterName", "InviteLink"]'::jsonb, TRUE, 'ACTIVE' @@ -40,9 +93,33 @@ VALUES ( 'password_reset', 'Password Reset', - 'Reset your Yimaru password', - 'Hi{{if .FirstName}} {{.FirstName}}{{end}}, use this link to reset your password: {{.ResetLink}}. The link expires in {{.ExpiresMinutes}} minutes.', - '

Hi{{if .FirstName}} {{.FirstName}}{{end}},

Use the link below to reset your password. It expires in {{.ExpiresMinutes}} minutes.

Reset your password

', + 'Reset your Yimaru Academy password', + 'Hi{{if .FirstName}} {{.FirstName}}{{end}}, + +Reset your password: {{.ResetLink}} + +This link expires in {{.ExpiresMinutes}} minutes.', + $reset_html$ + +Yimaru Academy + + +
+ + + + +
+

Yimaru Academy

+

Online Learning Platform

+
+

Reset your password

+

{{if .FirstName}}Hi {{.FirstName}}, {{end}}we received a request to reset your Yimaru Academy password. The link below expires in {{.ExpiresMinutes}} minutes.

+

Reset password

+

If you did not request a reset, ignore this email.

+
+

© 2026 Yimaru Academy · All rights reserved

+
$reset_html$, '["FirstName", "ResetLink", "ExpiresMinutes"]'::jsonb, TRUE, 'ACTIVE' @@ -50,9 +127,30 @@ VALUES ( 'welcome', 'Welcome Email', - 'Welcome to Yimaru', - 'Hi{{if .FirstName}} {{.FirstName}}{{end}}, welcome to Yimaru Online Learning Platform! Sign in at {{.LoginURL}} to get started.', - '

Hi{{if .FirstName}} {{.FirstName}}{{end}},

Welcome to Yimaru Online Learning Platform!

Sign in to get started

', + 'Welcome to Yimaru Academy', + 'Hi{{if .FirstName}} {{.FirstName}}{{end}}, + +Welcome to Yimaru Academy! Sign in to get started: {{.LoginURL}}', + $welcome_html$ + +Yimaru Academy + + +
+ + + + +
+

Yimaru Academy

+

Online Learning Platform

+
+

Welcome aboard

+

{{if .FirstName}}Hi {{.FirstName}}, {{end}}your Yimaru Academy account is ready. Start learning at your own pace.

+

Sign in to Yimaru Academy

+
+

© 2026 Yimaru Academy · All rights reserved

+
$welcome_html$, '["FirstName", "LoginURL"]'::jsonb, TRUE, 'ACTIVE' @@ -62,7 +160,25 @@ VALUES 'Custom Message', '{{.Subject}}', '{{.Message}}', - '

{{.Message}}

', + $custom_html$ + +Yimaru Academy + + +
+ + + + +
+

Yimaru Academy

+

Online Learning Platform

+
+

{{.Subject}}

+
{{.Message}}
+
+

© 2026 Yimaru Academy · All rights reserved

+
$custom_html$, '["Subject", "Message"]'::jsonb, TRUE, 'ACTIVE' diff --git a/db/migrations/000067_branded_email_template_seeds.down.sql b/db/migrations/000067_branded_email_template_seeds.down.sql new file mode 100644 index 0000000..7bd852d --- /dev/null +++ b/db/migrations/000067_branded_email_template_seeds.down.sql @@ -0,0 +1 @@ +-- No-op: branded template content is not reverted automatically. diff --git a/db/migrations/000067_branded_email_template_seeds.up.sql b/db/migrations/000067_branded_email_template_seeds.up.sql new file mode 100644 index 0000000..c14e343 --- /dev/null +++ b/db/migrations/000067_branded_email_template_seeds.up.sql @@ -0,0 +1,156 @@ +-- Refresh system email templates with Yimaru Academy branded HTML (admin portal colors). +-- Safe to run after 000066 when seeds used the original plain layout. + +UPDATE email_templates SET + name = 'One-Time Password', + subject = 'Yimaru Academy — Your verification code', + body_text = 'Yimaru Academy{{if .FirstName}}, {{.FirstName}}{{end}} + +Your verification code is {{.OTP}}. +It expires in {{.ExpiresMinutes}} minutes. + +Please do not share this code with anyone.', + body_html = $otp_html$ + +Yimaru Academy + + +
+ + + + +
+

Yimaru Academy

+

Online Learning Platform

+
+

Your verification code

+

{{if .FirstName}}Hi {{.FirstName}}, {{end}}use the code below to continue signing in to Yimaru Academy.

+
+

One-time password

+

{{.OTP}}

+

Expires in {{.ExpiresMinutes}} minutes

+
+

If you did not request this code, you can safely ignore this email.

+
+

© 2026 Yimaru Academy · All rights reserved

+
$otp_html$, + updated_at = NOW() +WHERE slug = 'otp'; + +UPDATE email_templates SET + name = 'User Invitation', + subject = 'You are invited to Yimaru Academy', + body_text = 'Hi{{if .FirstName}} {{.FirstName}}{{end}}, + +You have been invited{{if .InviterName}} by {{.InviterName}}{{end}} to join Yimaru Academy. + +Accept your invitation: {{.InviteLink}}', + body_html = $invite_html$ + +Yimaru Academy + + +
+ + + + +
+

Yimaru Academy

+

Online Learning Platform

+
+

You’re invited

+

{{if .FirstName}}Hi {{.FirstName}}, {{end}}you have been invited{{if .InviterName}} by {{.InviterName}}{{end}} to join Yimaru Academy.

+

Accept invitation

+

Or copy this link: {{.InviteLink}}

+
+

© 2026 Yimaru Academy · All rights reserved

+
$invite_html$, + updated_at = NOW() +WHERE slug = 'invitation'; + +UPDATE email_templates SET + name = 'Password Reset', + subject = 'Reset your Yimaru Academy password', + body_text = 'Hi{{if .FirstName}} {{.FirstName}}{{end}}, + +Reset your password: {{.ResetLink}} + +This link expires in {{.ExpiresMinutes}} minutes.', + body_html = $reset_html$ + +Yimaru Academy + + +
+ + + + +
+

Yimaru Academy

+

Online Learning Platform

+
+

Reset your password

+

{{if .FirstName}}Hi {{.FirstName}}, {{end}}we received a request to reset your Yimaru Academy password. The link below expires in {{.ExpiresMinutes}} minutes.

+

Reset password

+

If you did not request a reset, ignore this email.

+
+

© 2026 Yimaru Academy · All rights reserved

+
$reset_html$, + updated_at = NOW() +WHERE slug = 'password_reset'; + +UPDATE email_templates SET + name = 'Welcome Email', + subject = 'Welcome to Yimaru Academy', + body_text = 'Hi{{if .FirstName}} {{.FirstName}}{{end}}, + +Welcome to Yimaru Academy! Sign in to get started: {{.LoginURL}}', + body_html = $welcome_html$ + +Yimaru Academy + + +
+ + + + +
+

Yimaru Academy

+

Online Learning Platform

+
+

Welcome aboard

+

{{if .FirstName}}Hi {{.FirstName}}, {{end}}your Yimaru Academy account is ready. Start learning at your own pace.

+

Sign in to Yimaru Academy

+
+

© 2026 Yimaru Academy · All rights reserved

+
$welcome_html$, + updated_at = NOW() +WHERE slug = 'welcome'; + +UPDATE email_templates SET + name = 'Custom Message', + body_html = $custom_html$ + +Yimaru Academy + + +
+ + + + +
+

Yimaru Academy

+

Online Learning Platform

+
+

{{.Subject}}

+
{{.Message}}
+
+

© 2026 Yimaru Academy · All rights reserved

+
$custom_html$, + updated_at = NOW() +WHERE slug = 'custom_message'; diff --git a/internal/services/emailtemplates/brand_templates.go b/internal/services/emailtemplates/brand_templates.go new file mode 100644 index 0000000..deead36 --- /dev/null +++ b/internal/services/emailtemplates/brand_templates.go @@ -0,0 +1,78 @@ +package emailtemplates + +// Yimaru Academy brand colors (aligned with admin portal): +// Primary #9d2a83, gradient #7b1f6e → #c43a9a, surface #eef4ff, text #333 / #666. + +const ( + brandEmailHeader = ` + +Yimaru Academy + + +
+ + + + +
+

Yimaru Academy

+

Online Learning Platform

+
` + + brandEmailFooter = `
+

© 2026 Yimaru Academy · All rights reserved

+
` + + brandButtonPrimary = `display:inline-block;background-color:#9d2a83;color:#ffffff !important;text-decoration:none;padding:14px 28px;border-radius:8px;font-weight:600;font-size:15px;line-height:1.2;` + + brandHeadingStyle = `margin:0 0 8px;color:#333333;font-size:24px;font-weight:700;line-height:1.3;` + brandBodyStyle = `margin:0 0 20px;color:#666666;font-size:15px;line-height:1.6;` + brandMutedStyle = `margin:24px 0 0;color:#666666;font-size:14px;line-height:1.6;` + brandAccentBox = `background-color:#eef4ff;border-radius:8px;padding:20px;border:1px solid #e0e8f5;text-align:center;` +) + +const ( + defaultOTPSubject = "Yimaru Academy — Your verification code" + defaultOTPText = "Yimaru Academy{{if .FirstName}}, {{.FirstName}}{{end}}\n\nYour verification code is {{.OTP}}.\nIt expires in {{.ExpiresMinutes}} minutes.\n\nPlease do not share this code with anyone." + defaultOTPHTML = brandEmailHeader + ` +

Your verification code

+

{{if .FirstName}}Hi {{.FirstName}}, {{end}}use the code below to continue signing in to Yimaru Academy.

+
+

One-time password

+

{{.OTP}}

+

Expires in {{.ExpiresMinutes}} minutes

+
+

If you did not request this code, you can safely ignore this email.

+` + brandEmailFooter + + defaultInvitationSubject = "You are invited to Yimaru Academy" + defaultInvitationText = "Hi{{if .FirstName}} {{.FirstName}}{{end}},\n\nYou have been invited{{if .InviterName}} by {{.InviterName}}{{end}} to join Yimaru Academy.\n\nAccept your invitation: {{.InviteLink}}" + defaultInvitationHTML = brandEmailHeader + ` +

You’re invited

+

{{if .FirstName}}Hi {{.FirstName}}, {{end}}you have been invited{{if .InviterName}} by {{.InviterName}}{{end}} to join Yimaru Academy.

+

Accept invitation

+

Or copy this link: {{.InviteLink}}

+` + brandEmailFooter + + defaultPasswordResetSubject = "Reset your Yimaru Academy password" + defaultPasswordResetText = "Hi{{if .FirstName}} {{.FirstName}}{{end}},\n\nReset your password: {{.ResetLink}}\n\nThis link expires in {{.ExpiresMinutes}} minutes." + defaultPasswordResetHTML = brandEmailHeader + ` +

Reset your password

+

{{if .FirstName}}Hi {{.FirstName}}, {{end}}we received a request to reset your Yimaru Academy password. The link below expires in {{.ExpiresMinutes}} minutes.

+

Reset password

+

If you did not request a reset, ignore this email.

+` + brandEmailFooter + + defaultWelcomeSubject = "Welcome to Yimaru Academy" + defaultWelcomeText = "Hi{{if .FirstName}} {{.FirstName}}{{end}},\n\nWelcome to Yimaru Academy! Sign in to get started: {{.LoginURL}}" + defaultWelcomeHTML = brandEmailHeader + ` +

Welcome aboard

+

{{if .FirstName}}Hi {{.FirstName}}, {{end}}your Yimaru Academy account is ready. Start learning at your own pace.

+

Sign in to Yimaru Academy

+` + brandEmailFooter + + defaultCustomMessageHTML = brandEmailHeader + ` +

{{.Subject}}

+
{{.Message}}
+` + brandEmailFooter +) diff --git a/internal/services/emailtemplates/defaults.go b/internal/services/emailtemplates/defaults.go index 4551646..1ce84a6 100644 --- a/internal/services/emailtemplates/defaults.go +++ b/internal/services/emailtemplates/defaults.go @@ -7,49 +7,49 @@ import ( var defaultTemplates = map[string]domain.EmailTemplate{ domain.EmailTemplateSlugOTP: { - Slug: domain.EmailTemplateSlugOTP, - Name: "One-Time Password", - Subject: "Yimaru - One Time Password", - BodyText: "Welcome to Yimaru Online Learning Platform{{if .FirstName}}, {{.FirstName}}{{end}}. Your OTP is {{.OTP}}. It expires in {{.ExpiresMinutes}} minutes. Please do not share it with anyone.", - BodyHTML: "

Welcome to Yimaru Online Learning Platform{{if .FirstName}}, {{.FirstName}}{{end}}.

Your one-time password is {{.OTP}}.

It expires in {{.ExpiresMinutes}} minutes. Please do not share it with anyone.

", + Slug: domain.EmailTemplateSlugOTP, + Name: "One-Time Password", + Subject: defaultOTPSubject, + BodyText: defaultOTPText, + BodyHTML: defaultOTPHTML, Variables: []string{"OTP", "FirstName", "ExpiresMinutes"}, - Status: domain.EmailTemplateStatusActive, + Status: domain.EmailTemplateStatusActive, }, domain.EmailTemplateSlugInvitation: { - Slug: domain.EmailTemplateSlugInvitation, - Name: "User Invitation", - Subject: "You are invited to join Yimaru", - BodyText: "Hi{{if .FirstName}} {{.FirstName}}{{end}}, you have been invited{{if .InviterName}} by {{.InviterName}}{{end}} to join Yimaru Online Learning Platform. Accept your invitation: {{.InviteLink}}", - BodyHTML: "

Hi{{if .FirstName}} {{.FirstName}}{{end}},

You have been invited{{if .InviterName}} by {{.InviterName}}{{end}} to join Yimaru Online Learning Platform.

Accept your invitation

", + Slug: domain.EmailTemplateSlugInvitation, + Name: "User Invitation", + Subject: defaultInvitationSubject, + BodyText: defaultInvitationText, + BodyHTML: defaultInvitationHTML, Variables: []string{"FirstName", "InviterName", "InviteLink"}, - Status: domain.EmailTemplateStatusActive, + Status: domain.EmailTemplateStatusActive, }, domain.EmailTemplateSlugPasswordReset: { - Slug: domain.EmailTemplateSlugPasswordReset, - Name: "Password Reset", - Subject: "Reset your Yimaru password", - BodyText: "Hi{{if .FirstName}} {{.FirstName}}{{end}}, use this link to reset your password: {{.ResetLink}}. The link expires in {{.ExpiresMinutes}} minutes.", - BodyHTML: "

Hi{{if .FirstName}} {{.FirstName}}{{end}},

Use the link below to reset your password. It expires in {{.ExpiresMinutes}} minutes.

Reset your password

", + Slug: domain.EmailTemplateSlugPasswordReset, + Name: "Password Reset", + Subject: defaultPasswordResetSubject, + BodyText: defaultPasswordResetText, + BodyHTML: defaultPasswordResetHTML, Variables: []string{"FirstName", "ResetLink", "ExpiresMinutes"}, - Status: domain.EmailTemplateStatusActive, + Status: domain.EmailTemplateStatusActive, }, domain.EmailTemplateSlugWelcome: { - Slug: domain.EmailTemplateSlugWelcome, - Name: "Welcome Email", - Subject: "Welcome to Yimaru", - BodyText: "Hi{{if .FirstName}} {{.FirstName}}{{end}}, welcome to Yimaru Online Learning Platform! Sign in at {{.LoginURL}} to get started.", - BodyHTML: "

Hi{{if .FirstName}} {{.FirstName}}{{end}},

Welcome to Yimaru Online Learning Platform!

Sign in to get started

", + Slug: domain.EmailTemplateSlugWelcome, + Name: "Welcome Email", + Subject: defaultWelcomeSubject, + BodyText: defaultWelcomeText, + BodyHTML: defaultWelcomeHTML, Variables: []string{"FirstName", "LoginURL"}, - Status: domain.EmailTemplateStatusActive, + Status: domain.EmailTemplateStatusActive, }, domain.EmailTemplateSlugCustomMessage: { - Slug: domain.EmailTemplateSlugCustomMessage, - Name: "Custom Message", - Subject: "{{.Subject}}", - BodyText: "{{.Message}}", - BodyHTML: "

{{.Message}}

", + Slug: domain.EmailTemplateSlugCustomMessage, + Name: "Custom Message", + Subject: "{{.Subject}}", + BodyText: "{{.Message}}", + BodyHTML: defaultCustomMessageHTML, Variables: []string{"Subject", "Message"}, - Status: domain.EmailTemplateStatusActive, + Status: domain.EmailTemplateStatusActive, }, }