173 lines
4.9 KiB
TypeScript
173 lines
4.9 KiB
TypeScript
import { Resend } from "resend";
|
||
import type { Env } from "../env";
|
||
|
||
/**
|
||
* 发送密码重置邮件
|
||
* @param env 环境变量
|
||
* @param to 收件人邮箱
|
||
* @param resetToken 重置令牌
|
||
* @param username 用户名
|
||
* @returns 发送结果
|
||
*/
|
||
export async function sendPasswordResetEmail(
|
||
env: Env,
|
||
to: string,
|
||
resetToken: string,
|
||
username: string
|
||
): Promise<{ success: boolean; error?: string }> {
|
||
const apiKey = env.RESEND_API_KEY;
|
||
const fromEmail = env.RESEND_FROM_EMAIL || "noreply@yourdomain.com";
|
||
|
||
if (!apiKey) {
|
||
return {
|
||
success: false,
|
||
error: "RESEND_API_KEY 未配置,请运行: npx wrangler secret put RESEND_API_KEY",
|
||
};
|
||
}
|
||
|
||
try {
|
||
const resend = new Resend(apiKey);
|
||
|
||
// 发送邮件
|
||
const { data, error } = await resend.emails.send({
|
||
from: fromEmail,
|
||
to: [to],
|
||
subject: "密码重置验证码 - 聊天室",
|
||
html: `
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<style>
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||
line-height: 1.6;
|
||
color: #333;
|
||
max-width: 600px;
|
||
margin: 0 auto;
|
||
padding: 20px;
|
||
}
|
||
.container {
|
||
background-color: #f9f9f9;
|
||
border-radius: 8px;
|
||
padding: 30px;
|
||
border: 1px solid #e0e0e0;
|
||
}
|
||
.header {
|
||
text-align: center;
|
||
margin-bottom: 30px;
|
||
}
|
||
.header h1 {
|
||
color: #2c3e50;
|
||
margin: 0;
|
||
}
|
||
.content {
|
||
background-color: white;
|
||
padding: 25px;
|
||
border-radius: 6px;
|
||
margin-bottom: 20px;
|
||
}
|
||
.code-box {
|
||
background-color: #f8f9fa;
|
||
border: 2px dashed #007bff;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
margin: 25px 0;
|
||
text-align: center;
|
||
}
|
||
.verification-code {
|
||
font-size: 32px;
|
||
font-weight: bold;
|
||
color: #007bff;
|
||
letter-spacing: 4px;
|
||
font-family: 'Courier New', monospace;
|
||
user-select: all;
|
||
}
|
||
.footer {
|
||
text-align: center;
|
||
color: #666;
|
||
font-size: 14px;
|
||
margin-top: 20px;
|
||
}
|
||
.warning {
|
||
background-color: #fff3cd;
|
||
border-left: 4px solid #ffc107;
|
||
padding: 12px;
|
||
margin: 15px 0;
|
||
border-radius: 4px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<div class="header">
|
||
<h1>🔐 密码重置验证码</h1>
|
||
</div>
|
||
|
||
<div class="content">
|
||
<p>你好,<strong>${username}</strong>!</p>
|
||
|
||
<p>我们收到了你的密码重置请求。请使用以下验证码来重置你的密码:</p>
|
||
|
||
<div class="code-box">
|
||
<div style="color: #666; font-size: 14px; margin-bottom: 10px;">验证码</div>
|
||
<div class="verification-code">${resetToken}</div>
|
||
</div>
|
||
|
||
<p style="text-align: center; color: #666; font-size: 14px;">
|
||
请在密码重置页面输入此验证码
|
||
</p>
|
||
|
||
<div class="warning">
|
||
<strong>⚠️ 重要提示:</strong>
|
||
<ul style="margin: 10px 0;">
|
||
<li>此验证码将在 <strong>30 分钟</strong>后失效</li>
|
||
<li>如果你没有请求重置密码,请忽略此邮件</li>
|
||
<li>请勿将此验证码分享给他人</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="footer">
|
||
<p>此邮件由系统自动发送,请勿回复。</p>
|
||
<p style="color: #999; font-size: 12px;">© 2026 聊天室应用</p>
|
||
</div>
|
||
</div>
|
||
</body>
|
||
</html>
|
||
`,
|
||
text: `
|
||
你好,${username}!
|
||
|
||
我们收到了你的密码重置请求。请使用以下验证码来重置你的密码:
|
||
|
||
验证码:${resetToken}
|
||
|
||
重要提示:
|
||
- 此验证码将在 30 分钟后失效
|
||
- 如果你没有请求重置密码,请忽略此邮件
|
||
- 请勿将此验证码分享给他人
|
||
|
||
此邮件由系统自动发送,请勿回复。
|
||
`.trim(),
|
||
});
|
||
|
||
if (error) {
|
||
console.error("Resend 发送邮件失败:", error);
|
||
return {
|
||
success: false,
|
||
error: error.message || "邮件发送失败",
|
||
};
|
||
}
|
||
|
||
console.log("密码重置邮件已发送:", data);
|
||
return { success: true };
|
||
} catch (error) {
|
||
console.error("发送邮件时出错:", error);
|
||
return {
|
||
success: false,
|
||
error: error instanceof Error ? error.message : "未知错误",
|
||
};
|
||
}
|
||
}
|