Stale localStorage credentials (`jwt` / `auth` / `adminAuth` / `userJwt` /
`access_token`) can be the empty string, the literal string `"undefined"`,
or carry a stray newline / control character left over from an older
build. axios + undici reject these eagerly with `Invalid character in
header content ["Authorization"]`, so every API call crashes client-side
before reaching the worker.
This adds two tiny helpers in `frontend/src/utils/headers.js`:
- `safeHeaderValue(v)` returns the trimmed value when it is a non-empty
string with no control chars (per RFC 7230) and no `"undefined"` /
`"null"` sentinel; otherwise `undefined`.
- `safeBearerHeader(jwt)` wraps a safe JWT with `Bearer `, otherwise
`undefined`.
`apiFetch` builds the headers object incrementally and only sets each
auth header when its value is safe. Missing/unsafe credentials now drop
out cleanly and the worker returns a normal 401, which the existing
`response.status === 401` flow already handles by surfacing the auth
prompt — the same UX users see on a fresh session.
Tests: `frontend/src/utils/__tests__/headers.test.js` adds 9 vitest
cases covering safe input, sentinel strings, control chars (\\n / \\r /
\\t / NUL / 0x1F / DEL), trimming, and `Bearer` construction. Build
(`pnpm build`) and tests (`pnpm test`) both pass.
Co-authored-by: voidborne-d <voidborne.d@agentmail.to>
Co-authored-by: Dream Hunter <dreamhunter2333@gmail.com>
* feat(i18n): enhance locale handling and routing
- Implemented dynamic locale aliases in router configuration.
- Added support for preferred locale storage in global state.
- Improved locale resolution logic in router beforeEach guard.
- Created utility functions for locale management and path manipulation.
- Added tests for locale matching and message extraction.
- Updated Header component to allow language selection.
- Refactored getRouterPathWithLang to utilize new locale utilities.
- Updated Vite configuration to support aliasing for vue-i18n.
- Bumped version numbers across various packages to 1.9.0.
* feat(i18n): update version to 1.8.0 and enhance locale handling
- Updated version numbers across all package.json files to 1.8.0.
- Enhanced locale handling in App.vue by centralizing locale configurations.
- Improved Turnstile component to support dynamic language rendering.
- Refactored i18n utilities to include initial locale setup and empty locale messages.
- Updated i18n.ts to utilize the new locale management structure.
- Added naive-locale.ts for better integration with Naive UI's locale handling.
- Adjusted Header.vue to streamline language selection and locale changes.
- Fixed translations in multiple locale files for consistency and accuracy.
* fix(i18n): address review feedback
* feat(i18n): update default locale to English and enhance language handling in components
* fix(i18n): switch locale selector to dropdown
* docs: add topbar language and github order design spec
* fix(i18n): 修复 Header 语言切换器相关问题,恢复为独立控件并调整样式
* Refactor locale handling in router and add locale-guard utility functions
- Improved locale resolution logic in router by introducing utility functions for better readability and maintainability.
- Added `locale-guard.js` to encapsulate locale-related functions such as getting route locale, resolving locale for navigation, and applying locale navigation state.
- Updated JWT synchronization logic to streamline the handling of JWT from query parameters.
- Modified i18n messages test to check for coverage of registered locale message keys instead of extracting English source messages.
* 删除顶部栏语言和GitHub顺序设计文档
* fix: 修复前端设置初始化时未返回 domains 数组导致的 undefined 错误
* refactor(i18n): consolidate locale infrastructure
* fix(i18n): stabilize locale route switching
* fix(i18n): persist default locale selection
* fix(i18n): 修复前端设置初始化时未返回 domains 数组导致的 undefined 错误,统一按空数组兜底处理
feat(i18n): 添加 locale 别名处理,支持默认语言的重定向
test(i18n): 增加对默认语言别名重定向的测试用例
* refactor: replace useAppI18n with useScopedI18n in multiple components for improved localization management
* fix(tests): 移除不必要的 URL 断言以简化 Passkey 测试
* fix(i18n): 更新语言切换逻辑,确保使用当前语言设置进行路由导航
* fix(i18n): 强制路由切换以确保语言切换后正确导航
* refactor(i18n): 优化消息注册和路由本地化逻辑,移除冗余代码
* refactor(i18n): 拆分 API 文件以优化路由管理,更新语言处理逻辑
* fix: align i18n release notes and frontend test script
- Upgrade version to 1.8.0 in all package.json files
- Add cf-temp-mail-release-notify skill with MarkdownV2 Telegram posting
- Optimize docs_deploy.yml to auto-trigger on Tag Build CI completion
- Add v1.8.0 placeholder in CHANGELOG.md and CHANGELOG_EN.md
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* fix: auto initialize default send balance
* fix: tighten send access auto init flow
* refactor: centralize send balance state
* fix: separate legacy repair from admin control in send balance
Add an `address_sender.source` column to distinguish legacy / auto /
user / admin rows. `ensureDefaultSendBalance` now only repairs rows
with `source IS NULL`, so admin-disabled and user-requested rows are
never overwritten. Admin POST writes tag `source = 'admin'`; new
auto-init inserts tag `'auto'`; `requestSendMailAccess` inserts tag
`'user'`.
Bumps DB_VERSION to v0.0.8 with the usual `PRAGMA table_info` guarded
ALTER, plus a standalone SQL patch under db/.
Adds E2E regressions: legacy repair path, admin-disabled rows stay
disabled across settings and send, send after admin deletion
auto-initializes a fresh row.
* fix: drop runtime legacy repair; backfill source='legacy' on migrate
Pre-v0.0.8 schema cannot distinguish legacy request-send-access
remnants from admin-disabled rows — both share `balance = 0,
enabled = 0`. Letting ensureDefaultSendBalance repair that shape on
upgrade could silently re-enable an admin-disabled row.
Remove the runtime repair path entirely:
- `ensureDefaultSendBalance` now uses `ON CONFLICT(address) DO NOTHING`;
existing rows are never touched.
- The v0.0.8 migration (and the matching SQL patch) backfills every
pre-existing row with `source = 'legacy'`, making pre-migration
state explicitly off-limits to runtime auto-init.
- E2E: flip the legacy test to the negative direction — a
`source='legacy'` zero-balance row stays untouched by settings
reads and send attempts. Harden `resetSenderToLegacy` to return
404 when `meta.changes < 1`.
- Update changelog and docs: legacy/admin-disabled rows must be
restored manually via the admin UI.
* refactor: collapse send balance auto-init to missing-row insert
Per review feedback: the runtime guarantee we actually need is
"create an address_sender row when one is missing, leave existing
rows alone". Once `ensureDefaultSendBalance` switched to
`ON CONFLICT DO NOTHING`, the `source` column, the v0.0.8 migration,
and the `resetSenderToLegacy` test endpoint became dead weight —
the DO NOTHING path already protects admin-disabled and admin-edited
rows without any provenance metadata.
- Drop `address_sender.source` and the v0.0.8 migration; revert
DB_VERSION to v0.0.7. No schema change ships with this PR.
- Strip the `source` field from `ensureDefaultSendBalance`,
`requestSendMailAccess`, and the admin-update path.
- Remove the `/admin/test/reset_sender_to_legacy` test endpoint and
its E2E helper; the negative legacy-repair test it served is no
longer needed because the runtime no longer touches existing rows.
- E2E coverage stays focused on the three guardrails: missing-row
auto-init, admin-disabled rows stay disabled, admin deletion
triggers a fresh re-insert.
- Tighten changelog and docs to "auto-initialize missing rows".
* docs: align common-issues with missing-row-only auto-init
The FAQ entries for "DEFAULT_SEND_BALANCE set but still No balance"
still described the old behaviour of repairing legacy
`balance = 0 && enabled = 0` rows. Rewrite both zh and en rows to
match the current runtime: only addresses with no existing
`address_sender` row get auto-initialised; legacy, admin-disabled,
and admin-edited rows must be restored manually through the admin
console.
* fix: harden send mail form validation
* fix: tighten send mail content checks
* fix: refine send mail empty-content checks
* fix: reset send mail preview state
- Upgrade deps across frontend/worker/pages/vitepress-docs (wrangler 4.82.2, dompurify 3.4.0, resend 6.11.0, etc.)
- Bump version to v1.7.0 in all package.json and worker constants
- Add v1.7.0 CHANGELOG placeholder; move #978/#930 Bug Fixes from v1.6.0 to v1.7.0 (merged after v1.6.0 tag)
- Add upgrade-dependencies skill; translate version-upgrade skill to English
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: respect user mail deletion toggle in user center
Hide user mailbox delete actions and block /user_api/mails deletion when ENABLE_USER_DELETE_EMAIL is disabled. Add an e2e regression test and changelog entries for issue #978.
* test: hash user password in mail deletion e2e
Use the same SHA-256 pre-hashed password format as the frontend for the user register/login flow in the mail deletion regression test.
* feat(admin): add IP whitelist (strict allowlist mode) (#920)
- Add enableWhitelist/whitelist fields to IpBlacklistSettings
- Implement three-layer access control: whitelist → blacklist → daily limit
- Whitelist uses exact match for IPv4/IPv6, regex for patterns
- Whitelisted IPs skip blacklist checks (trusted)
- Fail-closed when cf-connecting-ip missing under whitelist mode
- Frontend: independent whitelist toggle + empty list protection
- Backend: backward compatible (old frontends get defaults)
- E2E tests: config validation + runtime behavior
- Docs: CHANGELOG zh/en updated
Closes#920
* fix(admin): address PR review feedback on IP whitelist
- Add IPv4-mapped IPv6 (::ffff:x.x.x.x) exact match in isWhitelisted
- Include error.message in whitelist regex parse failure log
- Include actual/max size in whitelist size limit error message
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(admin): validate whitelist regex on save and preserve existing whitelist on partial update
- Reject invalid regex patterns in whitelist at save time to prevent runtime lockout
- Preserve existing enableWhitelist/whitelist from DB when older clients omit these fields
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(admin): revert P2 - keep simple ?? defaults for backward compat
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(admin): validate whitelist elements are strings before trimming
Prevents 500 error when whitelist contains non-string elements (e.g. numbers, null)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs(admin): add IP blacklist/whitelist documentation (zh + en)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(admin): fix fingerprint blacklist bypass when cf-connecting-ip absent, improve e2e tests
- Split checkBlacklist into checkFingerprintBlacklist (IP-independent) and checkIpAsnBlacklist
- Fingerprint check now runs before the !reqIp early-return to prevent bypass
- Add afterEach reset to config test group, extract RESET_SETTINGS constant
- Strengthen whitelist-blocks test to deterministic 403 assertion
- Add e2e tests: invalid regex rejection, non-string element rejection, fingerprint-blocks-without-IP
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(admin): suppress no-useless-escape lint warning in whitelist regex check
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: upgrade version to v1.6.0
- Update version number to 1.6.0 in all package.json files
- Add v1.6.0 placeholder in CHANGELOG.md and CHANGELOG_EN.md
* docs: update release skill to use bilingual format (zh + en collapsed)
* chore: upgrade dependencies
* fix: correct CHANGELOG placeholder position and update version-upgrade skill
* docs: update version-upgrade skill with correct CHANGELOG placeholder position
* feat(admin): add column sorting and reset pagination on search (#918)
- Add server-side column sorting for admin address list (ID, name, created_at, updated_at, mail_count, send_count)
- Reset pagination to page 1 when searching or changing sort order
- Add optional orderBy parameter to handleListQuery with whitelist validation
Closes#918
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add JSDoc warning for orderBy parameter in handleListQuery
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address code review findings
- Fix count not resetting to 0 when search returns empty results
- Add source_meta column sorting support
- Use Object.hasOwn to prevent prototype pollution in sort column lookup
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add localStorage fallback for OAuth2 session state on mobile browsers
Some mobile browsers (Safari ITP, WebViews) lose sessionStorage during
cross-origin OAuth2 redirects. Add localStorage fallback via computed
wrapper that dual-writes on set and reads sessionStorage-first on get.
Also cleanup state in finally block to ensure one-time consumption.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: i18n for 'code not found' in OAuth2 callback
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: mail-parser-wasm treat message/rfc822 attachments as regular attachments
Previously, message/rfc822 attachments (e.g. .eml files) were
recursively parsed for sub-attachments instead of being returned
directly, causing them to be silently dropped. Now all attachments
are returned regardless of type.
Bump version to 0.2.2. Add .gitignore for worker build artifacts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: add missing entries to worker .gitignore
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: bump mail-parser-wasm to 0.2.2 in frontend
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add Turnstile CAPTCHA for login forms (#767)
Add optional Turnstile verification for admin login, user login, and
address password login via ENABLE_LOGIN_TURNSTILE_CHECK env var.
Does not affect existing Turnstile on address creation / registration.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add ENABLE_LOGIN_TURNSTILE_CHECK to wrangler.toml.template
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: ensure openSettings loaded before admin login modal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add Turnstile to site access password and fix settings field name
- Add Turnstile to site access password modal in Header.vue
- Add /open_api/site_login endpoint for password + Turnstile verification
- Fix settings field name from enableTurnstileLogin to enableLoginTurnstileCheck
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: move login endpoints to open_api/auth.ts
Move /open_api/site_login and /open_api/admin_login from commom_api.ts
to a dedicated open_api/auth.ts file for better code organization.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: change Turnstile check failure status from 500 to 400
Turnstile validation failure is a client error, not a server error.
Change all Turnstile check error responses from 500 to 400.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use unique IDs for multiple Turnstile instances
When multiple modals with Turnstile appear simultaneously (e.g., site
access + admin login), the hardcoded id="cf-turnstile" causes conflicts.
Generate a unique container ID per Turnstile instance to fix this.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: review fixes - cfToken separation, register Turnstile, error codes
- Separate cfToken refs in Login.vue to avoid token sharing between
login and new address creation Turnstile instances
- Add Turnstile check to user registration endpoint (not just verify_code)
- Show Turnstile on register tab regardless of enableMailVerify
- Pass cf_token in register request body
- Fix site_login error message to use CustomAuthPasswordMsg
- Fix verifyCode Turnstile error status from 500 to 400
- Restore empty line in commom_api.ts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: separate register Turnstile logic for with/without mail verify
- With mail verify: verify_code already checks Turnstile, register
skips Turnstile (token is one-time use)
- Without mail verify: register checks Turnstile directly
- Separate loginCfToken for login tab to avoid token sharing with
register tab Turnstile
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add enableLoginTurnstileCheck to store defaults, simplify changelog
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add /open_api/credential_login for credential login verification
Add credential_login endpoint that verifies both Turnstile token and
JWT credential server-side, replacing the generic verify_turnstile
endpoint. Credential login now validates the JWT before accepting it.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: improve login endpoints - hash passwords, expose Turnstile refresh, fix status codes
- site_login/admin_login: always called, verify hashed password + optional Turnstile
- credential_login: always called, verify JWT + optional Turnstile
- Frontend sends hashed passwords instead of plaintext
- Turnstile component exposes refresh method via defineExpose
- Fix Turnstile error status 500→400 in mails_api and telegram_api
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: rename to ENABLE_GLOBAL_TURNSTILE_CHECK and add isGlobalTurnstileEnabled helper
- Rename ENABLE_LOGIN_TURNSTILE_CHECK -> ENABLE_GLOBAL_TURNSTILE_CHECK
- Add isGlobalTurnstileEnabled() in utils.ts: checks env var + Turnstile keys all present
- Backend settings returns enableGlobalTurnstileCheck computed from the helper
- All backend endpoints use isGlobalTurnstileEnabled(c) instead of raw env check
- Update all frontend refs, docs, changelog, and wrangler template
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: use utils.isGlobalTurnstileEnabled instead of named import
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add E2E tests for turnstile login endpoints
- Test all 3 new /open_api/* endpoints when ENABLE_GLOBAL_TURNSTILE_CHECK is disabled
- Verify settings returns enableGlobalTurnstileCheck: false
- Test admin_login with correct/wrong/empty hashed password
- Test site_login returns 401 when no PASSWORDS configured
- Test credential_login with valid JWT, invalid JWT, empty credential
- Test address_login with empty cf_token works when turnstile disabled
- Add ADMIN_PASSWORDS to E2E wrangler config for admin_login tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: rename test file to login-endpoints.spec.ts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: validate JWT payload has address field in credential_login
Prevents user tokens or challenge tokens from being accepted as
address credentials since they share the same JWT_SECRET.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: refresh Turnstile token on login failure to allow retry
After a failed login attempt, the consumed Turnstile token is now
refreshed so users can retry without manually refreshing.
Also adds ref to signup Turnstile in UserLogin.vue to refresh after
verification code is sent (single-use token consumed).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: separate Turnstile tokens for signup and reset password flows
Split shared cfToken into signupCfToken and resetCfToken to prevent
single-use Turnstile token conflicts between signup tab and reset
password modal. Each flow now has its own token ref and refreshes
the correct Turnstile widget after use.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: update comments from "login turnstile" to "global turnstile"
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
docs: add webhook preset templates and Telegram per-user push docs (#769)
Add Telegram Bot, WeChat Work, Discord webhook preset templates to
frontend and documentation. Add per-user mail push and global push
documentation for Telegram Bot.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* chore: upgrade dependencies
- dompurify 3.3.1 → 3.3.2
- naive-ui 2.43.2 → 2.44.0
- vue-i18n 11.2.8 → 11.3.0
- @cloudflare/workers-types 4.20260305.1 → 4.20260307.1
- @types/node 25.3.3 → 25.3.5
- wrangler 4.70.0 → 4.71.0 (all subprojects)
* feat: upgrade @simplewebauthn packages from v10 to v13
Breaking changes addressed:
- [v11] startRegistration/startAuthentication now take object param
- [v11] registrationInfo.credential replaces flat destructuring
- [v11] authenticator param renamed to credential in verifyAuthenticationResponse
- [v13] @simplewebauthn/types removed, types imported from @simplewebauthn/server
Packages:
- @simplewebauthn/server: 10.0.1 → 13.2.3
- @simplewebauthn/browser: 10.0.0 → 13.2.2
- @simplewebauthn/types: removed (deprecated)
* test: add passkey API E2E tests
- User registration and login flow
- register_request/authenticate_request return valid WebAuthn options
- authenticate_response with invalid credential returns 404
- register_response with invalid credential returns error
- Passkey list empty for new user
- Rename/delete operations with validation
* fix: use UI login instead of localStorage injection in browser passkey test
The localStorage approach doesn't work with VueUse's useStorage because
it doesn't detect external changes during page navigation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: hash password before registration to match frontend login behavior
The frontend hashes passwords with SHA-256 before sending to the API.
Registration via API must use the same hashed password so that UI login
matches the stored value.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: allow crypto.subtle in Docker browser tests
The frontend uses crypto.subtle for password hashing, which requires
a secure context (HTTPS or localhost). In Docker, the frontend runs
at http://frontend:5173 which is not a secure context. Add Chromium
flag to treat this origin as secure.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: serve frontend over HTTPS in Docker for WebAuthn secure context
WebAuthn (navigator.credentials) and crypto.subtle both require a
secure context (HTTPS or localhost). The Docker frontend was serving
over HTTP, making passkey operations impossible.
Changes:
- Generate self-signed cert in Dockerfile.frontend
- Configure Vite to serve over HTTPS
- Update FRONTEND_URL to https://
- Add ignoreHTTPSErrors to Playwright browser config
- Use localStorage injection for passkey test login
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add Vite proxy to avoid mixed-content blocking in HTTPS Docker frontend
HTTPS pages cannot make HTTP API requests (mixed content). Add a Vite
proxy for all API paths so the browser makes same-origin HTTPS requests,
which Vite proxies to the HTTP worker server-to-server.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: store userJwt without JSON.stringify in localStorage
VueUse's useStorage with a string default uses raw string serialization
(no JSON wrapping). Using JSON.stringify added double quotes around the
JWT token, causing 401 Unauthorized from the worker.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: clean up passkey API test per review feedback
Remove unused variables and rename test to match actual behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: auto-reply not triggering when source_prefix is empty (#459)
- Empty source_prefix now matches all senders (was short-circuiting as falsy)
- Support regex matching with /pattern/ syntax in source_prefix
- Backward compatible: plain strings still use startsWith
- Use E2E_TEST_MODE switch to skip cloudflare:email import in tests
- Track reply() calls in E2E mock for testability
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: update auto-reply UI labels for regex support
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update changelogs for auto-reply fix and regex feature
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: upgrade version to v1.5.0
- Update version number to 1.5.0 in all package.json files and constants.ts
- Split CHANGELOG: v1.4.0 entries finalized, new v1.5.0(main) section with auto-reply changes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add error logging for invalid regex in auto-reply source_prefix
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: address CodeRabbit review suggestions
- Use const object instead of let for mock state tracking
- Add log when auto-reply subject/message falls back to defaults
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add source_prefix regex syntax to auto-reply docs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Security fix: XSS bypass via jsdom raw-text tag parsing,
prototype pollution with custom elements, and lenient config
parsing in _isValidAttribute.
Supersedes #872
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: correct API path typo `requset_send_mail_access` → `request_send_mail_access`
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: correct typo in send-access E2E test (requset → request)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: sanitize mail content in reply/forward to prevent XSS
- Add DOMPurify to sanitize HTML email content (whitelist-based)
- Add escapeHtml for plain text content (escape &<>"')
- Guard mail.originalSource with fallback to empty string
- Add jsdom for vitest DOM environment (DOMPurify requires DOM)
- Add XSS regression tests (script tags, event handlers, HTML escape)
- Add contentType assertion for empty message fallback case
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add XSS sanitization E2E screenshots
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: remove temporary screenshots from tree
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: normalize escapeHtml input and add forward text escape test
- escapeHtml: convert input via String(str ?? '') to handle non-string values
- Add test for plain text forward with special chars (<, &, >)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: preserve HTML content when replying to HTML emails (#728)
Reply was using curMail.text (plain text) instead of curMail.message (HTML),
causing loss of original email formatting. Forward already used HTML correctly.
Now reply prefers HTML content with plain text fallback, matching forward behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add vitest unit tests for reply/forward mail logic
Extract buildReplyModel and buildForwardModel into testable utility
functions and add 13 unit tests covering HTML content preservation,
plain text fallback, sender parsing, and subject formatting.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: remove unnecessary vitest exclude config
The e2e files have been deleted, so the test.exclude config in
vite.config.js is no longer needed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: revert unnecessary trailing comma in vite.config.js
Restore vite.config.js to match main exactly — no changes needed
for this PR.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add e2e screenshots for PR review
Screenshots from local Playwright test showing:
1. HTML email rendered correctly in inbox
2. Reply editor preserving HTML content in blockquote
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: remove temporary test screenshots
Screenshots have been posted as PR comment, no longer needed in tree.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use html contentType for HTML email replies instead of rich
wangEditor (rich text editor) strips block-level HTML tags inside
blockquote, losing all formatting. Use contentType 'html' for HTML
email replies (matching forward behavior) so content is edited as
raw HTML in a textarea, preserving all formatting.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: update e2e screenshots showing HTML formatting preserved
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: remove temporary screenshots from tree
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add SMTP send flow E2E screenshots with mailpit
Screenshots showing complete SMTP HTML email reply flow:
1. View rich HTML email (gradient headers, tables, badges)
2. Reply compose with HTML mode (textarea, not wangEditor)
3. Sent box showing preserved HTML formatting
4. Mailpit inbox receiving the SMTP email
5. Mailpit email detail with full HTML rendering
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: remove temporary SMTP test screenshots from tree
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- Add different messages based on mail count (empty vs select)
- Add semantic icons (InboxRound for inbox, SendRound for sent)
- Unify list container height to min-height: 60vh; max-height: 100vh
- Update CHANGELOG for v1.4.0
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Add isSendMailEnabled and isAnySendMailEnabled functions in common.ts
- Return enableSendMail field in /open_api/settings
- Hide sendmail tab, sendbox tab, and reply button when send mail is not configured
- Check RESEND_TOKEN, SMTP_CONFIG, and SEND_MAIL binding per domain
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Change updateAddressUpdatedAt to non-blocking async execution
- GET /api/mails, /api/settings, /user_api/settings no longer wait for DB update
- Improves response time for GET requests
- Also updates dependencies
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Add optional `icon` field to UserOauth2Settings type
- Include preset SVG icons for GitHub, Linux Do, and Authentik templates
- Render icons on OAuth2 login buttons
- Add icon configuration UI with preview in admin panel
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat(oauth2): add email format transformation support
- Add enableEmailFormat, userEmailFormat, userEmailReplace fields
- Support regex pattern matching and replacement template ($1, $2, etc.)
- Add Linux Do OAuth2 template with email format pre-configured
- Add input length limit (256 chars) to prevent ReDoS attacks
- Update admin UI with conditional display and tooltips
- Update documentation (zh/en) with configuration examples
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: update lock files and version
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: restore accessTokenFormat as optional field
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Use Gmail's #A8C7FA color for AI extraction alert and tag in dark mode
- Update CHANGELOG.md and CHANGELOG_EN.md for v1.2.1
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
fix: scheduled task cleanup error "e.get is not a function"
- Use optional chaining in i18n.getMessagesbyContext to safely access Context methods
- Update version to v1.2.1
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>