Add 2925 account pool binding helper

This commit is contained in:
2026-05-11 16:07:33 +08:00
parent 26f42427be
commit 228d650ca8
3 changed files with 326 additions and 0 deletions
@@ -0,0 +1,153 @@
# 2925 account pool integration notes — 2026-05-11
Decision: abandon POP3/SMTP/IMAP forwarding to ClawEmail. Use the original extension's 2925 webmail/account-pool model directly.
## Account pool
Secret file:
```text
~/.hermes/secrets/mail2925_accounts.txt
```
Format:
```text
邮箱----密码
user@2925.com----password
```
BOSS provided 20 accounts (`chickliu2021@2925.com` through `chickliu2040@2925.com`). Passwords are stored only in the local secret file with mode `0600`; never print or commit raw passwords.
State file:
```text
~/.hermes/state/mail2925_accounts_state.json
```
State fields per account:
```json
{
"lastUsedAt": 0,
"lastLoginAt": 0,
"lastLimitAt": 0,
"disabledUntil": 0,
"lastError": ""
}
```
## Hard invariant: alias/account binding
A generated 2925 registration address must be derived from the same 2925 account used to read mail.
Example:
```text
selected account: chickliu2021@2925.com
allowed alias: chickliu2021rl9sxh@2925.com
mail reader: logged in as chickliu2021@2925.com
```
Forbidden:
```text
alias from chickliu2021*, but mailbox logged in as chickliu2022@2925.com
```
Gate before reading any code:
```text
displayedMailboxEmail == selectedAccount.email
alias.localPart startsWith selectedAccount.localPart
```
If the displayed mailbox email differs, clear 2925 cookies and force-login the selected account before continuing.
## Alias generation
Use original extension rule, not Gmail-style plus tags:
```text
baseLocal + randomSuffix(6) + @2925.com
```
Example:
```text
chickliu2021@2925.com -> chickliu2021a8k2mz@2925.com
```
Do not generate `chickliu2021+tag@2925.com`.
## Account selection
Pick among accounts that are:
```text
enabled != false
have password
disabledUntil <= now
```
Sort by:
```text
lastUsedAt ascending, then email ascending
```
This rotates accounts and avoids overusing one mailbox.
## Limit handling
Detect 2925 limit messages:
```text
子邮箱已达上限
已达上限邮箱
子邮箱上限
邮箱已达上限
```
On hit:
```text
currentAccount.disabledUntil = now + 24h
currentAccount.lastLimitAt = now
currentAccount.lastError = reason
switch to next available account
terminate current OpenAI attempt and restart next round with a fresh alias
```
Do not continue the same OpenAI page after switching 2925 account; it risks mixing alias/account state.
## Local helper
```bash
python3 scripts/mail2925_pool.py selftest
python3 scripts/mail2925_pool.py list
python3 scripts/mail2925_pool.py pick
python3 scripts/mail2925_pool.py mark-limit chickliu2021@2925.com --reason '子邮箱已达上限邮箱'
python3 scripts/mail2925_pool.py mark-login chickliu2021@2925.com
```
`pick` prints only email + generated alias; never prints passwords.
Verified locally on 2026-05-11:
```json
{"accounts":20,"aliasFailures":[],"secretMode":"0o600","ok":true}
```
## Webmail flow to implement/use
1. Pick account and generate alias in one operation.
2. Open/login `https://2925.com/login/` or `https://2925.com/#/mailList`.
3. Verify displayed mailbox email equals picked account email.
4. Clear inbox before triggering OpenAI code.
5. Submit generated alias to OpenAI.
6. Poll 2925 inbox: 15 attempts, 15s interval.
7. Match OpenAI/auth/verify/code/login sender/subject/body; extract 6-digit code.
8. Open matching mail, extract body code, delete mail, return inbox.
9. Submit code; if invalid, add to rejectedCodes, resend, and continue polling.
10. On 2925 limit, mark current account cooldown and restart with next account.