Skip to main content

Messages & Conversations

The messaging system is one of the richest modules in Closegram. It supports multiple message types, group chats, paid messages, polls, live location, disappearing messages, and more.

Each property has its own checkbox marking whether that specific piece is fully implemented and functional end to end (frontend + backend) — update these yourself as work lands; this page won't overwrite them.

  • 1:1 direct messages
  • Group conversations (create a group, send/receive in it)
  • Shareable group invite links (/join/[token] — preview the group, then join after logging in)
  • Typing indicators
  • Read receipts (seen/delivered indicator) — blue double-check in MessageBubble.tsx when status === 'read', updated live via the messageRead subscription
  • Message reactions
  • Group posting permissions — creators/admins choose who can send (everyone / admins only / specific members); blocked members see a locked composer. Backend enforced in createMessage
  • Shared content in the details panel — Media / Docs / Links tabs, plus Starred and Pinned as their own sections
  • Remove members from a group (admin) — with an automatic prorated refund for members who paid to join a subscription group (conversationRemovalPreview shows the amount before confirming)
  • Add-members hides users whose "who can add me to groups" privacy blocks it (User.canAddToGroup)
  • Chat header name/avatar links to the other person's profile (1:1)
  • Starring/pinning messages
  • Real-time delivery via subscriptions
  • Paid/locked messages, coin-gated
  • Voice/video call integration from chat
  • Coin transfer via message
  • Polls in chat
  • Disappearing messages — enable per conversation (group admins, in the conversation details panel) with a duration (5 min / 1 h / 24 h / 7 days). New messages are stamped with an expiry on send, hidden from the thread once elapsed, and swept (soft-deleted) on the next read. Enabling it also fixed a pre-existing bug where updateConversationSettings never persisted anything (the resolver passed camelCase keys the manager read as snake_case, then wrote to a non-existent settings column). Works in both group chats (admin-managed) and 1:1 DMs, where either participant can toggle it (the settings mutation allows any participant of a direct conversation, admin-only for groups).
  • @mentions in chat — sending a message parses @username mentions from the text, resolves them to users and records MessageMention rows (new message_mention table + model + createMessage wiring), firing a mention notification that surfaces in the existing notifications panel. This also fixed dead code: the read side (MessageMention type, userMentions/messageMentions queries, markMentionAsRead, and the access-service methods) referenced a MessageMention model that never existed, so those paths threw at runtime. A dedicated in-chat mention-history view isn't built yet (the queries are functional at the API level).
  • Real-time chat translation — each participant can turn on a per-conversation translation mode and pick their reading language (Español / English) from the chat header. Incoming messages from the other person are shown in the reader's language, with a "Ver original" toggle on each translated bubble. Translations are computed once and cached in the message_translation table keyed by (message_id, target_language), so a message is only ever sent to the paid translation API once per language for its entire life — cost is bounded by an optional monthly character budget (TRANSLATION_MONTHLY_CHAR_LIMIT, tracked in Redis) and a per-message length cap. On send, the message is pre-translated (pre-warmed) into every language a recipient is currently reading in, so it appears translated instantly; if someone enables translation later, older messages are translated lazily on first read and then cached. Empty / URL-only / emoji-only text and same-language messages are skipped to avoid needless API spend. The engine is provider-agnostic (LibreTranslate, DeepL, Google Translate, or OpenAI) and picked from env vars — with no provider configured the feature is simply hidden and chat shows original text. See message-translation.service.js / message-translation.manager.js and the messageTranslations / setMessageTranslation GraphQL operations.
  • Location sharing in chat — 'Compartir ubicación' in the composer sends the current location (shareLocation); the bubble renders static & live locations (MessageBubble) with a Maps link and a Live badge (live-broadcast send needs locationType on LocationInput + a codegen regen)

See the technical reference for GraphQL schemas, routes, source file links, and per-field wiring status.