email.ts 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. import { Resend } from 'resend';
  2. const resend = new Resend(process.env.RESEND_API_KEY);
  3. interface SendEmailParams {
  4. to: string;
  5. subject: string;
  6. html: string;
  7. }
  8. export async function sendEmail({ to, subject, html }: SendEmailParams) {
  9. try {
  10. console.log('准备发送邮件:', {
  11. to,
  12. subject,
  13. from: process.env.RESEND_FROM_EMAIL,
  14. apiKeyExists: !!process.env.RESEND_API_KEY
  15. });
  16. if (!process.env.RESEND_API_KEY) {
  17. console.error('RESEND_API_KEY未设置');
  18. return { success: false, error: 'RESEND_API_KEY未设置' };
  19. }
  20. if (!process.env.RESEND_FROM_EMAIL) {
  21. console.error('RESEND_FROM_EMAIL未设置');
  22. return { success: false, error: 'RESEND_FROM_EMAIL未设置' };
  23. }
  24. const data = await resend.emails.send({
  25. from: process.env.RESEND_FROM_EMAIL!,
  26. to,
  27. subject,
  28. html,
  29. });
  30. console.log('邮件发送成功:', data);
  31. return { success: true, data };
  32. } catch (error) {
  33. console.error('邮件发送失败详细信息:', {
  34. error: error instanceof Error ? error.message : error,
  35. stack: error instanceof Error ? error.stack : undefined,
  36. to,
  37. subject
  38. });
  39. return { success: false, error };
  40. }
  41. }
  42. export function generateVerificationEmailHtml(verificationUrl: string, locale: string = 'en') {
  43. const isZh = locale === 'zh';
  44. return `
  45. <!DOCTYPE html>
  46. <html>
  47. <head>
  48. <meta charset="utf-8">
  49. <title>${isZh ? '邮箱验证' : 'Email Verification'}</title>
  50. <style>
  51. body {
  52. font-family: Arial, sans-serif;
  53. line-height: 1.6;
  54. color: #333;
  55. max-width: 600px;
  56. margin: 0 auto;
  57. padding: 20px;
  58. }
  59. .header {
  60. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  61. color: white;
  62. padding: 30px;
  63. text-align: center;
  64. border-radius: 10px 10px 0 0;
  65. }
  66. .content {
  67. background: #f9f9f9;
  68. padding: 30px;
  69. border-radius: 0 0 10px 10px;
  70. }
  71. .button {
  72. display: inline-block;
  73. background: #667eea;
  74. color: white;
  75. padding: 12px 30px;
  76. text-decoration: none;
  77. border-radius: 5px;
  78. margin: 20px 0;
  79. }
  80. .footer {
  81. text-align: center;
  82. margin-top: 30px;
  83. color: #666;
  84. font-size: 14px;
  85. }
  86. </style>
  87. </head>
  88. <body>
  89. <div class="header">
  90. <h1>Aiartools</h1>
  91. <h2>${isZh ? '邮箱验证' : 'Email Verification'}</h2>
  92. </div>
  93. <div class="content">
  94. <p>${isZh ? '您好!' : 'Hello!'}</p>
  95. <p>${isZh ? '感谢您注册Aiartools!请点击下面的按钮验证您的邮箱地址:' : 'Thank you for signing up for Aiartools! Please click the button below to verify your email address:'}</p>
  96. <div style="text-align: center;">
  97. <a href="${verificationUrl}" class="button">
  98. ${isZh ? '验证邮箱' : 'Verify Email'}
  99. </a>
  100. </div>
  101. <p>${isZh ? '或者复制以下链接到浏览器中:' : 'Or copy and paste this link into your browser:'}</p>
  102. <p style="word-break: break-all; background: #eee; padding: 10px; border-radius: 5px;">
  103. ${verificationUrl}
  104. </p>
  105. <p style="margin-top: 30px;">
  106. ${isZh ? '此链接将在24小时后过期。' : 'This link will expire in 24 hours.'}
  107. </p>
  108. <p>
  109. ${isZh ? '如果您没有注册Aiartools账户,请忽略此邮件。' : 'If you did not sign up for Aiartools, please ignore this email.'}
  110. </p>
  111. </div>
  112. <div class="footer">
  113. <p>&copy; 2025 Aiartools. ${isZh ? '保留所有权利。' : 'All rights reserved.'}</p>
  114. </div>
  115. </body>
  116. </html>
  117. `;
  118. }
  119. export function generatePasswordResetEmailHtml(resetUrl: string, locale: string = 'zh'): string {
  120. const isZh = locale === 'zh';
  121. return `
  122. <!DOCTYPE html>
  123. <html lang="${locale}">
  124. <head>
  125. <meta charset="UTF-8">
  126. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  127. <title>${isZh ? 'Aiartools - 密码重置' : 'Aiartools - Password Reset'}</title>
  128. <style>
  129. body {
  130. font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  131. line-height: 1.6;
  132. color: #333;
  133. max-width: 600px;
  134. margin: 0 auto;
  135. padding: 20px;
  136. background-color: #f8fafc;
  137. }
  138. .container {
  139. background-color: white;
  140. border-radius: 12px;
  141. padding: 40px;
  142. box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  143. }
  144. .header {
  145. text-align: center;
  146. margin-bottom: 30px;
  147. }
  148. .logo {
  149. font-size: 32px;
  150. font-weight: bold;
  151. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  152. -webkit-background-clip: text;
  153. -webkit-text-fill-color: transparent;
  154. background-clip: text;
  155. margin-bottom: 10px;
  156. }
  157. .title {
  158. font-size: 24px;
  159. color: #1f2937;
  160. margin: 20px 0;
  161. }
  162. .content {
  163. font-size: 16px;
  164. color: #4b5563;
  165. margin-bottom: 30px;
  166. }
  167. .button {
  168. display: inline-block;
  169. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  170. color: white;
  171. text-decoration: none;
  172. padding: 15px 30px;
  173. border-radius: 8px;
  174. font-weight: 600;
  175. margin: 20px 0;
  176. transition: transform 0.2s;
  177. }
  178. .button:hover {
  179. transform: translateY(-2px);
  180. }
  181. .footer {
  182. text-align: center;
  183. margin-top: 40px;
  184. font-size: 14px;
  185. color: #9ca3af;
  186. border-top: 1px solid #e5e7eb;
  187. padding-top: 20px;
  188. }
  189. .warning {
  190. background-color: #fef3c7;
  191. border: 1px solid #f59e0b;
  192. border-radius: 8px;
  193. padding: 15px;
  194. margin: 20px 0;
  195. font-size: 14px;
  196. color: #92400e;
  197. }
  198. </style>
  199. </head>
  200. <body>
  201. <div class="container">
  202. <div class="header">
  203. <div class="logo">Aiartools</div>
  204. <h1 class="title">${isZh ? '密码重置请求' : 'Password Reset Request'}</h1>
  205. </div>
  206. <div class="content">
  207. <p>${isZh ? '您好!' : 'Hello!'}</p>
  208. <p>${isZh
  209. ? '我们收到了您的密码重置请求。点击下面的按钮来重置您的密码:'
  210. : 'We received a request to reset your password. Click the button below to reset your password:'
  211. }</p>
  212. <div style="text-align: center;">
  213. <a href="${resetUrl}" class="button">
  214. ${isZh ? '重置密码' : 'Reset Password'}
  215. </a>
  216. </div>
  217. <div class="warning">
  218. <p><strong>${isZh ? '重要提示:' : 'Important:'}</strong></p>
  219. <ul>
  220. <li>${isZh ? '此链接将在24小时后过期' : 'This link will expire in 24 hours'}</li>
  221. <li>${isZh ? '如果您没有请求重置密码,请忽略此邮件' : 'If you did not request this password reset, please ignore this email'}</li>
  222. <li>${isZh ? '为了安全,请不要将此链接分享给他人' : 'For security reasons, do not share this link with others'}</li>
  223. </ul>
  224. </div>
  225. <p>${isZh
  226. ? '如果按钮无法点击,请复制以下链接到浏览器中打开:'
  227. : 'If the button doesn\'t work, copy and paste this link into your browser:'
  228. }</p>
  229. <p style="word-break: break-all; color: #667eea; font-size: 14px;">
  230. ${resetUrl}
  231. </p>
  232. </div>
  233. <div class="footer">
  234. <p>${isZh ? '此邮件由 Aiartools 自动发送,请勿回复。' : 'This email was sent automatically by Aiartools. Please do not reply.'}</p>
  235. <p>&copy; 2025 Aiartools. ${isZh ? '保留所有权利。' : 'All rights reserved.'}</p>
  236. </div>
  237. </div>
  238. </body>
  239. </html>
  240. `;
  241. }