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, }, }