Access and routing
One lazy-loaded route
/messages loads MESSAGES_ROUTES on demand and renders a single component, MessageContainerComponent. Creators and fans share the same page, and the UI branches on userProfile.is_creator.
Guards
authGuard,permissionGuard, andcreatorApprovalGuardrun before the route activates.- Delegate users need the
SEND_REPLY_TO_DIRECT_MESSAGESpermission to use the page. - The
canDeactivateGuardblocks leaving while the send queue still has items, and the component also listens tobeforeunload.
Route-scoped voice recorder
The route provides its own AudioRecordingService, so each visit gets a fresh recorder that is discarded on exit.
Deep links
?user=opens or creates a conversation with that username or refer id.?id=sets the tip-back id used to jump to a conversation after a tip.history.state.user_keyandhistory.state.gsi4pkstart a direct message or broadcast from another screen.history.state.groupIdreopens a message list right after it is created.- Returning from payment lands back on
/messageswith the user query param.
Layout and tabs
Two-pane inbox
A conversation list on the left and the open thread with its composer on the right. On mobile only one pane shows at a time, and a close button on the thread returns to the list.
Top tabs
- Messages for everyone.
- Setup Automated Messages only for users with the
AUTOMATED_MESSAGESpermission, and on white-label sites only for the host owner.
Creator sub-tabs
Creators split their inbox into Priority, General, and Message Lists. Priority and General show unread counts. Fans do not see the tab bar and get a single General list.
Under-18 restriction
Users under 18 can read existing threads, but the composer, the New Message button, and the tip control are not rendered for them. In place of the composer they see You must be 18 years or older to send Direct Messages with a Customer Support link to the help page, for people who only need help with a purchase.
Conversation list
Sorting into tabs
- A conversation goes to Priority when the other user is featured or a tip is unread.
- Everything else with a last message goes to General.
- Broadcasts and message lists go to Message Lists.
- Conversations with no last message are hidden.
- Lists sort by most recent message first.
Search
A search box filters conversations on the server with a 500 ms debounce.
Row details
- Avatar, name, last message preview, and time.
- Unread badge that caps at
99+. - Green online marker.
- Verified tick and user badges.
- A subscriber mark on featured users who are also subscribers.
- A tip icon when the last message is an unread tip.
- Guaranteed-reply pending marker, so creators can spot tipped messages waiting for an answer.
- The last-message preview shows the word Media for attachments, and text is trimmed to 35 characters with URLs stripped.
- A friendly empty state, No Messages Found, and a loader while the list fetches.
Message list rows
Message Lists rows show the list icon and name. A dropdown offers edit details, edit participants, and delete, with a confirmation dialog on delete. Individual rows in the other tabs get a Block User item in their dropdown.
Unread bookkeeping
Opening a conversation zeroes its unread count and subtracts that amount from the global message counter in the header. Tab counters recompute from the lists.
Thread
Header
- Avatar and name link to the user's profile.
- Online or offline status for one-to-one chats.
- Verified tick and badges.
- For lists, the group icon and a N Recipients link that opens the participants editor.
- A Block User action, hidden on white-label sites. It opens a confirm dialog and refreshes the inbox afterwards.
History and grouping
History loads from previousMessages. Messages are grouped by day with a date label (Today for new ones), and the avatar and time only repeat when the sender changes.
Live delivery
- Incoming messages arrive over the chat socket and append to the open thread, then scroll to the bottom.
- The socket's
memberspayload fills the online list that drives the green dots and the Online or Offline label. - If the message is for another conversation, the header counter goes up by one and the inbox refetches.
- A presence heartbeat calls
setChatEmailevery 2 minutes so others see you online.
Message types rendered
- Text, with URLs turned into links and bare URLs stripped from previews.
- Images with a lightbox that steps through every image in the thread.
- Video with an in-thread player and a close button.
- Video plays through the IVS player.
- Audio with a full player. Locked audio shows a placeholder icon instead of a player, and on white-label sites the placeholder is the branded audio icon.
- Other files show their name and size, with a download button for the receiver.
- Store share cards, portfolio item cards, and event ticket cards that route to the shared page when clicked. Event cards show date, time, venue, timezone, and a VIEW TICKET button.
- Order dispute messages show the product image, name, price, and the quoted dispute reason.
- Tip and guaranteed-reply banners with the amount in the sender's currency. Guaranteed replies also show a refund icon.
Paid and free attachments
Free previews show right away. Paid attachments are locked for the receiver with an Unlock button and the price. The sender always sees their own media. Every media item has a download button, and the mobile app receives downloads through the bridge.
Sending state
Outgoing messages appear immediately with a spinner and circular upload progress, then settle when the server confirms.
Composer
Text input
- Auto-growing textarea that expands up to 4 rows.
Entersends. Empty text with no attachments does nothing.- Emoji picker that closes on outside click.
- For broadcasts, an Insert First Name button drops a
[First Name]token at the cursor.
Attachments
- Images, video, and audio files, up to 5 paid items per message.
- Images are compressed and get a generated thumbnail. Videos get a thumbnail and a unique upload name.
- Videos over 512 MB are rejected with an error toast.
- Restricted-content responses open a carousel that shows the blocked image.
- Each attachment has a remove button. The mobile app opens its own file picker through the bridge.
Voice notes
Record with a live waveform drawn on a canvas in the brand color and a running timer. Stop to add the note as an attachment, or cancel to discard. Recorded notes can be previewed in a player before sending.
Price to view
- A price tag button opens a modal to set a price, with an optional currency conversion step that stores international prices per currency.
- The button only renders for users with the
LOCK_MESSAGEpermission, in one-to-one threads and in lists. - It stays disabled until at least one attachment is present, and it is also disabled while sending or while the tip popup is open.
- The allowed price is $1 to $10,000. Anything outside that range shows an inline error and blocks saving.
- Once a price is set, attachments can be dragged between a paid list and a single free preview slot, and reordered inside the paid list.
- You cannot select more than 5 paid items or more than 1 free item.
- Paid share links cannot become the free preview.
- A paid price needs at least one paid attachment.
- Clearing the price moves the free preview back into the normal attachments.
Amount validation
The price prompt only accepts valid decimals. The tip field shows a range error such as Price must be between min and max based on the user's currency.
Paid DMs, tips, and guaranteed replies
DM validation
Opening or starting a conversation calls ChatUserValidation. The response decides whether the composer shows, whether the creator charges for DMs, how many free DMs remain, whether guaranteed replies apply, and whether the creator is hidden.
Free DM credits
- Creators with a DM price show either You have N Free Messages remaining, Unlimited, or a notice with the price once credits run out.
- Each free send calls
consumeDMCreditsand updates the counter from the response.
Pay per message
When credits are gone, sending routes the fan to the payment page with the message, media, and price stored for after payment. The pending item is removed from the queue and the fan returns to the thread afterward.
Tips
- A tip popup with preset amounts generated as multiples
1, 5, 10, 20, 50of the minimum, converted into the fan's currency. - A custom amount field with min and max validation.
- When the creator offers guaranteed replies, the popup shows a Minimum Tip badge with the required amount in the fan's currency.
- Tips attach to the message. The amount is converted back to USD with the stored exchange rate.
- A combined pay per message and tip flow sends one payment with both amounts.
- Tips are blocked when a price or share content is already set on the message.
Guaranteed replies
- Creators can require a minimum tip for a guaranteed reply.
- Fans who send without a tip get a Guaranteed Reply Offer modal with the minimum amount and a Send without Tip option.
- Choosing to tip opens the tip popup with the minimum preselected.
- Refunds happen automatically within 7 days if the creator does not reply.
- When the creator replies,
redeemGuaranteedReplyruns and a guaranteed reply tip received modal shows the amount. - Delegate users skip the modal and send directly.
Unlocking paid items
Fans tap Unlock on a locked message. The delegate permission check runs first, then the payment page opens for that message id and price. Prices show in the fan's currency when it is not USD.
New message and message lists
New message modal
- Searchable list of people you can message, debounced at 500 ms.
- Pick one person to open a direct message.
- On the Message Lists tab the button becomes + New Message List and the same modal creates a list.
Creating a list
- Step 1 picks recipients, and the picks show as removable chips at the top. Step 2 sets a list name (max 25 characters) and an optional icon image, which the user crops to a round avatar.
- You can pick either individual users (at least 2) or groups, not both. Selecting one type disables the other.
- Groups include subscription plans and predefined segments: Fans and Subscribers, Current Fans, All Current Subscribers, Previously Paid Subscribers, Top 20 Spenders, and Prior Paid DM Users.
- The icon is compressed and uploaded, and restricted images are rejected.
- Starting a list from another screen jumps straight to step 2.
Editing a list
- Edit details changes the name and icon.
- Edit participants shows current participants and non-participants with search, and only sends the diff (add and remove users or groups).
- The save button is disabled if the list would drop below 2 users or 1 group.
- The list on screen updates locally without a full reload.
Sending to a list
Messages to a list go through sendPublic with the list id instead of one recipient, and the thread shows the recipient count. The [First Name] token is inserted client-side and left in the payload for the backend to personalize per recipient.
Deleting a list
Delete asks for confirmation, calls the delete endpoint, then refreshes the list and shows the server message.
Automated messages
What they are
Creators define a message that is sent on its own when a fan does something. Setup lives under the Setup Automated Messages tab.
Event types
- Fan Registers (
FOLLOW_MESSAGE) - Fan Purchases Subscription (
SUBSCRIPTION_PURCHASE_MESSAGE) - Fan Purchases Your Content Post (
CONTENT_PURCHASE_MESSAGE) - Fan's Birthday (
BIRTHDAY_WISH_MESSAGE)
Setup modal
- Dynamic form with a required event type and required message text, capped at 1000 characters, with the same first-name insert as the composer.
- The same attachment, voice note, price, and free preview controls as the composer.
- An event type that already has a message is disabled in the dropdown, so each type has at most one message.
- A price needs a paid attachment.
List, edit, delete
- A table shows event type and message text with per-row edit and delete actions.
- Editing locks the event type and pre-fills text, price, and media.
- Delete asks for confirmation and removes by event type.
- An empty state reads You have not setup any automated messages yet.
White-label and safety
White-label behavior
- The conversation list is hidden unless the site allows DMs (
WLSAllowDMs), or the user is the site owner. - Fans on a white-label site are sent straight to a chat with the site's creator.
- Message payloads include the owner id as
wls_owner_id. - Creators that are not available on the current site show This Creator is not currently available on TopFan.
- Block is hidden and new message is limited to the owner.
- Button text color for the waveform comes from the site's branding.
- The page hides the site footer and keeps the header visible, so the inbox fills the viewport.
Safety and hygiene
- Right-click is disabled on message images.
- Images use responsive WebP variants with lazy loading.
- Subscriptions and timers are cleaned up on destroy.
- Amounts, prices, and user ids come from the server or profile, not from hard-coded values.
Hard limits worth keeping in a revamp
- Paid attachments per message: 5
- Free preview items: 1
- Video size: 512 MB
- Attachment price: $1 to $10,000
- Automated message text: 1000 characters
- Message list name: 25 characters
- Minimum users in a list: 2, or 1 group
- Minimum sender age: 18
- Composer height: up to 4 rows
- Unread badge cap: 99+
- Search debounce: 500 ms
- Presence heartbeat: every 2 minutes
- Guaranteed reply refund window: 7 days
Loose ends in the code
Not features, but worth knowing before a revamp.
Older messages do not load
The thread has infinite-scroll markup and a getUserChatMessage(..., onSrcollItemUp) path, but onUp() has its call commented out. Only the first page of history shows.
Subscription prompt is unreachable
openSubscriptionOptionPopup and goToPaymentForSubscription exist in the container, but nothing in the template calls them. onlySubscribers is also declared and never set.
Report user is unreachable
reportUser opens the report dialog, but no menu item triggers it.
Size of the container
MessageContainerComponent is about 3,950 lines of TypeScript and 2,300 lines of template. Sending, uploads, payments, list management, and sharing all live in it.
Two price modals
The composer uses MessagePriceModelComponent with currency conversion. The automated-message form uses MessagePriceModelV2Component on top of AlertModalComponent with no conversion.