Compare commits

..

8 Commits
v1.9.0 ... main

Author SHA1 Message Date
Dream Hunter
70b30c2494 chore: upgrade Twisted to stable 26.4.0 (#1071)
chore: upgrade twisted to stable 26.4.0
2026-06-25 00:23:17 +08:00
Dream Hunter
1a1dd720c8 chore: upgrade smtp proxy and e2e dependencies (#1069) 2026-06-24 23:59:16 +08:00
Dream Hunter
2d501d82cf chore: upgrade dependencies (#1068) 2026-06-24 23:39:10 +08:00
Chánh Niệm
7c57592742 docs: add Resend DNS-only proxy warning to prevent #515-style verification failures (#1062)
docs: add Resend DNS-only proxy warning to send-mail config

Resend domain verification CNAME records must use DNS-only (gray
cloud) on Cloudflare. Proxied (orange cloud) records prevent
verification, and a single failed attempt can take hours before
retry. This is a recurring issue (#515) that the Resend setup
docs did not warn about.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-06-19 15:49:03 +08:00
凉心
41105ed803 fix: add page header padding for mobile layout (#1056)
* fix: add page header padding for mobile layout

* fix: limit page header padding to mobile layout

* docs: update changelog for mobile header fix

---------

Co-authored-by: dreamhunter2333 <dreamhunter2333@gmail.com>
2026-06-13 11:39:51 +08:00
Dream Hunter
c924b71a5c fix: compact HTML before AI email extraction (#1057)
fix: compact html before ai extraction
2026-06-12 00:41:19 +08:00
Dream Hunter
4f0b44de2e chore: upgrade Vitest to v4 (#1052)
chore: upgrade vitest to v4
2026-06-03 23:29:56 +08:00
Dream Hunter
f6fcbe793c feat: upgrade version to v1.10.0 (#1051)
- Update version number to 1.10.0 in all package.json files

- Add v1.10.0 placeholder in CHANGELOG.md
2026-06-03 22:14:44 +08:00
18 changed files with 2067 additions and 2022 deletions

View File

@@ -28,6 +28,7 @@ Upgrade the version number of the cloudflare_temp_email project.
4. Update the `VERSION` constant in `worker/src/constants.ts`.
5. Insert a new version placeholder at the top of `CHANGELOG.md`.
6. Insert a new version placeholder at the top of `CHANGELOG_EN.md`.
7. Rename the previous version heading in both changelogs from `## v{OLD_VERSION}(main)` to `## v{OLD_VERSION}` so only the new active development version keeps `(main)`.
## CHANGELOG format
@@ -44,7 +45,14 @@ In `CHANGELOG.md`, insert before the existing `## v{OLD_VERSION}(main)` line (i.
```
`CHANGELOG_EN.md` uses the same format.
After inserting the new placeholder, update the old heading:
```diff
-## v{OLD_VERSION}(main)
+## v{OLD_VERSION}
```
`CHANGELOG_EN.md` uses the same format and must receive the same old-heading update.
## Commit message format
@@ -53,4 +61,5 @@ feat: upgrade version to v{VERSION}
- Update version number to {VERSION} in all package.json files
- Add v{VERSION} placeholder in CHANGELOG.md
- Move (main) marker from v{OLD_VERSION} to v{VERSION}
```

View File

@@ -6,7 +6,18 @@
<a href="CHANGELOG_EN.md">English</a>
</p>
## v1.9.0(main)
## v1.10.0(main)
### Features
### Bug Fixes
- fix: |AI 提取| HTML-only 邮件在发送给 Workers AI 前会先压缩为可读文本,避免样式模板过长导致验证码位于 4000 字截断之后而无法识别
- fix: |Frontend| 移动端 Header 增加页头内边距,避免标题、菜单按钮与屏幕边缘过近
### Improvements
## v1.9.0
### Features

View File

@@ -6,7 +6,18 @@
<a href="CHANGELOG_EN.md">English</a>
</p>
## v1.9.0(main)
## v1.10.0(main)
### Features
### Bug Fixes
- fix: |AI Extract| Convert HTML-only mail bodies into compact readable text before sending them to Workers AI, preventing long templates from pushing verification codes past the 4000-character truncation window
- fix: |Frontend| Add mobile Header page padding so the title and menu button no longer sit too close to the screen edge
### Improvements
## v1.9.0
### Features

76
e2e/package-lock.json generated
View File

@@ -6,8 +6,8 @@
"": {
"name": "cloudflare-temp-email-e2e",
"dependencies": {
"imapflow": "^1.3.1",
"nodemailer": "^8.0.5"
"imapflow": "^1.4.2",
"nodemailer": "^9.0.1"
},
"devDependencies": {
"@playwright/test": "1.58.2",
@@ -69,13 +69,13 @@
}
},
"node_modules/@zone-eu/mailsplit": {
"version": "5.4.8",
"resolved": "https://registry.npmjs.org/@zone-eu/mailsplit/-/mailsplit-5.4.8.tgz",
"integrity": "sha512-eEyACj4JZ7sjzRvy26QhLgKEMWwQbsw1+QZnlLX+/gihcNH07lVPOcnwf5U6UAL7gkc//J3jVd76o/WS+taUiA==",
"version": "5.4.12",
"resolved": "https://registry.npmjs.org/@zone-eu/mailsplit/-/mailsplit-5.4.12.tgz",
"integrity": "sha512-w7Gy+NvjZ0MiXm8F6zfjImAqcTONKDImgWVBjDKQVFUXWuz3VFM5levNArkL2M877ajql5+bkS2pDV56injlmg==",
"license": "(MIT OR EUPL-1.1+)",
"dependencies": {
"libbase64": "1.3.0",
"libmime": "5.3.7",
"libmime": "5.3.8",
"libqp": "2.1.1"
}
},
@@ -129,38 +129,26 @@
}
},
"node_modules/imapflow": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/imapflow/-/imapflow-1.3.1.tgz",
"integrity": "sha512-DKwpMDR1EWXpV5T7adqQAccN7n684AX3poEZ5F3YoPlm2MyGeKavpRgNr3qptdEQaK+x5SlZ9jigT+cMs4geBA==",
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/imapflow/-/imapflow-1.4.2.tgz",
"integrity": "sha512-73CGfb5+W0FkZ5CY4GfSdsoXyQ+17wdKkpMN2vwJHdLtOOFQWxv0ilG7KYY79XHBYg5njjqxXYB2FPw5Tl81zQ==",
"license": "MIT",
"dependencies": {
"@zone-eu/mailsplit": "5.4.8",
"@zone-eu/mailsplit": "5.4.12",
"encoding-japanese": "2.2.0",
"iconv-lite": "0.7.2",
"libbase64": "1.3.0",
"libmime": "5.3.8",
"libqp": "2.1.1",
"nodemailer": "8.0.5",
"nodemailer": "9.0.1",
"pino": "10.3.1",
"socks": "2.8.7"
}
},
"node_modules/imapflow/node_modules/libmime": {
"version": "5.3.8",
"resolved": "https://registry.npmjs.org/libmime/-/libmime-5.3.8.tgz",
"integrity": "sha512-ZrCY+Q66mPvasAfjsQ/IgahzoBvfE1VdtGRpo1hwRB1oK3wJKxhKA3GOcd2a6j7AH5eMFccxK9fBoCpRZTf8ng==",
"license": "MIT",
"dependencies": {
"encoding-japanese": "2.2.0",
"iconv-lite": "0.7.2",
"libbase64": "1.3.0",
"libqp": "2.1.1"
"socks": "2.8.9"
}
},
"node_modules/ip-address": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
"integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==",
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
"integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==",
"license": "MIT",
"engines": {
"node": ">= 12"
@@ -173,29 +161,17 @@
"license": "MIT"
},
"node_modules/libmime": {
"version": "5.3.7",
"resolved": "https://registry.npmjs.org/libmime/-/libmime-5.3.7.tgz",
"integrity": "sha512-FlDb3Wtha8P01kTL3P9M+ZDNDWPKPmKHWaU/cG/lg5pfuAwdflVpZE+wm9m7pKmC5ww6s+zTxBKS1p6yl3KpSw==",
"version": "5.3.8",
"resolved": "https://registry.npmjs.org/libmime/-/libmime-5.3.8.tgz",
"integrity": "sha512-ZrCY+Q66mPvasAfjsQ/IgahzoBvfE1VdtGRpo1hwRB1oK3wJKxhKA3GOcd2a6j7AH5eMFccxK9fBoCpRZTf8ng==",
"license": "MIT",
"dependencies": {
"encoding-japanese": "2.2.0",
"iconv-lite": "0.6.3",
"iconv-lite": "0.7.2",
"libbase64": "1.3.0",
"libqp": "2.1.1"
}
},
"node_modules/libmime/node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/libqp": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/libqp/-/libqp-2.1.1.tgz",
@@ -203,9 +179,9 @@
"license": "MIT"
},
"node_modules/nodemailer": {
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.5.tgz",
"integrity": "sha512-0PF8Yb1yZuQfQbq+5/pZJrtF6WQcjTd5/S4JOHs9PGFxuTqoB/icwuB44pOdURHJbRKX1PPoJZtY7R4VUoCC8w==",
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-9.0.1.tgz",
"integrity": "sha512-Gwv8SQewT616ZM/URn0H54b8PWo/Wum7md3EW2aWy1lO27+WZCX+Xyak3J+NlmHUjDh5ME+uesJUDRbR3Ye8Bw==",
"license": "MIT-0",
"engines": {
"node": ">=6.0.0"
@@ -346,12 +322,12 @@
}
},
"node_modules/socks": {
"version": "2.8.7",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz",
"integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==",
"version": "2.8.9",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz",
"integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==",
"license": "MIT",
"dependencies": {
"ip-address": "^10.0.1",
"ip-address": "^10.1.1",
"smart-buffer": "^4.2.0"
},
"engines": {

View File

@@ -13,7 +13,7 @@
"ws": "^8.18.0"
},
"dependencies": {
"imapflow": "^1.3.1",
"nodemailer": "^8.0.5"
"imapflow": "^1.4.2",
"nodemailer": "^9.0.1"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "cloudflare_temp_email",
"version": "1.9.0",
"version": "1.10.0",
"private": true,
"type": "module",
"scripts": {
@@ -28,16 +28,16 @@
"@vueuse/core": "^14.3.0",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"axios": "^1.16.1",
"dompurify": "^3.4.7",
"axios": "^1.18.1",
"dompurify": "^3.4.11",
"jszip": "^3.10.1",
"mail-parser-wasm": "^0.2.2",
"naive-ui": "^2.44.1",
"postal-mime": "^2.7.4",
"vooks": "^0.2.12",
"vue": "^3.5.35",
"vue": "^3.5.38",
"vue-clipboard3": "^2.0.0",
"vue-i18n": "^11.4.4",
"vue-i18n": "^11.4.6",
"vue-router": "^4.6.4"
},
"devDependencies": {
@@ -50,10 +50,10 @@
"vite": "^7.3.5",
"vite-plugin-pwa": "^1.3.0",
"vite-plugin-wasm": "^3.6.0",
"vitest": "^3.2.6",
"vitest": "^4.1.9",
"workbox-build": "^7.4.1",
"workbox-window": "^7.4.1",
"wrangler": "^4.96.0"
"wrangler": "^4.104.0"
},
"packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39"
}

1500
frontend/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -435,6 +435,10 @@ onMounted(async () => {
}
@media (max-width: 640px) {
:deep(.n-page-header) {
padding: 10px;
}
:deep(.n-page-header__title) {
min-width: 0;
}

View File

@@ -1,6 +1,6 @@
{
"name": "temp-email-pages",
"version": "1.9.0",
"version": "1.10.0",
"description": "",
"main": "index.js",
"scripts": {
@@ -11,7 +11,7 @@
"author": "",
"license": "ISC",
"devDependencies": {
"wrangler": "^4.96.0"
"wrangler": "^4.104.0"
},
"packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39"
}

View File

@@ -1,6 +1,6 @@
aiosmtpd==1.4.6
pydantic-settings==2.13.1
Twisted==25.5.0
pydantic-settings==2.14.2
Twisted==26.4.0
httpx==0.28.1
pyOpenSSL==26.0.0
service-identity==24.2.0

View File

@@ -48,6 +48,12 @@ No additional env var is required.
Register at `https://resend.com/domains` and add DNS records according to the instructions.
> [!WARNING] DNS record proxy status on Cloudflare
> Resend domain verification CNAME records **must be set to DNS-only** (gray cloud) in
> the Cloudflare DNS dashboard. Proxied (orange cloud) records will prevent Resend from
> completing verification, and a single failed attempt can take several hours before
> Retry becomes available. See [#515](https://github.com/dreamhunter2333/cloudflare_temp_email/issues/515).
Create an `api key` on the `API KEYS` page.
Then execute the following command to add `RESEND_TOKEN` to secrets:

View File

@@ -48,6 +48,12 @@ send_email = [
注册 `https://resend.com/domains` 根据提示添加 DNS 记录,
> [!WARNING] Cloudflare 上 DNS 记录的代理状态
> Resend 域名验证的 CNAME 记录**必须设置为 仅 DNS**(灰云),在
> Cloudflare DNS 控制面板中代理(橙云)记录会阻止 Resend
> 完成验证,且一次失败的尝试可能需要数小时才能重试。
> 参见 [#515](https://github.com/dreamhunter2333/cloudflare_temp_email/issues/515)。
`API KEYS` 页面创建 `api key`
然后执行下面的命令,将 `RESEND_TOKEN` 添加到 secrets 中

View File

@@ -1,12 +1,12 @@
{
"name": "temp-mail-docs",
"private": true,
"version": "1.9.0",
"version": "1.10.0",
"type": "module",
"devDependencies": {
"@types/node": "^25.9.1",
"@types/node": "^26.0.0",
"vitepress": "^1.6.4",
"wrangler": "^4.96.0"
"wrangler": "^4.104.0"
},
"scripts": {
"dev": "vitepress dev docs",

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "cloudflare_temp_email",
"version": "1.9.0",
"version": "1.10.0",
"private": true,
"type": "module",
"scripts": {
@@ -11,23 +11,23 @@
"build": "wrangler deploy --dry-run --outdir dist --minify"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20260602.1",
"@cloudflare/workers-types": "^4.20260624.1",
"@eslint/js": "9.39.1",
"@types/node": "^25.9.1",
"@types/node": "^25.9.4",
"eslint": "9.39.1",
"globals": "^16.5.0",
"typescript-eslint": "^8.60.1",
"wrangler": "^4.96.0"
"typescript-eslint": "^8.62.0",
"wrangler": "^4.104.0"
},
"dependencies": {
"@aws-sdk/client-s3": "3.888.0",
"@aws-sdk/s3-request-presigner": "3.888.0",
"@simplewebauthn/server": "^13.3.1",
"hono": "^4.12.23",
"@simplewebauthn/server": "^13.3.2",
"hono": "^4.12.27",
"jsonpath-plus": "^10.4.0",
"mimetext": "^3.0.28",
"postal-mime": "^2.7.4",
"resend": "^6.12.4",
"resend": "^6.14.0",
"telegraf": "4.16.3",
"worker-mailer": "^1.2.1"
},

1381
worker/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
export const CONSTANTS = {
VERSION: 'v' + '1.9.0',
VERSION: 'v' + '1.10.0',
// DB Version
DB_VERSION_KEY: 'db_version',

View File

@@ -166,6 +166,65 @@ async function saveExtractMetadata(
}
}
function decodeHtmlEntities(text: string): string {
const entities: Record<string, string> = {
amp: '&',
lt: '<',
gt: '>',
quot: '"',
apos: "'",
nbsp: ' ',
};
const decodeCodePoint = (value: number, fallback: string) => {
if (!Number.isFinite(value) || value < 0 || value > 0x10ffff) {
return fallback;
}
return String.fromCodePoint(value);
};
return text.replace(/&(#x[0-9a-f]+|#\d+|[a-z][a-z0-9]+);/gi, (match, entity) => {
const normalized = entity.toLowerCase();
if (normalized.startsWith('#x')) {
const value = Number.parseInt(normalized.slice(2), 16);
return decodeCodePoint(value, match);
}
if (normalized.startsWith('#')) {
const value = Number.parseInt(normalized.slice(1), 10);
return decodeCodePoint(value, match);
}
return entities[normalized] ?? match;
});
}
function htmlToTextForAi(html: string): string {
return decodeHtmlEntities(
html
.replace(/<\s*(script|style|head|svg)[^>]*>[\s\S]*?<\s*\/\s*\1\s*>/gi, ' ')
.replace(/<!--[\s\S]*?-->/g, ' ')
.replace(/<a\b[^>]*\bhref=(["'])(.*?)\1[^>]*>([\s\S]*?)<\/a>/gi, ' $3 $2 ')
.replace(/<\s*br\s*\/?>/gi, '\n')
.replace(/<\/\s*(p|div|tr|td|th|li|table|section|article|header|footer|h[1-6])\s*>/gi, '\n')
.replace(/<[^>]+>/g, ' ')
)
.replace(/[ \t\r\f\v]+/g, ' ')
.replace(/\n\s+/g, '\n')
.replace(/\n{3,}/g, '\n\n')
.trim();
}
function getEmailContentForExtract(parsedEmail: Awaited<ReturnType<typeof commonParseMail>>): string {
if (parsedEmail?.text) {
return parsedEmail.text;
}
if (!parsedEmail?.html) {
return "";
}
return htmlToTextForAi(parsedEmail.html) || parsedEmail.html;
}
/**
* Main extraction function
* Checks if extraction is enabled, processes the email content, and saves to database.
@@ -219,7 +278,7 @@ export async function extractEmailInfo(
// Parse email to get content (shared by the AI path and the regex fallback)
const parsedEmail = await commonParseMail(parsedEmailContext);
const emailContent = parsedEmail?.text || parsedEmail?.html || "";
const emailContent = getEmailContentForExtract(parsedEmail);
if (!emailContent) {
return null;