- {/* 错误和成功提示 */}
- {(errorMessage || successMessage) && (
-
- {errorMessage || successMessage}
-
- )}
-
-
- setIsCustom(false)}
- >
- {t('mailbox.createRandom')}
-
- setIsCustom(true)}
- >
- {t('mailbox.createCustom')}
-
-
-
- {isCustom ? (
-
- ) : (
-
- {isLoading ? t('common.loading') : t('mailbox.createRandom')}
-
- )}
-
- );
-};
-
-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}
+
+ @
+
+ {domains.map(d => (
+ {d}
+ ))}
+
= ({
) : (
<>
- {/* 桌面版显示 */}
-
- {/* 邮箱地址和操作按钮 */}
-
-
- {mailbox.address.includes('@') ? mailbox.address : `${mailbox.address}@${domain}`}
+
+ {/* 电脑端邮箱地址显示 */}
+
+
+ {mailbox.address}@
+
+ {domains.map(d => (
+ {d}
+ ))}
+
-
+ >
@@ -323,12 +353,12 @@ const HeaderMailbox: React.FC
= ({
{renderMobileAddress()}
-
+ >
@@ -340,43 +370,43 @@ const HeaderMailbox: React.FC = ({
)}
-
-
-
-
-
-
- {/* 更新成功提示 */}
- {showRefreshSuccess && (
-
- {t('mailbox.refreshSuccess')}
-
-
- )}
-
-
-
setIsCustomMode(true)}
- className={`w-6 h-6 ${customizeButtonClass}`}
- disabled={isActionLoading}
- title={t('mailbox.customize')}
- >
-
-
- {/* 错误信息显示 */}
- {(copyError || refreshError) && (
+
+
+
+
+
+ {/* 更新成功提示 */}
+ {showRefreshSuccess && (
+
+ {t('mailbox.refreshSuccess')}
+
+
+ )}
+
+
+
setIsCustomMode(true)}
+ className={`w-6 h-6 ${customizeButtonClass}`}
+ disabled={isActionLoading}
+ title={t('mailbox.customize')}
+ >
+
+
+
+
+ {/* 错误信息显示 */}
+ {(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