Reference
Private messaging reference
Member behavior, group history, blocking and private report access.
Includes the complete docs/MESSAGES.md reference from this build. Application paths refer to your own installation.
On this page
Private messages#
One-to-one conversations use one unique, sorted member pair. Only those two members can read, search, count, mark read, block, send or report within a conversation. Administration does not bypass participant checks. Guests and banned accounts cannot use the inbox. Sending requires verified, nonbanned sender and recipient.
The desktop inbox has a conversation rail and chat pane. Mobile shows the list first and opens a separate chat view with a back button. New conversation opens a dedicated recipient picker with focused member search and Back/Cancel controls. Its ?new=1 URL works without JavaScript; the default inbox opens it in place. Search matches member names; it returns at most 20 available recipients and paginates conversations at 20 per page. Chat history shows the latest 30 messages first, with older pages. Messages are plain escaped text, at most 5,000 characters. Named group conversations support 3–20 initial members. There are no attachments, calls, presence indicators or typing events.
Opening a visible chat with JavaScript posts the greatest message ID rendered on that page to the authorized read endpoint. The cursor is per participant, monotonic, and must refer to a message in the same conversation. The GET itself never changes read state. A manual mark-read button provides a fallback. Header badges count unread conversations containing a message from another member beyond the reader's cursor. Sending your own message does not clear incoming unread messages. Visible inboxes poll every 10 seconds, with error backoff up to 60 seconds and no hidden-tab requests. Incoming messages update at the bottom of the latest page; scrolling up or viewing an older page preserves reading position. Drafts, focused controls and open report forms remain intact. Manual refresh works without JavaScript. Revoked membership clears the private pane when the next poll returns access denied or not found.
Blocking prevents new sends in both directions, including attempts to start the same pair again. Either participant retains access to history. Each member controls only their own block; unblocking does not remove the other member's block. Creation, sending and blocking lock both member rows in sorted order and sending then locks the conversation; the unique pair database constraint also protects concurrent creation.
A participant can report an incoming message with a 5–2,000 character reason. The report shares only that message by default. An explicit checkbox adds at most the two immediately preceding messages in that conversation. A stored snapshot fixes the disclosed content at submission time. Reviewers require the global message.report.review permission, granted to administrators on fresh installation and upgrade. Forum moderator assignments do not grant it. The dedicated report queue exposes these snapshots and reasons, not a conversation browsing endpoint. Resolutions are 3–2,000 characters, record the resolving member, and cannot overwrite an already resolved report.
All messaging limits are keyed by authenticated member: searches/views 60 per minute, sends 20, reads 30, blocks 20, reports 5, resolutions 20. All mutations require CSRF protection. Report pages are paginated at 20.
History remains stored after blocking or reporting. This version has no member deletion/archive UI or retention scheduler. Deleting either direct-conversation member at the database level cascades that direct conversation, messages, related reports and blocks; deleting a reviewer only clears the report's resolved_by. Backups must be protected as private data. Private means restricted application access; this is not end-to-end encryption and infrastructure/database operators can access stored content.
Group conversations (1.3)#
Use New conversation → Create a group conversation. Search members and select names, enter a group name (100 characters) and a first message. JavaScript retains selected names across searches in the current browser tab; without JavaScript, choose members from one bounded search result. The creator owns the group. The owner can rename, invite or remove members and transfer ownership to another verified, nonbanned group member. Invitations are described under abuse controls below. Transfer ownership before leaving. Members can leave and block/unblock other current members. Blocks prevent the blocked pair from sending to each other through the group; other members can continue sending. Creation and addition reject any blocked pair.
A new or returning member sees only messages sent after joining, including previews, unread counts, read cursors and report context. Leaving or removal immediately revokes all group history and write access. Stored reports retain their disclosed snapshots. There is no history export or membership event log. An owner deleted directly from the database is succeeded by the remaining member with the lowest account ID, claimed atomically during the next group mutation. A deleted member's messages cascade; other group history remains. Groups may shrink below three members after departures. The owner cannot leave a one-member group; keep the group as history or add another member and transfer ownership.
Group sends and membership changes lock the conversation and current membership rows; eligible users and blocks are checked under locks. Deadlocks receive bounded transaction retries. MySQL 8.4 and 9.7 are the supported concurrency targets; SQLite feature tests alone do not prove row-lock behavior.
Abuse controls#
New-account gate. An account must be FORUM_DM_MIN_ACCOUNT_AGE_HOURS old (default 24; 0 turns the gate off) before it can start a conversation with someone it has no conversation with, create a group, or invite a member. Administrators are exempt. Replying inside an existing conversation is never gated, so a new member can always answer someone who wrote to them. The gate counts from the account's creation time, not from email verification.
Message opt-out. Account settings → Private messages lets a member stop accepting new conversations and group invitations. Their name no longer appears in the recipient picker, and attempts to start a conversation, create a group with them or invite them are refused. Conversations that already exist continue, either side can still send, and the member can still block individuals. Administration does not bypass the opt-out.
Group invitations. Creating a group, or an owner adding a member later, sends an invitation instead of adding the person. The invitee sees the group name and who invited them in their inbox, and nothing else: no members, no messages. Accepting adds them to the group. Declining, or the owner cancelling, removes the invitation. Someone invited when a group is created reads the group from its opening message; someone invited later reads only messages sent after they accept. Pending invitations count toward the 20-member limit, so an owner cannot invite more people than the group can hold. Blocks are checked when inviting and again on accepting, so a block made in between still holds. Invitations do not expire; the invitee or the owner can remove one at any time.
Searches. % and _ typed into the inbox, recipient and administration searches match themselves instead of acting as wildcards.