diff --git a/README.en.md b/README.en.md index bb2cef0..81cf3e1 100644 --- a/README.en.md +++ b/README.en.md @@ -83,37 +83,7 @@ ## 🚀 Quick Deployment -ZMAIL consists of two parts that need to be deployed separately: - -### 🖥️ Frontend Deployment - -
-

1️⃣ Deploy Frontend to Cloudflare Pages

- - Deploy Frontend to Cloudflare Pages - -
- -
-
    -
  1. Click the "Deploy to Cloudflare Pages" button
  2. -
  3. Connect your GitHub account and select this repository
  4. -
  5. Configure build settings: -
      -
    • Build command: yarn build
    • -
    • Build output directory: dist
    • -
    • Root directory (Advanced) -> Path: frontend
    • -
    -
  6. -
  7. Configure environment variables: -
      -
    • VITE_API_BASE_URL: Your Worker API base URL (e.g., https://api.mdzz.uk)
    • -
    • VITE_EMAIL_DOMAIN: Your Domain (e.g., mdzz.uk)
    • -
    -
  8. -
  9. Click "Save and Deploy"
  10. -
-
+ZMAIL consists of two parts that need to be deployed separately, deploy the backend first, then the frontend: ### ⚙️ Backend Deployment @@ -151,6 +121,38 @@ ZMAIL consists of two parts that need to be deployed separately: --- +### 🖥️ Frontend Deployment + +
+

1️⃣ Deploy Frontend to Cloudflare Pages

+ + Deploy Frontend to Cloudflare Pages + +
+ +
+
    +
  1. Click the "Deploy to Cloudflare Pages" button
  2. +
  3. Connect your GitHub account and select this repository
  4. +
  5. Configure build settings: +
      +
    • Build command: yarn build
    • +
    • Build output directory: dist
    • +
    • Root directory (Advanced) -> Path: frontend
    • +
    +
  6. +
  7. Configure environment variables: +
      +
    • VITE_API_BASE_URL: Your Worker API base URL (e.g., https://api.mdzz.uk)
    • +
    • VITE_EMAIL_DOMAIN: Your domain list, separated by ',' (e.g., mdzz.uk,zaunist.com)
    • +
    +
  8. +
  9. Click "Save and Deploy"
  10. +
+
+ +--- + ## 💻 Local Development ### 🎨 Frontend Development diff --git a/README.md b/README.md index 4e5fdb1..a706b75 100644 --- a/README.md +++ b/README.md @@ -83,37 +83,7 @@ ## 🚀 快速部署 -ZMAIL 由前端和后端两部分组成,需要分别部署: - -### 🖥️ 前端部署步骤 - -
-

1️⃣ 部署前端到 Cloudflare Pages

- - Deploy Frontend to Cloudflare Pages - -
- -
-
    -
  1. 点击"部署前端到 Cloudflare Pages"按钮
  2. -
  3. 连接您的GitHub账户并选择导入此仓库
  4. -
  5. 设置构建配置: -
      -
    • 构建命令: yarn build
    • -
    • 构建输出目录: dist
    • -
    • 根目录(高级)-> 路径: frontend
    • -
    -
  6. -
  7. 配置环境变量: -
      -
    • VITE_API_BASE_URL: 您的Worker API基础URL (例如: https://api.mdzz.uk)
    • -
    • VITE_EMAIL_DOMAIN: 您的域名 (例如: mdzz.uk)
    • -
    -
  8. -
  9. 点击"保存并部署"
  10. -
-
+ZMAIL 由前端和后端两部分组成,需要先部署后端,然后再部署前端: ### ⚙️ 后端部署步骤 @@ -151,6 +121,37 @@ ZMAIL 由前端和后端两部分组成,需要分别部署: --- +### 🖥️ 前端部署步骤 + +
+

1️⃣ 部署前端到 Cloudflare Pages

+ + Deploy Frontend to Cloudflare Pages + +
+ +
+
    +
  1. 点击"部署前端到 Cloudflare Pages"按钮
  2. +
  3. 连接您的GitHub账户并选择导入此仓库
  4. +
  5. 设置构建配置: +
      +
    • 构建命令: yarn build
    • +
    • 构建输出目录: dist
    • +
    • 根目录(高级)-> 路径: frontend
    • +
    +
  6. +
  7. 配置环境变量: +
      +
    • VITE_API_BASE_URL: 您的Worker API基础URL (例如: https://api.mdzz.uk)
    • +
    • VITE_EMAIL_DOMAIN: 您的域名列表,使用','分割 (例如: mdzz.uk,zaunist.com)
    • +
    +
  8. +
  9. 点击"保存并部署"
  10. +
+
+ + ## 💻 本地开发 ### 🎨 前端开发 diff --git a/frontend/.env.example b/frontend/.env.example index bf9ee5d..66fa124 100644 --- a/frontend/.env.example +++ b/frontend/.env.example @@ -1,5 +1,5 @@ -# 邮箱域名配置 -VITE_EMAIL_DOMAIN=example.com +# 邮箱域名配置(多域名以逗号分隔) +VITE_EMAIL_DOMAIN=example.com,abc.com,def.com # API地址配置 VITE_API_BASE_URL=https://api.example.com diff --git a/frontend/src/components/CreateMailboxForm.tsx b/frontend/src/components/CreateMailboxForm.tsx deleted file mode 100644 index c05e457..0000000 --- a/frontend/src/components/CreateMailboxForm.tsx +++ /dev/null @@ -1,225 +0,0 @@ -import React, { useState, useRef, useEffect } from 'react'; -import { useTranslation } from 'react-i18next'; - -interface CreateMailboxFormProps { - onMailboxCreated: (address: string) => void; -} - -const CreateMailboxForm: React.FC = ({ onMailboxCreated }) => { - const { t } = useTranslation(); - const [isCustom, setIsCustom] = useState(false); - const [customAddress, setCustomAddress] = useState(''); - const expiresInHours = 24; - const [isLoading, setIsLoading] = useState(false); - const [errorMessage, setErrorMessage] = useState(null); - const [successMessage, setSuccessMessage] = useState(null); - const errorTimeoutRef = useRef(null); - const successTimeoutRef = useRef(null); - - // 清除提示的定时器 - useEffect(() => { - return () => { - if (errorTimeoutRef.current) { - window.clearTimeout(errorTimeoutRef.current); - } - if (successTimeoutRef.current) { - window.clearTimeout(successTimeoutRef.current); - } - }; - }, []); - - const handleCreateRandom = async () => { - try { - // 清除之前的错误和成功信息 - setErrorMessage(null); - setSuccessMessage(null); - - setIsLoading(true); - const response = await fetch('/api/mailboxes', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - expiresInHours, - }), - }); - - if (!response.ok) { - throw new Error('Failed to create mailbox'); - } - - const data = await response.json(); - if (data.success) { - setSuccessMessage(t('mailbox.createSuccess')); - - // 2秒后调用回调函数 - if (successTimeoutRef.current) { - window.clearTimeout(successTimeoutRef.current); - } - successTimeoutRef.current = window.setTimeout(() => { - onMailboxCreated(data.mailbox.address); - }, 2000); - } else { - throw new Error(data.error || 'Unknown error'); - } - } catch (error) { - setErrorMessage(t('mailbox.createFailed')); - - // 3秒后清除错误信息 - if (errorTimeoutRef.current) { - window.clearTimeout(errorTimeoutRef.current); - } - errorTimeoutRef.current = window.setTimeout(() => { - setErrorMessage(null); - }, 3000); - } finally { - setIsLoading(false); - } - }; - - const handleCreateCustom = async (e: React.FormEvent) => { - e.preventDefault(); - - // 清除之前的错误和成功信息 - setErrorMessage(null); - setSuccessMessage(null); - - if (!customAddress.trim()) { - setErrorMessage(t('mailbox.invalidAddress')); - - // 3秒后清除错误信息 - if (errorTimeoutRef.current) { - window.clearTimeout(errorTimeoutRef.current); - } - errorTimeoutRef.current = window.setTimeout(() => { - setErrorMessage(null); - }, 3000); - return; - } - - try { - setIsLoading(true); - const response = await fetch('/api/mailboxes', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - address: customAddress.trim(), - expiresInHours, - }), - }); - - if (!response.ok) { - if (response.status === 400) { - setErrorMessage(t('mailbox.addressExists')); - - // 3秒后清除错误信息 - if (errorTimeoutRef.current) { - window.clearTimeout(errorTimeoutRef.current); - } - errorTimeoutRef.current = window.setTimeout(() => { - setErrorMessage(null); - }, 3000); - return; - } - throw new Error('Failed to create mailbox'); - } - - const data = await response.json(); - if (data.success) { - setSuccessMessage(t('mailbox.createSuccess')); - - // 2秒后调用回调函数 - if (successTimeoutRef.current) { - window.clearTimeout(successTimeoutRef.current); - } - successTimeoutRef.current = window.setTimeout(() => { - onMailboxCreated(data.mailbox.address); - }, 2000); - } else { - throw new Error(data.error || 'Unknown error'); - } - } catch (error) { - setErrorMessage(t('mailbox.createFailed')); - - // 3秒后清除错误信息 - if (errorTimeoutRef.current) { - window.clearTimeout(errorTimeoutRef.current); - } - errorTimeoutRef.current = window.setTimeout(() => { - setErrorMessage(null); - }, 3000); - } finally { - setIsLoading(false); - } - }; - - return ( -
- {/* 错误和成功提示 */} - {(errorMessage || successMessage) && ( -
- {errorMessage || successMessage} -
- )} - -
- - -
- - {isCustom ? ( -
-
-
- setCustomAddress(e.target.value)} - className="flex-1 px-3 py-2 border rounded-l-md focus:outline-none focus:ring-2 focus:ring-primary" - placeholder={t('mailbox.customAddressPlaceholder')} - disabled={isLoading} - /> - -
-
-
- ) : ( - - )} -
- ); -}; - -export default CreateMailboxForm; \ No newline at end of file diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index 520f81e..27c0f8a 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next'; import LanguageSwitcher from './LanguageSwitcher'; import HeaderMailbox from './HeaderMailbox'; import Container from './Container'; -import { EMAIL_DOMAIN } from '../config'; +import { EMAIL_DOMAINS, DEFAULT_EMAIL_DOMAIN } from '../config'; interface HeaderProps { mailbox: Mailbox | null; @@ -32,7 +32,8 @@ const Header: React.FC = ({
diff --git a/frontend/src/components/HeaderMailbox.tsx b/frontend/src/components/HeaderMailbox.tsx index 173a0f0..5e528a6 100644 --- a/frontend/src/components/HeaderMailbox.tsx +++ b/frontend/src/components/HeaderMailbox.tsx @@ -6,6 +6,7 @@ interface HeaderMailboxProps { mailbox: Mailbox | null; onMailboxChange: (mailbox: Mailbox) => void; domain: string; + domains: string[]; isLoading: boolean; } @@ -13,11 +14,13 @@ const HeaderMailbox: React.FC = ({ mailbox, onMailboxChange, domain, + domains, isLoading }) => { const { t } = useTranslation(); const [isCustomMode, setIsCustomMode] = useState(false); const [customAddress, setCustomAddress] = useState(''); + const [selectedDomain, setSelectedDomain] = useState(domain); const [isActionLoading, setIsActionLoading] = useState(false); const [showCopyTooltip, setShowCopyTooltip] = useState(false); const [copyError, setCopyError] = useState(null); @@ -30,6 +33,11 @@ const HeaderMailbox: React.FC = ({ const successMessageTimeoutRef = useRef(null); const errorMessageTimeoutRef = useRef(null); + // 当props中的domain变化时更新selectedDomain + useEffect(() => { + setSelectedDomain(domain); + }, [domain]); + // 清除提示的定时器 useEffect(() => { return () => { @@ -55,7 +63,7 @@ const HeaderMailbox: React.FC = ({ // 清除之前的错误信息 setCopyError(null); - const fullAddress = mailbox.address.includes('@') ? mailbox.address : `${mailbox.address}@${domain}`; + const fullAddress = mailbox.address.includes('@') ? mailbox.address : `${mailbox.address}@${selectedDomain}`; navigator.clipboard.writeText(fullAddress) .then(() => { // 显示复制成功提示 @@ -175,7 +183,7 @@ const HeaderMailbox: React.FC = ({ // 移动设备上的邮箱地址显示 const renderMobileAddress = () => { - const fullAddress = mailbox.address.includes('@') ? mailbox.address : `${mailbox.address}@${domain}`; + const fullAddress = mailbox.address.includes('@') ? mailbox.address : `${mailbox.address}@${selectedDomain}`; const [username, domainPart] = fullAddress.split('@'); // 如果用户名太长,截断显示 @@ -188,6 +196,11 @@ const HeaderMailbox: React.FC = ({ ); }; + // 切换域名 + const handleDomainChange = (e: React.ChangeEvent) => { + setSelectedDomain(e.target.value); + }; + // 按钮基础样式 const buttonBaseClass = "flex items-center justify-center rounded-md transition-all duration-200"; const copyButtonClass = `${buttonBaseClass} hover:bg-primary/20 hover:text-primary hover:scale-110 mx-1`; @@ -215,8 +228,17 @@ const HeaderMailbox: React.FC = ({ disabled={isActionLoading} autoFocus /> - - @{domain} + + @ +
@@ -323,12 +353,12 @@ const HeaderMailbox: React.FC = ({ {renderMobileAddress()}
- @@ -340,43 +370,43 @@ const HeaderMailbox: React.FC = ({
)} - -
- - - {/* 更新成功提示 */} - {showRefreshSuccess && ( -
- {t('mailbox.refreshSuccess')} -
-
- )} -
- - - {/* 错误信息显示 */} - {(copyError || refreshError) && ( +
+ + + {/* 更新成功提示 */} + {showRefreshSuccess && ( +
+ {t('mailbox.refreshSuccess')} +
+
+ )} +
+ + + + + {/* 错误信息显示 */} + {(copyError || refreshError) && (
- {copyError || refreshError} -
+ {copyError || refreshError} + )} - )} diff --git a/frontend/src/config.ts b/frontend/src/config.ts index dc747d5..6097d8e 100644 --- a/frontend/src/config.ts +++ b/frontend/src/config.ts @@ -1,7 +1,8 @@ // 配置文件,用于管理域名和API地址设置 // 邮箱域名配置 -export const EMAIL_DOMAIN = import.meta.env.VITE_EMAIL_DOMAIN; +export const EMAIL_DOMAINS = (import.meta.env.VITE_EMAIL_DOMAIN || '').split(',').map(domain => domain.trim()); +export const DEFAULT_EMAIL_DOMAIN = EMAIL_DOMAINS[0] || ''; // API地址配置 export const API_BASE_URL = import.meta.env.VITE_API_BASE_URL; diff --git a/worker/src/database.ts b/worker/src/database.ts index 439e7f8..3899c4d 100644 --- a/worker/src/database.ts +++ b/worker/src/database.ts @@ -7,8 +7,7 @@ import { EmailListItem, Attachment, AttachmentListItem, - SaveAttachmentParams, - AttachmentChunk + SaveAttachmentParams } from './types'; import { generateId, diff --git a/worker/src/email-builder.ts b/worker/src/email-builder.ts deleted file mode 100644 index d9337f5..0000000 --- a/worker/src/email-builder.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { generateId } from './utils'; - -/** - * 构建MIME格式的邮件 - * @param fromAddress 发件人地址 - * @param toAddress 收件人地址 - * @param toName 收件人名称 - * @param subject 邮件主题 - * @param textContent 纯文本内容 - * @param htmlContent HTML内容 - * @returns 原始MIME格式邮件 - */ -export function buildMimeEmail( - fromAddress: string, - toAddress: string, - toName: string = '', - subject: string, - textContent: string = '', - htmlContent: string = '' -): string { - // 生成邮件边界 - const boundary = `----=_Part_${Date.now().toString(16)}`; - - // 构建邮件头 - const headers = [ - `From: ${fromAddress}`, - `To: ${toName ? `${toName} <${toAddress}>` : toAddress}`, - `Subject: ${subject}`, - `Date: ${new Date().toUTCString()}`, - `Message-ID: <${generateId()}@${fromAddress.split('@')[1]}>`, - 'MIME-Version: 1.0', - `Content-Type: multipart/alternative; boundary="${boundary}"`, - '', - '' - ].join('\r\n'); - - // 构建邮件内容 - let body = ''; - - // 添加纯文本部分 - if (textContent) { - body += [ - `--${boundary}`, - 'Content-Type: text/plain; charset=UTF-8', - 'Content-Transfer-Encoding: 7bit', - '', - textContent, - '' - ].join('\r\n'); - } - - // 添加HTML部分 - if (htmlContent) { - body += [ - `--${boundary}`, - 'Content-Type: text/html; charset=UTF-8', - 'Content-Transfer-Encoding: 7bit', - '', - htmlContent, - '' - ].join('\r\n'); - } - - // 添加结束边界 - body += `--${boundary}--\r\n`; - - // 完整的原始邮件 - return headers + body; -} - -/** - * 解析原始邮件内容 - * @param rawEmail 原始邮件内容 - * @returns 解析后的邮件对象 - */ -export async function parseRawEmail(rawEmail: ArrayBuffer): Promise { - try { - // 动态导入PostalMime - const PostalMimeModule = await import('postal-mime'); - - // 获取构造函数 - const PostalMimeConstructor = PostalMimeModule.default || PostalMimeModule; - - // 创建解析器实例 - const parser = new PostalMimeConstructor(); - - // 解析邮件 - return await parser.parse(rawEmail); - } catch (error) { - console.error('PostalMime解析错误:', error); - throw error; // 重新抛出错误,让调用者处理 - } -} \ No newline at end of file diff --git a/worker/src/index.ts b/worker/src/index.ts index 72fc576..db6323a 100644 --- a/worker/src/index.ts +++ b/worker/src/index.ts @@ -43,7 +43,6 @@ export default { // 处理邮件 async email(message: any, env: Env, ctx: ExecutionContext): Promise { try { - await initializeDatabase(env.DB); await handleEmail(message, env); } catch (error) { console.error('处理邮件失败:', error); @@ -54,7 +53,6 @@ export default { // 定时任务 - 每小时清理过期邮箱以及过期邮件和已被阅读的邮件 async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext): Promise { try { - await initializeDatabase(env.DB); const deleted = await cleanupExpiredMailboxes(env.DB); console.log(`已清理 ${deleted} 个过期邮箱`); const deletedMail = await cleanupExpiredMails(env.DB); diff --git a/worker/src/routes.ts b/worker/src/routes.ts index 1357ca8..5be43b4 100644 --- a/worker/src/routes.ts +++ b/worker/src/routes.ts @@ -1,6 +1,6 @@ import { Hono } from 'hono'; import { cors } from 'hono/cors'; -import { Env, SendEmailParams, ParsedEmail } from './types'; +import { Env} from './types'; import { createMailbox, getMailbox, @@ -11,8 +11,7 @@ import { getAttachments, getAttachment } from './database'; -import { generateRandomAddress, isValidEmailAddress, sendEmail } from './utils'; -import { parseRawEmail } from './email-builder'; +import { generateRandomAddress, } from './utils'; // 创建 Hono 应用 const app = new Hono<{ Bindings: Env }>(); @@ -30,123 +29,6 @@ app.get('/', (c) => { return c.json({ status: 'ok', message: '临时邮箱系统API正常运行' }); }); -// 发送邮件API,暂时不用 -app.post('/api/send-email', async (c) => { - try { - const body = await c.req.json() as SendEmailParams; - - // 验证必要参数 - if (!body.fromAddress || typeof body.fromAddress !== 'string') { - return c.json({ success: false, error: '发件人地址不能为空' }, 400); - } - - if (!body.toAddress || typeof body.toAddress !== 'string') { - return c.json({ success: false, error: '收件人地址不能为空' }, 400); - } - - if (!isValidEmailAddress(body.toAddress)) { - return c.json({ success: false, error: '收件人邮箱地址格式无效' }, 400); - } - - if (!body.subject || typeof body.subject !== 'string') { - return c.json({ success: false, error: '邮件主题不能为空' }, 400); - } - - if (!body.textContent && !body.htmlContent) { - return c.json({ success: false, error: '邮件内容不能为空' }, 400); - } - - // // 查找发件人邮箱是否存在 - // const mailbox = await getMailbox(c.env.DB, body.fromAddress); - - // if (!mailbox) { - // return c.json({ success: false, error: '发件人邮箱不存在' }, 404); - // } - - // 发送邮件 - const result = await sendEmail( - body.fromAddress, - body.toAddress, - body.toName || '', - body.subject, - body.textContent || '', - body.htmlContent || '' - ); - - if (result.success) { - return c.json({ - success: true, - message: '邮件发送成功' - }); - } else { - return c.json({ - success: false, - error: result.error || '邮件发送失败' - }, 500); - } - } catch (error) { - console.error('发送邮件失败:', error); - return c.json({ - success: false, - error: '发送邮件失败', - message: error instanceof Error ? error.message : String(error) - }, 500); - } -}); - -// 接收原始邮件并解析,用于测试解析邮件功能 -app.post('/api/parse-email', async (c) => { - try { - console.log('接收到解析邮件请求'); - - // 获取请求体中的原始邮件内容 - const body = await c.req.text(); - - if (!body) { - console.log('未提供邮件内容'); - return c.json({ success: false, error: '未提供邮件内容' }, 400); - } - - console.log(`接收到邮件内容,长度: ${body.length} 字节`); - - // 将文本内容转换为ArrayBuffer - const encoder = new TextEncoder(); - const rawEmail = encoder.encode(body).buffer; - - // 使用PostalMime解析邮件 - const parsedEmail = await parseRawEmail(rawEmail) as ParsedEmail; - - console.log('邮件解析成功:', { - subject: parsedEmail.subject, - from: parsedEmail.from, - to: parsedEmail.to?.length || 0, - hasText: !!parsedEmail.text, - hasHtml: !!parsedEmail.html, - attachments: parsedEmail.attachments?.length || 0 - }); - - // 返回解析结果 - return c.json({ - success: true, - email: { - subject: parsedEmail.subject, - from: parsedEmail.from, - to: parsedEmail.to, - text: parsedEmail.text, - html: parsedEmail.html, - hasAttachments: !!parsedEmail.attachments?.length, - attachmentsCount: parsedEmail.attachments?.length || 0 - } - }); - } catch (error) { - console.error('解析邮件失败:', error); - return c.json({ - success: false, - error: '解析邮件失败', - message: error instanceof Error ? error.message : String(error) - }, 500); - } -}); // 创建邮箱 app.post('/api/mailboxes', async (c) => { @@ -366,13 +248,4 @@ app.delete('/api/emails/:id', async (c) => { } }); -// 调试端点 -app.get('/api/debug/db', (c) => { - return c.json({ - dbDefined: !!c.env.DB, - dbType: typeof c.env.DB, - dbMethods: c.env.DB ? Object.keys(c.env.DB) : [] - }); -}); - export default app; \ No newline at end of file diff --git a/worker/src/utils.ts b/worker/src/utils.ts index d98bb8a..02aed8d 100644 --- a/worker/src/utils.ts +++ b/worker/src/utils.ts @@ -76,91 +76,3 @@ export function generateRandomString(length: number): string { const date = new Date(timestamp * 1000); return date.toISOString(); } - - /** - * 发送邮件 - * @param fromAddress 发件人地址 - * @param toAddress 收件人地址 - * @param toName 收件人名称 - * @param subject 邮件主题 - * @param textContent 纯文本内容 - * @param htmlContent HTML内容 - * @returns 发送结果 - */ - export async function sendEmail( - fromAddress: string, - toAddress: string, - toName: string = '', - subject: string, - textContent: string = '', - htmlContent: string = '' - ): Promise<{ success: boolean; message?: string; error?: string }> { - try { - // 导入buildMimeEmail函数 - const { buildMimeEmail } = await import('./email-builder'); - - // 构建MIME格式邮件 - const rawEmail = buildMimeEmail( - fromAddress, - toAddress, - toName, - subject, - textContent, - htmlContent - ); - - // 使用MailChannels API发送邮件 - const response = await fetch('https://api.mailchannels.net/tx/v1/send', { - method: 'POST', - headers: { - 'content-type': 'application/json', - }, - body: JSON.stringify({ - personalizations: [ - { - to: [{ email: toAddress, name: toName || undefined }], - }, - ], - from: { - email: fromAddress, - name: extractMailboxName(fromAddress), - }, - subject: subject, - content: [ - { - type: 'text/plain', - value: textContent, - }, - ...(htmlContent - ? [ - { - type: 'text/html', - value: htmlContent, - }, - ] - : []), - ], - headers: { - 'X-Generated-By': 'TempMail-System', - 'X-Mime-Version': '1.0', - }, - raw_mime: btoa(rawEmail), - }), - }); - - if (response.ok) { - return { success: true, message: '邮件发送成功' }; - } else { - const errorData = await response.json() as { errors?: string[] }; - return { - success: false, - error: `邮件发送失败: ${errorData.errors?.[0] || response.statusText}`, - }; - } - } catch (error) { - return { - success: false, - error: `邮件发送失败: ${error instanceof Error ? error.message : String(error)}`, - }; - } - } \ No newline at end of file