From a45d01f9fd2874543b7d80fadcb9683ac38acade Mon Sep 17 00:00:00 2001 From: Dream Hunter Date: Thu, 26 Mar 2026 00:18:15 +0800 Subject: [PATCH 1/4] feat: return address_id in /admin/new_address response (#913) * feat: return address_id in /admin/new_address response - Add address_id field to newAddress function return type - Update CHANGELOG.md and CHANGELOG_EN.md Fixes #912 Co-Authored-By: Claude Opus 4.6 * test: verify address_id in new_address response * fix: add address_id validation and improve test coverage - Add null check for address_id after DB query - Change address_id to required field in return type - Add dedicated test for /admin/new_address endpoint - Update e2e helper return type to non-optional --------- Co-authored-by: Claude Opus 4.6 --- CHANGELOG.md | 1 + CHANGELOG_EN.md | 1 + e2e/fixtures/test-helpers.ts | 4 ++-- e2e/tests/api/address-lifecycle.spec.ts | 3 ++- e2e/tests/api/admin-new-address.spec.ts | 19 +++++++++++++++++++ worker/src/common.ts | 7 ++++++- 6 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 e2e/tests/api/admin-new-address.spec.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e107df05a..e24c7275c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ### Features +- feat: |Admin API| `/admin/new_address` 接口返回值新增 `address_id` 字段,避免创建后需再次查询地址 ID(#912) - feat: |自动回复| 发件人过滤支持正则表达式匹配,使用 `/pattern/` 语法(如 `/@example\.com$/`),同时保持前缀匹配的向后兼容 - feat: |Turnstile| 新增全局登录表单 Turnstile 人机验证,通过 `ENABLE_GLOBAL_TURNSTILE_CHECK` 环境变量控制(#767) - feat: |Telegram| Telegram 推送支持发送邮件附件(单文件限制 50MB),多附件通过 `sendMediaGroup` 批量发送,通过 `ENABLE_TG_PUSH_ATTACHMENT` 环境变量开启(#894) diff --git a/CHANGELOG_EN.md b/CHANGELOG_EN.md index 38e0ca7f71..e19298b6d3 100644 --- a/CHANGELOG_EN.md +++ b/CHANGELOG_EN.md @@ -10,6 +10,7 @@ ### Features +- feat: |Admin API| `/admin/new_address` endpoint now returns `address_id` field, avoiding additional query after address creation (#912) - feat: |Auto Reply| Add regex matching support for sender filter using `/pattern/` syntax (e.g. `/@example\.com$/`), backward compatible with prefix matching - feat: |Turnstile| Add global Turnstile CAPTCHA for all login forms via `ENABLE_GLOBAL_TURNSTILE_CHECK` env var (#767) - feat: |Telegram| Support sending email attachments in Telegram push (50MB per file limit), multiple attachments sent via `sendMediaGroup`, controlled by `ENABLE_TG_PUSH_ATTACHMENT` env var (#894) diff --git a/e2e/fixtures/test-helpers.ts b/e2e/fixtures/test-helpers.ts index ff05c3c85b..2720e0ecec 100644 --- a/e2e/fixtures/test-helpers.ts +++ b/e2e/fixtures/test-helpers.ts @@ -16,7 +16,7 @@ export async function createTestAddress( ctx: APIRequestContext, name: string, domain: string = TEST_DOMAIN -): Promise<{ jwt: string; address: string }> { +): Promise<{ jwt: string; address: string; address_id: number }> { const uniqueName = `${name}${Date.now()}`; const res = await ctx.post(`${WORKER_URL}/api/new_address`, { data: { name: uniqueName, domain }, @@ -25,7 +25,7 @@ export async function createTestAddress( throw new Error(`Failed to create address: ${res.status()} ${await res.text()}`); } const body = await res.json(); - return { jwt: body.jwt, address: body.address }; + return { jwt: body.jwt, address: body.address, address_id: body.address_id }; } /** diff --git a/e2e/tests/api/address-lifecycle.spec.ts b/e2e/tests/api/address-lifecycle.spec.ts index cc0f5c7f31..31bfdd8a5d 100644 --- a/e2e/tests/api/address-lifecycle.spec.ts +++ b/e2e/tests/api/address-lifecycle.spec.ts @@ -4,9 +4,10 @@ import { WORKER_URL, TEST_DOMAIN, createTestAddress, deleteAddress, requestSendA test.describe('Address Lifecycle', () => { test('create address, request send access, fetch settings, then delete', async ({ request }) => { // Create address - const { jwt, address } = await createTestAddress(request, 'lifecycle-test'); + const { jwt, address, address_id } = await createTestAddress(request, 'lifecycle-test'); expect(address).toContain('@' + TEST_DOMAIN); expect(jwt).toBeTruthy(); + expect(address_id).toBeGreaterThan(0); // Request send access (creates address_sender row with DEFAULT_SEND_BALANCE) await requestSendAccess(request, jwt); diff --git a/e2e/tests/api/admin-new-address.spec.ts b/e2e/tests/api/admin-new-address.spec.ts new file mode 100644 index 0000000000..37e9c19a5d --- /dev/null +++ b/e2e/tests/api/admin-new-address.spec.ts @@ -0,0 +1,19 @@ +import { test, expect } from '@playwright/test'; +import { WORKER_URL, TEST_DOMAIN } from '../../fixtures/test-helpers'; + +test.describe('Admin New Address', () => { + test('should return address_id in response', async ({ request }) => { + const uniqueName = `admin-test${Date.now()}`; + const res = await request.post(`${WORKER_URL}/admin/new_address`, { + data: { name: uniqueName, domain: TEST_DOMAIN }, + }); + + expect(res.ok()).toBe(true); + const body = await res.json(); + + expect(body.address).toContain('@' + TEST_DOMAIN); + expect(body.jwt).toBeTruthy(); + expect(body.address_id).toBeGreaterThan(0); + expect(typeof body.address_id).toBe('number'); + }); +}); diff --git a/worker/src/common.ts b/worker/src/common.ts index 9b758f0ff7..4021714f1f 100644 --- a/worker/src/common.ts +++ b/worker/src/common.ts @@ -168,7 +168,7 @@ export const newAddress = async ( enableCheckNameRegex?: boolean, sourceMeta?: string | undefined | null, } -): Promise<{ address: string, jwt: string, password?: string | null }> => { +): Promise<{ address: string, jwt: string, password?: string | null, address_id: number }> => { const msgs = i18n.getMessagesbyContext(c); // trim whitespace and remove special characters name = name.trim().replace(getNameRegex(c), '') @@ -247,6 +247,10 @@ export const newAddress = async ( `SELECT id FROM address where name = ?` ).bind(name).first("id"); + if (!address_id) { + throw new Error(msgs.FailedCreateAddressMsg); + } + // 如果启用地址密码功能,自动生成密码 const generatedPassword = await generatePasswordForAddress(c, name); @@ -259,6 +263,7 @@ export const newAddress = async ( jwt: jwt, address: name, password: generatedPassword, + address_id: address_id, } } From c97a9a278b5a9afb5877f515161892d4ec72cc49 Mon Sep 17 00:00:00 2001 From: Dream Hunter Date: Thu, 26 Mar 2026 02:10:04 +0800 Subject: [PATCH 2/4] docs: clarify Address JWT vs User JWT and reorganize API menu (#914) - Add warning notes in new-address-api and mail-api docs - Explain the difference between Address JWT and User JWT - Create dedicated 'API Endpoints' section in sidebar - Update both zh and en documentation Refs #910 --- CHANGELOG.md | 1 + CHANGELOG_EN.md | 1 + vitepress-docs/docs/.vitepress/en.ts | 12 +++++++++--- vitepress-docs/docs/.vitepress/zh.ts | 12 +++++++++--- vitepress-docs/docs/en/guide/feature/mail-api.md | 8 ++++++++ .../docs/en/guide/feature/new-address-api.md | 14 ++++++++++++++ vitepress-docs/docs/zh/guide/feature/mail-api.md | 8 ++++++++ .../docs/zh/guide/feature/new-address-api.md | 14 ++++++++++++++ 8 files changed, 64 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e24c7275c9..11979929dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ ### Docs +- docs: |API| 新增地址 JWT 与用户 JWT 的区分说明,避免混淆两种认证方式;调整文档菜单结构,将 API 接口文档归类到独立分组(#910) - docs: |Telegram| 新增每用户邮件推送和全局推送功能说明文档(#769) - docs: |Webhook| 新增 Telegram Bot、企业微信、Discord 等常用推送平台的 Webhook 模板示例 - feat: |Webhook| 前端预设模板新增 Telegram Bot、企业微信、Discord 三个模板 diff --git a/CHANGELOG_EN.md b/CHANGELOG_EN.md index e19298b6d3..40e94de86f 100644 --- a/CHANGELOG_EN.md +++ b/CHANGELOG_EN.md @@ -27,6 +27,7 @@ ### Docs +- docs: |API| Add clarification between Address JWT and User JWT to avoid confusion; reorganize documentation menu structure with dedicated API Endpoints section (#910) - docs: |Telegram| Add per-user mail push and global push documentation (#769) - docs: |Webhook| Add webhook template examples for Telegram Bot, WeChat Work, Discord and other common push platforms - feat: |Webhook| Add Telegram Bot, WeChat Work, Discord preset templates to frontend webhook settings diff --git a/vitepress-docs/docs/.vitepress/en.ts b/vitepress-docs/docs/.vitepress/en.ts index 99a3c697a4..c36d252668 100644 --- a/vitepress-docs/docs/.vitepress/en.ts +++ b/vitepress-docs/docs/.vitepress/en.ts @@ -149,19 +149,25 @@ function sidebarGuide(): DefaultTheme.SidebarItem[] { items: [ { text: 'AI Email Recognition', link: 'feature/ai-extract' }, { text: 'Configure SMTP IMAP Proxy', link: 'feature/config-smtp-proxy' }, - { text: 'Send Email API', link: 'feature/send-mail-api' }, - { text: 'View Email API', link: 'feature/mail-api' }, { text: 'Configure Subdomain Email', link: 'feature/subdomain' }, { text: 'Configure Telegram Bot', link: 'feature/telegram' }, { text: 'Configure S3 Attachments', link: 'feature/s3-attachment' }, { text: 'Configure WASM Email Parser', link: 'feature/mail_parser_wasm_worker' }, { text: 'Configure Webhook', link: 'feature/webhook' }, - { text: 'New Address API', link: 'feature/new-address-api' }, { text: 'OAuth2 Third-party Login', link: 'feature/user-oauth2' }, { text: 'Enhance with Other Workers', link: 'feature/another-worker-enhanced' }, { text: 'Add Google Ads', link: 'feature/google-ads.md' }, ] }, + { + text: 'API Endpoints', + collapsed: false, + items: [ + { text: 'New Address API', link: 'feature/new-address-api' }, + { text: 'View Email API', link: 'feature/mail-api' }, + { text: 'Send Email API', link: 'feature/send-mail-api' }, + ] + }, { text: 'Feature Overview', collapsed: false, diff --git a/vitepress-docs/docs/.vitepress/zh.ts b/vitepress-docs/docs/.vitepress/zh.ts index fb018ba40e..5048f0ddcd 100644 --- a/vitepress-docs/docs/.vitepress/zh.ts +++ b/vitepress-docs/docs/.vitepress/zh.ts @@ -149,19 +149,25 @@ function sidebarGuide(): DefaultTheme.SidebarItem[] { items: [ { text: 'AI 邮件识别', link: 'feature/ai-extract' }, { text: '配置 SMTP IMAP 代理服务', link: 'feature/config-smtp-proxy' }, - { text: '发送邮件 API', link: 'feature/send-mail-api' }, - { text: '查看邮件 API', link: 'feature/mail-api' }, { text: '配置子域名邮箱', link: 'feature/subdomain' }, { text: '配置 Telegram Bot', link: 'feature/telegram' }, { text: '配置 S3 附件', link: 'feature/s3-attachment' }, { text: '配置 worker 使用 wasm 解析邮件', link: 'feature/mail_parser_wasm_worker' }, { text: '配置 webhook', link: 'feature/webhook' }, - { text: '新建邮箱地址 API', link: 'feature/new-address-api' }, { text: 'Oauth2 第三方登录', link: 'feature/user-oauth2' }, { text: '配置其他worker增强', link: 'feature/another-worker-enhanced' }, { text: '给网页增加 Google Ads', link: 'feature/google-ads.md' }, ] }, + { + text: 'API 接口', + collapsed: false, + items: [ + { text: '新建邮箱地址 API', link: 'feature/new-address-api' }, + { text: '查看邮件 API', link: 'feature/mail-api' }, + { text: '发送邮件 API', link: 'feature/send-mail-api' }, + ] + }, { text: '功能简介', collapsed: false, diff --git a/vitepress-docs/docs/en/guide/feature/mail-api.md b/vitepress-docs/docs/en/guide/feature/mail-api.md index b20cc88785..1e95ad023c 100644 --- a/vitepress-docs/docs/en/guide/feature/mail-api.md +++ b/vitepress-docs/docs/en/guide/feature/mail-api.md @@ -131,6 +131,14 @@ print(response.json()) ## User Mail API +::: warning Note: User JWT vs Address JWT +This endpoint uses **User JWT** (obtained via `/user_api/login` or `/user_api/register`), with `x-user-token` header. + +**Do not confuse with Address JWT**: +- Address JWT uses `Authorization: Bearer ` to access `/api/*` endpoints +- User JWT uses `x-user-token: ` to access `/user_api/*` endpoints +::: + Supports `address` filter ```python diff --git a/vitepress-docs/docs/en/guide/feature/new-address-api.md b/vitepress-docs/docs/en/guide/feature/new-address-api.md index 147700baf4..cfb8933388 100644 --- a/vitepress-docs/docs/en/guide/feature/new-address-api.md +++ b/vitepress-docs/docs/en/guide/feature/new-address-api.md @@ -1,5 +1,19 @@ # Create New Email Address API +::: warning Note: Address JWT vs User JWT +This page describes **Address JWT**, which is different from **User JWT**: + +- **Address JWT**: Returned when creating a mailbox via `/api/new_address` or `/admin/new_address` + - Use `Authorization: Bearer ` header + - Access `/api/*` endpoints (view mails, delete mails, etc.) + +- **User JWT**: Obtained via `/user_api/login` or `/user_api/register` + - Use `x-user-token: ` header + - Access `/user_api/*` endpoints (user account management) + +**Do not confuse these two JWT types!** +::: + ## Create Email Address via Admin API This is a `python` example using the `requests` library to send emails. diff --git a/vitepress-docs/docs/zh/guide/feature/mail-api.md b/vitepress-docs/docs/zh/guide/feature/mail-api.md index 88dd630a7d..7fe06ccfdd 100644 --- a/vitepress-docs/docs/zh/guide/feature/mail-api.md +++ b/vitepress-docs/docs/zh/guide/feature/mail-api.md @@ -131,6 +131,14 @@ print(response.json()) ## user 邮件 API +::: warning 注意:用户 JWT vs 地址 JWT +此接口使用**用户 JWT**(通过 `/user_api/login` 或 `/user_api/register` 获得),使用 `x-user-token` header。 + +**请勿与地址 JWT 混淆**: +- 地址 JWT 使用 `Authorization: Bearer ` 访问 `/api/*` 接口 +- 用户 JWT 使用 `x-user-token: ` 访问 `/user_api/*` 接口 +::: + 支持 `address` 过滤 ```python diff --git a/vitepress-docs/docs/zh/guide/feature/new-address-api.md b/vitepress-docs/docs/zh/guide/feature/new-address-api.md index 0efe3835ef..1fd03369e4 100644 --- a/vitepress-docs/docs/zh/guide/feature/new-address-api.md +++ b/vitepress-docs/docs/zh/guide/feature/new-address-api.md @@ -1,5 +1,19 @@ # 新建邮箱地址 API +::: warning 注意:地址 JWT vs 用户 JWT +本页面介绍的是**地址 JWT**,与**用户 JWT** 是两种不同的认证方式: + +- **地址 JWT**:通过 `/api/new_address` 或 `/admin/new_address` 创建邮箱时返回 + - 使用 `Authorization: Bearer ` header + - 用于访问 `/api/*` 接口(查看邮件、删除邮件等) + +- **用户 JWT**:通过 `/user_api/login` 或 `/user_api/register` 获得 + - 使用 `x-user-token: ` header + - 用于访问 `/user_api/*` 接口(用户账户管理) + +**请勿混淆两种 JWT 的使用方式!** +::: + ## 通过 admin API 新建邮箱地址 这是一个 `python` 的例子,使用 `requests` 库发送邮件。 From 424991a165cccfbc00eec92c240cafc440621e7d Mon Sep 17 00:00:00 2001 From: BobDLA <3804610+BobDLA@users.noreply.github.com> Date: Sun, 29 Mar 2026 01:48:17 +0800 Subject: [PATCH 3/4] fix: surface backend deploy errors in GitHub Actions (#917) --- .github/workflows/backend_deploy.yaml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/backend_deploy.yaml b/.github/workflows/backend_deploy.yaml index 7a6a8199cd..c80ac53c14 100644 --- a/.github/workflows/backend_deploy.yaml +++ b/.github/workflows/backend_deploy.yaml @@ -67,11 +67,12 @@ jobs: if [ "$debug_mode" = "true" ]; then pnpm run deploy else - output=$(pnpm run deploy 2>&1) - if [ $? -ne 0 ]; then - code=$? - echo "Command failed with exit code $code" - exit $code + if pnpm run deploy >/dev/null 2>&1; then + echo "Deploy succeeded" + else + code=$? + echo "Command failed with exit code $code" + exit "$code" fi fi echo "Deployed for tag ${{ github.ref_name }}" @@ -79,4 +80,4 @@ jobs: CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} # ✅ 将 secret 映射到环境变量中 - WRANGLER_TOML_CONTENT: ${{ secrets.BACKEND_TOML }} \ No newline at end of file + WRANGLER_TOML_CONTENT: ${{ secrets.BACKEND_TOML }} From be1bf71a47b06618c16848df9dbce9ba30c17e8e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 14:55:53 +0800 Subject: [PATCH 4/4] chore(deps): bump nodemailer and imapflow in /e2e (#916) Bumps [nodemailer](https://github.com/nodemailer/nodemailer) and [imapflow](https://github.com/postalsys/imapflow). These dependencies needed to be updated together. Updates `nodemailer` from 8.0.1 to 8.0.4 - [Release notes](https://github.com/nodemailer/nodemailer/releases) - [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md) - [Commits](https://github.com/nodemailer/nodemailer/compare/v8.0.1...v8.0.4) Updates `imapflow` from 1.2.12 to 1.2.18 - [Release notes](https://github.com/postalsys/imapflow/releases) - [Changelog](https://github.com/postalsys/imapflow/blob/master/CHANGELOG.md) - [Commits](https://github.com/postalsys/imapflow/compare/v1.2.12...v1.2.18) --- updated-dependencies: - dependency-name: nodemailer dependency-version: 8.0.4 dependency-type: direct:production - dependency-name: imapflow dependency-version: 1.2.18 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- e2e/package-lock.json | 18 +++++++++--------- e2e/package.json | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/e2e/package-lock.json b/e2e/package-lock.json index 7f0b33b039..6dcda164a8 100644 --- a/e2e/package-lock.json +++ b/e2e/package-lock.json @@ -6,8 +6,8 @@ "": { "name": "cloudflare-temp-email-e2e", "dependencies": { - "imapflow": "^1.2.12", - "nodemailer": "^8.0.1" + "imapflow": "^1.2.18", + "nodemailer": "^8.0.4" }, "devDependencies": { "@playwright/test": "1.58.2", @@ -129,9 +129,9 @@ } }, "node_modules/imapflow": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/imapflow/-/imapflow-1.2.12.tgz", - "integrity": "sha512-UX8qCKXZk2xExe/x8KPTSbhROdtUGP13bSLSjT9Sb3YwGuryD4aFNlGhbWBW5B1GtgHMRxVv9yvl61RqXgIQtQ==", + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/imapflow/-/imapflow-1.2.18.tgz", + "integrity": "sha512-zxYvcG9ckj/UcTRs+ZDT+wJzW8DqkjgWZwc1z4Q28R/4C/1YvJieVETOuR/9ztCXcycURC50PJShMimITvz5wQ==", "license": "MIT", "dependencies": { "@zone-eu/mailsplit": "5.4.8", @@ -140,7 +140,7 @@ "libbase64": "1.3.0", "libmime": "5.3.7", "libqp": "2.1.1", - "nodemailer": "8.0.1", + "nodemailer": "8.0.4", "pino": "10.3.1", "socks": "2.8.7" } @@ -191,9 +191,9 @@ "license": "MIT" }, "node_modules/nodemailer": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.1.tgz", - "integrity": "sha512-5kcldIXmaEjZcHR6F28IKGSgpmZHaF1IXLWFTG+Xh3S+Cce4MiakLtWY+PlBU69fLbRa8HlaGIrC/QolUpHkhg==", + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.4.tgz", + "integrity": "sha512-k+jf6N8PfQJ0Fe8ZhJlgqU5qJU44Lpvp2yvidH3vp1lPnVQMgi4yEEMPXg5eJS1gFIJTVq1NHBk7Ia9ARdSBdQ==", "license": "MIT-0", "engines": { "node": ">=6.0.0" diff --git a/e2e/package.json b/e2e/package.json index fa403bc9e8..ebc641662e 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -13,7 +13,7 @@ "ws": "^8.18.0" }, "dependencies": { - "imapflow": "^1.2.12", - "nodemailer": "^8.0.1" + "imapflow": "^1.2.18", + "nodemailer": "^8.0.4" } }