diff --git a/internal/service/user_service.go b/internal/service/user_service.go
index a22ee7e..f63bbb4 100644
--- a/internal/service/user_service.go
+++ b/internal/service/user_service.go
@@ -130,8 +130,7 @@ func (svc *UserService) SendEmailVerifyCode(ctx context.Context, emailAddr strin
code := fmt.Sprintf("%06d", rand.Intn(1000000))
- subject := "Your verification code"
- body := fmt.Sprintf("Your verification code is: %s\nIt will expire in 10 minutes.", code)
+ subject, body := email.BuildVerifyCodeEmail(emailAddr, code)
if err := email.Send(ctx, emailAddr, subject, body); err != nil {
log.Error(ctx, "func", "SendEmailVerifyCode", "msg", "send email error", "error", err)
return err
diff --git a/pkg/email/template.go b/pkg/email/template.go
new file mode 100644
index 0000000..adf82c3
--- /dev/null
+++ b/pkg/email/template.go
@@ -0,0 +1,166 @@
+package email
+
+import "fmt"
+
+// BuildVerifyCodeEmail returns a locale-appropriate subject and HTML body for
+// the verification code email. Chinese domains get a Chinese email; all others
+// get an English one.
+func BuildVerifyCodeEmail(toEmail, code string) (subject, body string) {
+ domain := toEmail[lastIndex(toEmail, '@')+1:]
+ if chineseDomainRe.MatchString(domain) {
+ return buildVerifyCodeZH(code)
+ }
+ return buildVerifyCodeEN(code)
+}
+
+func buildVerifyCodeZH(code string) (subject, body string) {
+ subject = "您的验证码"
+ body = fmt.Sprintf(`
+
+
+
+
+验证码
+
+
+
+
+
+
+
+
+
+ |
+ TexPixel
+ |
+
+
+
+
+ |
+ 验证您的邮箱
+
+ 请使用以下验证码完成注册。验证码仅对您本人有效,请勿分享给他人。
+
+
+
+
+
+
+ 验证码 10 分钟内有效,请尽快使用
+
+ |
+
+
+
+
+ |
+
+ |
+
+
+
+
+ |
+
+ 如果您没有请求此验证码,可以忽略本邮件,您的账户仍然安全。
+ © 2025 TexPixel. 保留所有权利。
+
+ |
+
+
+
+ |
+
+
+
+`, code)
+ return
+}
+
+func buildVerifyCodeEN(code string) (subject, body string) {
+ subject = "Your verification code"
+ body = fmt.Sprintf(`
+
+
+
+
+Verification Code
+
+
+
+
+
+
+
+
+
+ |
+ TexPixel
+ |
+
+
+
+
+ |
+ Verify your email address
+
+ Use the verification code below to complete your registration. Never share this code with anyone.
+
+
+
+
+
+
+ This code expires in 10 minutes
+
+ |
+
+
+
+
+ |
+
+ |
+
+
+
+
+ |
+
+ If you didn’t request this code, you can safely ignore this email. Your account is still secure.
+ © 2025 TexPixel. All rights reserved.
+
+ |
+
+
+
+ |
+
+
+
+`, code)
+ return
+}
+
+// lastIndex returns the last index of sep in s, or -1.
+func lastIndex(s string, sep byte) int {
+ for i := len(s) - 1; i >= 0; i-- {
+ if s[i] == sep {
+ return i
+ }
+ }
+ return -1
+}