Files
cloudflare_temp_email/worker/wrangler.toml.template
jiaxin ebeb94ed23 fix: auto initialize default send balance (#985)
* 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.
2026-04-20 12:40:14 +08:00

174 lines
7.0 KiB
Plaintext

name = "cloudflare_temp_email"
main = "src/worker.ts"
compatibility_date = "2025-04-01"
compatibility_flags = [ "nodejs_compat" ]
keep_vars = true
# if you want use custom_domain, you need to add routes
# routes = [
# { pattern = "temp-email-api.xxxxx.xyz", custom_domain = true },
# ]
# if you want deploy worker with frontend assets, you need to add assets
# [assets]
# directory = "../frontend/dist/"
# binding = "ASSETS"
# run_worker_first = true
# enable cron if you want set auto clean up
# [triggers]
# crons = [ "0 0 * * *" ]
# send_email = [
# { name = "SEND_MAIL" },
# ]
# SEND_MAIL_DOMAINS = ["example.com", "mail.example.com"]
[vars]
# DEFAULT_LANG = "zh"
# TITLE = "Custom Title" # custom title
# ANNOUNCEMENT = "Custom Announcement"
# always show ANNOUNCEMENT even no changes
# ALWAYS_SHOW_ANNOUNCEMENT = true
PREFIX = "tmp"
# address check REGEX, if not set, will not check
# ADDRESS_CHECK_REGEX = "^(?!.*admin).*"
# address name replace REGEX, if not set, the default is [^a-z0-9]
# ADDRESS_REGEX = "[^a-z0-9]"
# (min, max) length of the adderss, if not set, the default is (1, 30)
# MIN_ADDRESS_LEN = 1
# MAX_ADDRESS_LEN = 30
# Disable custom email address name, if set true, users cannot input custom email name, will auto generate
# DISABLE_CUSTOM_ADDRESS_NAME = true
# IF YOU WANT TO MAKE YOUR SITE PRIVATE, UNCOMMENT THE FOLLOWING LINES
# PASSWORDS = ["123", "456"]
# For admin panel
# ADMIN_PASSWORDS = ["123", "456"]
# warning: no password or user check for admin portal
# DISABLE_ADMIN_PASSWORD_CHECK = false
# ADMIN CONTACT, CAN BE ANY STRING
# ADMIN_CONTACT = "xx@xx.xxx"
# Create new address with default domain first, if set true, will use first domain from DEFAULT_DOMAINS when no domain specified
# CREATE_ADDRESS_DEFAULT_DOMAIN_FIRST = false
DEFAULT_DOMAINS = ["xxx.xxx1" , "xxx.xxx2"] # domain name for no role users
DOMAINS = ["xxx.xxx1" , "xxx.xxx2"] # all domain names
# Allow /api/new_address and /admin/new_address to accept subdomains that end with an allowed base domain
# e.g. if DOMAINS contains "abc.com", API can accept "team.abc.com" and "dev.team.abc.com"
# ENABLE_CREATE_ADDRESS_SUBDOMAIN_MATCH = true
# Allow optional random subdomain generation for the listed base domains
# e.g. name@abc.com => name@r4nd0m.abc.com
# RANDOM_SUBDOMAIN_DOMAINS = ["abc.com"]
# RANDOM_SUBDOMAIN_LENGTH = 8
# For chinese domain name, you can use DOMAIN_LABELS to show chinese domain name
# DOMAIN_LABELS = ["中文.xxx", "xxx.xxx2"]
# USER_DEFAULT_ROLE = "vip" # default role for new users(only when enable mail verification)
# ADMIN_USER_ROLE = "admin" # the role which can access admin panel
# User roles configuration, if domains is empty will use default_domains, if prefix is null will use default prefix, if prefix is empty string will not use prefix
# USER_ROLES = [
# { domains = ["xxx.xxx1" , "xxx.xxx2"], role = "vip", prefix = "vip" },
# { domains = ["xxx.xxx1" , "xxx.xxx2"], role = "admin", prefix = "" },
# ]
JWT_SECRET = "xxx"
BLACK_LIST = ""
# Allow users to create email addresses
ENABLE_USER_CREATE_EMAIL = true
# Disable anonymous user create email, if set true, users can only create email addresses after logging in
# DISABLE_ANONYMOUS_USER_CREATE_EMAIL = true
# Allow users to delete messages
ENABLE_USER_DELETE_EMAIL = true
# Allow automatic replies to emails
ENABLE_AUTO_REPLY = false
# Allow webhook
# ENABLE_WEBHOOK = true
# Enable address password feature, if set true, will generate password for new address and support password login and change
# ENABLE_ADDRESS_PASSWORD = false
# Footer text
# COPYRIGHT = "Dream Hunter"
# DISABLE_SHOW_GITHUB = true
# Status monitoring page URL
# STATUS_URL = "https://status.example.com"
# default send balance, auto initialized when users open settings or send mail; if not set, it will be 0
# DEFAULT_SEND_BALANCE = 1
# the role which can send emails without limit, multiple roles can be separated by ,
# NO_LIMIT_SEND_ROLE = "vip"
# Turnstile verification
# CF_TURNSTILE_SITE_KEY = ""
# CF_TURNSTILE_SECRET_KEY = ""
# Enable global Turnstile check for all login forms (requires Turnstile keys above)
# ENABLE_GLOBAL_TURNSTILE_CHECK = true
# telegram bot
# TG_MAX_ADDRESS = 5
# telegram bot info, predefined bot info can reduce latency of the webhook
# TG_BOT_INFO = "{}"
# allow user to switch language via /lang command
# TG_ALLOW_USER_LANG = true
# enable sending email attachments via Telegram push (50MB per file limit)
# ENABLE_TG_PUSH_ATTACHMENT = true
# global forward address list, if set, all emails will be forwarded to these addresses
# FORWARD_ADDRESS_LIST = ["xxx@xxx.com"]
# subdomain forward address list, if set, subdomain emails will be forwarded to these addresses
# SUBDOMAIN_FORWARD_ADDRESS_LIST = """
# [
# {"domains":[""],"forward":"xxx1@xxx.com"},
# {"domains":["subdomain-1.domain.com","subdomain-2.domain.com"],"forward":"xxx2@xxx.com"}
# ]
# """
# Frontend URL
# FRONTEND_URL = "https://xxxx.xxx"
# Enable check junk mail
# ENABLE_CHECK_JUNK_MAIL = false
# junk mail check list, if status exists and status is not pass, will be marked as junk mail
# JUNK_MAIL_CHECK_LIST = = ["spf", "dkim", "dmarc"]
# junk mail force check pass list, if no status or status is not pass, will be marked as junk mail
# JUNK_MAIL_FORCE_PASS_LIST = ["spf", "dkim", "dmarc"]
# remove attachment if size exceed 2MB, mail maybe mising some information due to parsing
# REMOVE_EXCEED_SIZE_ATTACHMENT = true
# remove all attachment, mail maybe mising some information due to parsing
# REMOVE_ALL_ATTACHMENT = true
# enable gzip compressed email storage in raw_blob column (run db_migration first)
# ENABLE_MAIL_GZIP = true
# AI email extraction, automatically extract verification codes, auth links, etc.
# ENABLE_AI_EMAIL_EXTRACT = true
# AI model name, choose from https://developers.cloudflare.com/workers-ai/models/#text-generation
# AI_EXTRACT_MODEL = "@cf/meta/llama-3.1-8b-instruct"
# Calling other woker to process email
# ENABLE_ANOTHER_WORKER = false
# ANOTHER_WORKER_LIST = """
# [
# {
# "binding":"AUTH_INBOX",
# "method":"rpcEmail",
# "keywords":[
# "验证码","激活码","激活链接","确认链接","验证邮箱","确认邮件","账号激活","邮件验证","账户确认","安全码","认证码","安全验证","登陆码","确认码","启用账户","激活账户","账号验证","注册确认",
# "account","activation","verify","verification","activate","confirmation","email","code","validate","registration","login","code","expire","confirm"
# ]
# }
# ]
# """
[[d1_databases]]
binding = "DB"
database_name = "xxx"
database_id = "xxx"
# Workers AI binding (required for AI email extraction)
# [ai]
# binding = "AI"
# kv config for send email verification code
# [[kv_namespaces]]
# binding = "KV"
# id = "xxxx"
# ratelimit config for /api/new_address
# [[unsafe.bindings]]
# name = "RATE_LIMITER"
# type = "ratelimit"
# namespace_id = "1001"
# # 10 requests per minute
# simple = { limit = 10, period = 60 }
# binding another worker service (parse the code or link), e.g. auth-inbox
# [[services]]
# binding = "AUTH_INBOX"
# service = "auth-inbox"