feat: upgrade verification code email with bilingual HTML template
- Chinese domains (qq.com, 163.com, etc.) receive a Chinese email - All other domains receive an English email - Prominent code display: 40px monospace with wide letter-spacing - Clean OpenAI-inspired layout with dark header and card design Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
166
pkg/email/template.go
Normal file
166
pkg/email/template.go
Normal file
@@ -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(`<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>验证码</title>
|
||||
</head>
|
||||
<body style="margin:0;padding:0;background:#f4f4f5;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;">
|
||||
<table width="100%%" cellpadding="0" cellspacing="0" style="background:#f4f4f5;padding:40px 0;">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table width="480" cellpadding="0" cellspacing="0" style="background:#ffffff;border-radius:12px;overflow:hidden;box-shadow:0 1px 4px rgba(0,0,0,.08);">
|
||||
|
||||
<!-- Header -->
|
||||
<tr>
|
||||
<td style="background:#0a0a0a;padding:32px 40px;">
|
||||
<span style="color:#ffffff;font-size:20px;font-weight:700;letter-spacing:-0.3px;">TexPixel</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Body -->
|
||||
<tr>
|
||||
<td style="padding:40px 40px 32px;">
|
||||
<p style="margin:0 0 8px;font-size:24px;font-weight:700;color:#0a0a0a;line-height:1.3;">验证您的邮箱</p>
|
||||
<p style="margin:0 0 32px;font-size:15px;color:#6b7280;line-height:1.6;">
|
||||
请使用以下验证码完成注册。验证码仅对您本人有效,请勿分享给他人。
|
||||
</p>
|
||||
|
||||
<!-- Code block -->
|
||||
<table width="100%%" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td align="center" style="background:#f9fafb;border:1px solid #e5e7eb;border-radius:8px;padding:28px 0;">
|
||||
<span style="font-size:40px;font-weight:700;letter-spacing:12px;color:#0a0a0a;font-family:'Courier New',Courier,monospace;">%s</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="margin:24px 0 0;font-size:13px;color:#9ca3af;text-align:center;">
|
||||
验证码 <strong>10 分钟</strong>内有效,请尽快使用
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Divider -->
|
||||
<tr>
|
||||
<td style="padding:0 40px;">
|
||||
<div style="height:1px;background:#f3f4f6;"></div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer -->
|
||||
<tr>
|
||||
<td style="padding:24px 40px 32px;">
|
||||
<p style="margin:0;font-size:12px;color:#9ca3af;line-height:1.7;">
|
||||
如果您没有请求此验证码,可以忽略本邮件,您的账户仍然安全。<br/>
|
||||
© 2025 TexPixel. 保留所有权利。
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>`, code)
|
||||
return
|
||||
}
|
||||
|
||||
func buildVerifyCodeEN(code string) (subject, body string) {
|
||||
subject = "Your verification code"
|
||||
body = fmt.Sprintf(`<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Verification Code</title>
|
||||
</head>
|
||||
<body style="margin:0;padding:0;background:#f4f4f5;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;">
|
||||
<table width="100%%" cellpadding="0" cellspacing="0" style="background:#f4f4f5;padding:40px 0;">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table width="480" cellpadding="0" cellspacing="0" style="background:#ffffff;border-radius:12px;overflow:hidden;box-shadow:0 1px 4px rgba(0,0,0,.08);">
|
||||
|
||||
<!-- Header -->
|
||||
<tr>
|
||||
<td style="background:#0a0a0a;padding:32px 40px;">
|
||||
<span style="color:#ffffff;font-size:20px;font-weight:700;letter-spacing:-0.3px;">TexPixel</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Body -->
|
||||
<tr>
|
||||
<td style="padding:40px 40px 32px;">
|
||||
<p style="margin:0 0 8px;font-size:24px;font-weight:700;color:#0a0a0a;line-height:1.3;">Verify your email address</p>
|
||||
<p style="margin:0 0 32px;font-size:15px;color:#6b7280;line-height:1.6;">
|
||||
Use the verification code below to complete your registration. Never share this code with anyone.
|
||||
</p>
|
||||
|
||||
<!-- Code block -->
|
||||
<table width="100%%" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td align="center" style="background:#f9fafb;border:1px solid #e5e7eb;border-radius:8px;padding:28px 0;">
|
||||
<span style="font-size:40px;font-weight:700;letter-spacing:12px;color:#0a0a0a;font-family:'Courier New',Courier,monospace;">%s</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="margin:24px 0 0;font-size:13px;color:#9ca3af;text-align:center;">
|
||||
This code expires in <strong>10 minutes</strong>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Divider -->
|
||||
<tr>
|
||||
<td style="padding:0 40px;">
|
||||
<div style="height:1px;background:#f3f4f6;"></div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer -->
|
||||
<tr>
|
||||
<td style="padding:24px 40px 32px;">
|
||||
<p style="margin:0;font-size:12px;color:#9ca3af;line-height:1.7;">
|
||||
If you didn’t request this code, you can safely ignore this email. Your account is still secure.<br/>
|
||||
© 2025 TexPixel. All rights reserved.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>`, 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
|
||||
}
|
||||
Reference in New Issue
Block a user