Appearance
Epic 44 — Paid Messaging & One-Message-Then-Wait Rule
✅ SHIPPED 2026-06-18 (backend
07ccb5a, frontend06dc4d9). Recipient-typed cost (PRODUCER 2500 / talent·pet-owner·agency 2000) from the recipient'sproducerProfile; non-rejectedMatch/PetMatch(either axis) waives cost + turn rule; one-message-then-wait enforced server-side (AWAITING_REPLY); charged per outbound turn viadeductCasTarsbefore the message is created;messagingPolicy(recipientId, conversationId)drives the FE pre-send disclosure + composer lock (ConversationPage handles draft + existing; NewMessagePage routes into the draft). Gated by the existingDIRECT_MESSAGE_CREDIT_COSTflag. The flatDIRECT_MESSAGE_CREDITFEATURE_COSTS entry is now dead-but-retained. BE 1404 + FE 564 tests green.
Reworks: Epic 17 (Messaging) + the current
DIRECT_MESSAGE_CREDIT(50 CasTars cold-outreach) model. Decision (user, 2026-06-16): Messaging costs CasTars by recipient type — 2,000 to message a Talent, 2,500 to message a Producer. A user may send only one message and must wait for a reply before sending again, and this must be made clear before the first message.
Why: Today opening a cold conversation costs a flat 50 CasTars (waived when users share a job context) with no rate limit. The stakeholders want a much higher, recipient-typed cost and a strict turn-taking rule to keep outreach high-intent and reduce spam. Remember talent = Talent + Pet Owner for the 2,000 cost.
Architecture: Replace FEATURE_COSTS.DIRECT_MESSAGE_CREDIT with a recipient-type cost resolved from the recipient's profile: TALENT/PET_OWNER/AGENCY → 2000, PRODUCER → 2500 (decision 2026-06-16: Agency = talent cost). Charge on each outbound message that opens a new turn. Enforce the one-message rule server-side: a sender may have at most one unanswered outbound message in a conversation; the next send is blocked until the recipient replies. Decision (user, 2026-06-16): messaging inside a mutual Match (Epic 43) is EXEMPT (free) — a match is mutual opt-in, so it replaces the old shared-job-context waiver; the cost + one-message rule apply only to non-matched (cold) outreach.
BE-MSG-COST-001 — Recipient-typed message cost
Files:
- Edit:
castyou-backend/src/services/messaging/index.ts+src/services/castars/features.ts— remove the flatDIRECT_MESSAGE_CREDIT(50); replace theusersShareJobContextwaiver with a match waiver (a conversation between two users who share an activeMatchis free); resolve cost from recipient profile type (2000 talent/pet-owner/agency, 2500 producer); deduct via the CasTars ledger on send; throwINSUFFICIENT_BALANCEwhen short.
Acceptance criteria: Sending to a talent/agency costs 2000, to a producer 2500; a matched conversation is free; insufficient balance blocks the send with a clear error.
BE-MSG-COST-002 — One-message-then-wait enforcement
Files:
- Edit:
castyou-backend/src/models/mongo/Conversation.ts/Message.ts+src/services/messaging/index.ts— track the last outbound sender; reject a second outbound message from the same sender until the counterpart replies (code: 'AWAITING_REPLY').
Acceptance criteria: A sender's 2nd consecutive message (no reply yet) is rejected; after a reply, they can send again (and are charged again).
FE-MSG-COST-001 — Pre-send disclosure + turn-aware composer
Files:
- Edit:
castyou-frontend/apps/app/src/pages/messages/{ConversationPage,NewMessagePage}.tsx— before the first message, a clear notice/modal: the exact cost (by recipient type) and the one-message-then-wait rule; disable the composer while awaiting a reply or when balance is insufficient; show the cost on the send button. DS + i18n en/pt/es.
Acceptance criteria: The cost + one-message rule are shown before the first send; the composer disables while awaiting a reply or underfunded.
TEST-MSG-COST-001 — Messaging cost coverage
Acceptance criteria: Recipient-typed cost, insufficient-balance block, and the awaiting-reply lock are tested; the disclosure renders.