thread-master/apps/backend/internal/module/notification/domain/hermes_template.go

138 lines
4.0 KiB
Go
Raw Permalink Normal View History

2026-07-10 12:54:45 +00:00
package domain
import (
"fmt"
"github.com/matcornic/hermes/v2"
)
// BuildHermesHTML renders a hermes email skeleton (same stack as stand-alone notification).
func BuildHermesHTML(brand Brand, body hermes.Body) (string, error) {
if brand.Name == "" {
brand.Name = "Harbor Desk"
}
h := hermes.Hermes{
Product: hermes.Product{
Name: brand.Name,
Link: brand.Link,
Logo: brand.LogoURL,
Copyright: brand.Copyright,
},
// Default theme is clean table layout suitable for most clients.
}
return h.GenerateHTML(hermes.Email{Body: body})
}
// GenerateTemplate builds subject + HTML for a kind/lang with placeholders still present
// ({{.Username}}, {{.VerifyCode}}, {{.ResetURL}} filled later by text/template).
func GenerateTemplate(kind TemplateKind, lang Language, brand Brand) (*EmailTemplate, error) {
lang = NormalizeLanguage(string(lang))
switch kind {
case TemplateEmailVerify:
return generateEmailVerify(lang, brand)
case TemplatePasswordReset:
return generatePasswordReset(lang, brand)
default:
return nil, fmt.Errorf("unknown template kind: %s", kind)
}
}
func generateEmailVerify(lang Language, brand Brand) (*EmailTemplate, error) {
var subject string
var intros, outros []string
var instr string
if lang == LangEnUS {
subject = "[Harbor Desk] Verify your email"
intros = []string{
"You received this email because we need to verify your email address.",
}
instr = "Copy this verification code and enter it on the verify page (valid for 30 minutes):"
outros = []string{
"If you did not create an account, you can ignore this email.",
"This is an automated message — please do not reply.",
}
} else {
subject = "【Harbor Desk】信箱驗證碼"
intros = []string{
"您收到此信是因為需要驗證您的電子信箱。",
}
instr = "請複製下方驗證碼,於 30 分鐘內到驗證頁完成確認:"
outros = []string{
"若您並未註冊帳號,可忽略本信。",
"此為系統自動發出,請勿直接回覆。",
}
}
html, err := BuildHermesHTML(brand, hermes.Body{
Name: "{{.Username}}",
Intros: intros,
Actions: []hermes.Action{
{
Instructions: instr,
InviteCode: "{{.VerifyCode}}",
},
},
Outros: outros,
Signature: " ",
})
if err != nil {
return nil, err
}
return &EmailTemplate{Subject: subject, Body: html}, nil
}
func generatePasswordReset(lang Language, brand Brand) (*EmailTemplate, error) {
var subject, instr, btnText string
var intros, outros []string
if lang == LangEnUS {
subject = "[Harbor Desk] Reset your password"
intros = []string{
"You received this email because a password reset was requested for your account.",
}
instr = "Copy this code, or open the reset page with the button below (valid for 30 minutes):"
btnText = "Open reset page"
outros = []string{
"If you did not request a password reset, no further action is required.",
"This is an automated message — please do not reply.",
}
} else {
subject = "【Harbor Desk】重設密碼"
intros = []string{
"您收到此信是因為有人申請重設您的帳戶密碼。",
}
instr = "請複製下方驗證碼或點擊按鈕開啟重設頁30 分鐘內有效):"
btnText = "開啟重設密碼頁"
outros = []string{
"若您並未申請重設密碼,可忽略本信,帳號不會有任何變更。",
"此為系統自動發出,請勿直接回覆。",
}
}
actions := []hermes.Action{
{
Instructions: instr,
InviteCode: "{{.VerifyCode}}",
},
}
// Button only when ResetURL placeholder present after render — always include;
// empty href is avoided by filling ResetURL before send.
actions = append(actions, hermes.Action{
Button: hermes.Button{
Color: "#0f172a",
Text: btnText,
Link: "{{.ResetURL}}",
},
})
html, err := BuildHermesHTML(brand, hermes.Body{
Name: "{{.Username}}",
Intros: intros,
Actions: actions,
Outros: outros,
Signature: " ",
})
if err != nil {
return nil, err
}
return &EmailTemplate{Subject: subject, Body: html}, nil
}