Feature status: ACCOUNT-001 in the canonical register.
Proposal. The current codebase is built around one active account per network.
The user wants to run 2+ Threads accounts (and likely the same for X/Facebook later). Today:
SocialAccountalready has@@unique([network, handle]), so multiple rows per network are possible.AccountsService.findByNetwork()returns the first active account viafindFirst.SessionsService.getOrCreateSession()keys sessions bynetwork, not account.RateLimitServiceuses Redis keys likerate_limit:x, shared across all accounts on a network.BrowserFactorypools contexts per network, not per account.PostingServiceusespost.accountIdbut assumes a single account is available.GenerationServicepicks one account per network per topic.
This means a second Threads account cannot be posted to independently, and adding one risks platform-side account correlation (same browser fingerprint / cookies / IP).
An operator can configure N social accounts per network in the same SPA instance. Each account:
- has its own handle/credentials/cookies,
- has its own persistent session pool,
- has its own rate-limit counter,
- can be enabled/disabled independently,
- is selectable/visible in the dashboard,
- gets distinct content when per-account prompts (Feature 04) are enabled.
No schema rewrite is required for the core table because SocialAccount already supports (network, handle) uniqueness. Add small helper fields:
model SocialAccount {
// existing fields ...
displayName String? // human label, e.g. "Soulwise US"
priority Int @default(0) // higher = preferred for round-robin
groupId String? // optional AccountGroup for shared proxy/fingerprint
fingerprintSeed String? // deterministic seed for per-account browser fingerprint
proxyUrl String? // optional per-account proxy override
active Boolean @default(true)
// ...
}
model AccountGroup {
id String @id @default(uuid())
name String
proxyUrl String? // shared rotating/sticky proxy for all accounts in group
timezone String? // e.g. America/New_York
fingerprintProfile Json? // Camoufox per-context fingerprint preset
accounts SocialAccount[]
}Accounts for the same network should be selectable by priority and active.
Keep env as the source of truth for credentials. Support numbered account suffixes:
SOCIAL_THREADS_USERNAME_1=account_a
SOCIAL_THREADS_PASSWORD_1=...
SOCIAL_THREADS_COOKIES_1=...
SOCIAL_THREADS_ACTIVE_1=true
SOCIAL_THREADS_USERNAME_2=account_b
SOCIAL_THREADS_PASSWORD_2=...
...
AccountsService.seedFromEnv() should loop indices until no SOCIAL_{NETWORK}_USERNAME_{N} is found. Existing un-suffixed vars map to index 1 for backward compatibility.
findByNetwork(network)→ returns an array of active accounts sorted bypriority DESC, createdAt ASC.- Add
findById(id). - Add
getCredentials(account)(by account row, not by network). - Add
getNextAccountForNetwork(network, options?)for round-robin / priority selection. - Update controller:
GET /accountsreturns all accounts;GET /accounts?network=THREADSfilters.
- Change key from
login:{network}tologin:{accountId}. getOrCreateSession(accountId, network).- Each account gets its own
Sessionrow keyed byaccountId. - For Threads: remember that every Threads account is backed by an Instagram account;
credentialsRefper account must point to the matching Instagram credentials.
- Pooled contexts keyed by
network:accountIdinstead ofnetwork. - For Facebook persistent contexts: store each account in a separate
CAMOUFOX_PROFILE_DIR/{network}/{handle}directory. - Use Camoufox per-context fingerprint isolation (
context.addInitScript) with a deterministic seed derived fromaccountId. Relevant upstream patch docs:packages/backend/src/infrastructure/browser/browser.factory.tscurrently launches one global Camoufox identity; per-context patches are documented at https://github.com/daijro/camoufox/blob/adc44fc8/docs/per-context-patches.md. - Optional per-account proxy via
BrowserFactorylaunch args or context proxy.
postById()already haspost.accountId; load the account and pass it to session/poster selection.- Use account-specific session and browser context.
- Queue job id stays
postId(posts are unique per account after generation).
- Include
accountIdin Redis keys:spa:ratelimit:{network}:{accountId}:day. - The existing Lua script only needs a composite
networkstring (e.g."X:account_123"); however, per-account limits must be configurable (see Feature 02). - This overlaps with
docs/refactor/phase-6-7-p3-strategic-features.md7.5 "Per-account rate limit keys".
Current queues are spa-posting-x, spa-posting-threads, spa-posting-facebook with concurrency=1.
Options:
- Single queue per network, concurrency=1 (simplest): all accounts on a network serialize. One stuck account blocks others.
- One queue per account:
spa-posting-x:{accountId}with concurrency=1. Better isolation but more Redis queues and workers. - One queue per network with worker concurrency = active accounts + per-account mutex: complex but preserves queue count.
Recommendation: start with option 2 behind a flag BULLMQ_PER_ACCOUNT_QUEUES=true. When disabled, fall back to option 1 for small deployments.
If accounts share the same prompt/brand voice, the same generated post can be round-robin assigned to accounts. If per-account prompts are enabled (Feature 04), each account needs its own graph run. For this feature:
- Accept an optional
accountIdin generation entry points. - If no
accountIdis passed, pick the next active account for each network (round-robin) and writepost.accountId. - In a future phase, run the graph per account when per-account prompts are on.
GET /accounts— list all accounts with status and latest session.POST /accounts— create account (credentials still come from env by reference;credentialsRefstored).PATCH /accounts/:id— enable/disable, setdisplayName,priority,groupId.DELETE /accounts/:id— soft-delete (setactive=false) to preserve history.- Dashboard accounts card with per-account post/session/rate-limit status.
This is the highest-risk feature because of account correlation.
- Browser fingerprint isolation is non-negotiable. Multiple Threads accounts accessed from the same browser profile/cookies/IP will be linked by Meta. Use per-context Camoufox fingerprints and, ideally, per-account residential proxies.
- Threads is not standalone. Each Threads account is an Instagram account. One Instagram account = one Threads profile. Multi-account means managing separate Instagram credentials per account. See external research: https://blog.send.win/manage-multiple-threads-accounts-multi-account-management-guide-2026/
- Stagger posting times and avoid cross-posting identical text across accounts.
- Session cookies/storage must be isolated per account. The current
Session.storageStateis perSessionrow and keyed toaccountIdalready, so the schema is ready; the code path needs to load the right session. - Rate limits per account prevent one hot account from starving others.
-
AccountsServiceseeds N accounts per network from indexed env vars. -
findByNetworkreturns an array and all callers handle it. -
SessionsServicecreates/loads sessions peraccountId. -
BrowserFactoryprovides isolated contexts per account (pooled + fingerprint seed). -
RateLimitServicekeys includeaccountId. -
PostingServiceposts using the account attached to thePostrow. -
GenerationServiceassigns an account to each generatedPost. - Queue worker can process posts for multiple accounts without cross-account cookie leaks.
- UI lists and manages accounts per network.
- Dry-run mode works for each account independently.
- Should accounts be grouped by
AccountGroupfor shared proxy/fingerprint profile, or should every account get its own proxy? - How do we rotate accounts when posting the same topic? Round-robin, priority, or manual per-post selection?
- Do we allow per-account
ENABLED_NETWORKS, or is that still global? - For Threads/Instagram, do we store the Instagram handle separately, or reuse
handle?
L (2-4 weeks). Touches account model, sessions, browser factory, rate limits, queue, posting, generation, and UI. Browser isolation is the hard part.
docs/reviews/accounts.md(F5 multi-account idea)docs/refactor/phase-6-7-p3-strategic-features.md7.5 per-account rate-limit keysdocs/features/multi-instance-distribution.mdpackages/backend/src/modules/accounts/accounts.service.tspackages/backend/src/modules/sessions/sessions.service.tspackages/backend/src/infrastructure/browser/browser.factory.ts