* feat: add cf-temp-mail-usage skill and parsed mail API for AI agents
- feat: new /api/parsed_mails and /api/parsed_mail/:id endpoints returning
server-parsed subject/text/html/attachments metadata (reuses commonParseMail)
- feat: add .claude/skills/cf-temp-mail-usage read-only skill so AI agents
(OpenClaw / Codex / Cursor) can consume a mailbox with a user-supplied JWT,
bypassing the Turnstile challenge required for mailbox creation
- refactor: split mails_api/index.ts and admin_api/index.ts into thin route
shells; move business logic into dedicated *_api.ts files
- docs: update README / README_EN / CHANGELOG with agent-email feature and
npx degit install instructions for the skill
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat: rename skill to cf-temp-mail-agent-mail, add agent-email docs, fix sender trim
- Rename skill from cf-temp-mail-usage to cf-temp-mail-agent-mail
- Rewrite SKILL.md: parsed API primary, local fallback, prerequisites, multi-agent install
- Add vitepress docs (zh + en) for AI Agent mailbox usage
- Fix leading space in parsed_mail_api sender field via .trim()
- Update README install section with 3 install methods
- Update changelogs (zh + en)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* docs: simplify README agent skill section to one-liner with links
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat: add send mail API to skill, credential persistence, remove poll example
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
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.
* feat(mail): support gzip compressed email storage in D1 raw_blob column
Add ENABLE_MAIL_GZIP env var to optionally gzip-compress incoming emails
into a new raw_blob BLOB column, saving D1 storage space. Reading is
backward-compatible: prioritizes raw_blob (decompress) with fallback to
plaintext raw field. Includes DB migration v0.0.7, docs, and changelogs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: gzip fallback on missing column + decouple resolve from handleListQuery
- email/index.ts: gzip INSERT failure now falls back to plaintext INSERT
instead of silently losing the email (P1: data loss prevention)
- common.ts: add handleMailListQuery for raw_mails-specific list queries
with resolveRawEmailList, keeping handleListQuery generic
- Replace handleListQuery → handleMailListQuery in mails_api, admin_mail_api,
user_mail_api (only raw_mails callers)
- Add e2e test infrastructure: worker-gzip service, wrangler.toml.e2e.gzip,
api-gzip playwright project, mail-gzip.spec.ts with 4 test cases
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address CodeRabbit review feedback for gzip feature
- Use destructuring in resolveRawEmailRow to truly remove raw_blob key
- Narrow fallback scope: only fallback to plaintext on compression failure
or missing raw_blob column, re-throw other DB errors
- Clean unused imports in e2e gzip test
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add try-catch in resolveRawEmail to prevent single corrupt blob from failing entire list
A corrupted raw_blob would cause decompressBlob to throw, which with
Promise.all in resolveRawEmailList would reject the entire batch query.
Now catches decompression errors and falls back to row.raw field.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(mail): align sendAdminInternalMail with gzip storage path
sendAdminInternalMail now respects ENABLE_MAIL_GZIP: compresses to
raw_blob when enabled, with fallback to plaintext on failure.
Added e2e test verifying admin internal mail is readable under gzip.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(e2e): match admin internal mail by body content instead of encoded subject
mimetext base64-encodes the Subject header, so the raw MIME string
does not contain the literal subject text. Match on body content
(balance: 99) which is plaintext.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(e2e): add WORKER_GZIP_URL guard and length assertions in gzip tests
Address CodeRabbit feedback:
- Skip gzip tests when WORKER_GZIP_URL is not set to prevent false positives
- Assert results array length before accessing [0] for clearer error messages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(mail): narrow gzip fallback scope and fix webhook query compatibility
- sendAdminInternalMail: separate compress vs DB error handling, only
fallback to plaintext on compression failure or missing raw_blob
column, rethrow other DB errors (aligns with email/index.ts)
- Webhook test endpoints: use SELECT * instead of explicit raw_blob
column reference, so pre-migration databases don't 500
- Docs/changelog: clarify that db_migration must run before enabling
ENABLE_MAIL_GZIP
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(telegram): use generic Record type for raw_mails query result
Align with other query sites — avoid hardcoding raw_blob in the
TypeScript type annotation so the query works with or without the
column after migration.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(models): add RawMailRow type and unify raw_mails query typing
Add RawMailRow type to models with raw_blob as optional field, replacing
ad-hoc Record<string, unknown> and inline type annotations across
webhook test endpoints, telegram API, and gzip utilities.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
fix: update address timestamp on send mail, refactor TG attachment guard
- Call updateAddressUpdatedAt after successful send mail to keep
address activity timestamp up to date
- Refactor Telegram attachment push: replace early return with if block
to prevent skipping future logic after attachment section
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>
* 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>
- 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>
* feat: add i18n support for backend API and Telegram bot
- Add comprehensive i18n support for all backend API error messages (zh/en)
- Add /lang command for Telegram bot to set language preference
- Add bilingual command descriptions for Telegram bot
- Support per-user language preference stored in KV
- Global push uses DEFAULT_LANG, user push uses saved preference
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: improve Telegram bot language preference feature
- Add internationalized message for disabled language feature
- Fix hardcoded English message in /lang command
- Optimize getTgMessages calls (reduce from 3 to 1 call)
- Remove verbose comments for better code clarity
- Add TgLangFeatureDisabledMsg to i18n (zh/en)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
- Add source_meta field to address table for tracking creation source
- Web: records client IP address (with fallback to 'web:unknown')
- Telegram: records 'tg:{userId}'
- Admin: records 'admin'
- Add database migration with field existence check
- Add frontend display in admin Account page
- Backward compatible: fallback if field doesn't exist
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-authored-by: Claude <noreply@anthropic.com>
- Add clear inbox/sent items APIs for users and admins
- Implement ENABLE_USER_DELETE_EMAIL permission checks
- Fix multilingual support for success messages
- Update Vue to 3.5.21 and Wrangler to 4.34.0
- Add UI components for clearing email data in account settings
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-authored-by: Claude <noreply@anthropic.com>
- Add enableAllowList flag to webhook settings for flexible access control
- Update frontend UI with toggle switch and improved user experience
- Maintain backward compatibility with default allow-all behavior
- Add input validation hints and better form controls across admin panels
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-authored-by: Claude <noreply@anthropic.com>
* feat: add var DISABLE_CUSTOM_ADDRESS_NAME and CREATE_ADDRESS_DEFAULT_DOMAIN_FIRST
* fix: enhance input validation with trim() for address creation
- Add trim() handling in newAddress() function to prevent whitespace issues
- Add trim() handling for address prefixes to ensure consistent formatting
- Add trim() handling in Telegram API address parsing for robustness
- Prevents edge cases with whitespace-only or padded input strings
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat: Junk mail only check JUNK_MAIL_FORCE_PASS_LIST
* feat: add `JUNK_MAIL_CHECK_LIST` for check exits and passed item && add `ParsedEmailContext` to cache the parsed Email