User Guide

Messaging

In-app messaging — announcements + direct threads, per-topic, role-aware contacts.

Built-in messaging avoids scattering team coordination across email + Slack + Teams when the conversation is about a specific timesheet, invoice, or contract. Everyone's on TimeTrack Pro already; replies live next to the work.

Two message types

📢

Announcements

One-way broadcast from management to every active user in the tenant. Read-only — no replies. Use for policy changes, deadlines, holiday closures. Only super_admin + manager can post.
💬

Direct threads

Two-way conversations between any pair of users. Per-topic: each new conversation gets a fresh thread id, so Alice and Bob can have parallel threads — "Payment question", "Vacation request", "Sept invoice" — each with its own inbox row.

Who you can message

Server-side role-scoped contact list (no fishing-expedition directory):

  • ✓Super admin — every active user in the tenant
  • ✓Manager — super admins + other managers + project-scope users
  • ✓Employee — super admins + managers from project memberships
  • ✓Vendor admin — super admins + managers from vendor clients
  • ✓Vendor member — receive-only (can't initiate)
Privacy — DMs are not surveillance
Direct threads are not a surveillance channel. Super admins and managers can't read messages they aren't a sender or recipient of. Every read + mark-as-read call runs through assertCanAccessThread() — the helper checks the thread's first message and requires the caller to be its sender or recipient.

Delivery + tracking

📧

Email backup

Every send has a "Also send email notification" checkbox (default on). Recipients on PTO who don't check TTP still hear about it. Email failures log a warning but never fail the in-app send.
🔔

Unread badge

Header polls /api/messages/unread-count every 30s. Inbox splits Announcements + Direct Messages. URL syncs to ?thread=<id> for sharing direct links.

Dashboard "Reports to" line

Employee + vendor admin dashboards show Reports to: Sarah Connor, Mike Ross at the top. Each name is clickable — opens the compose dialog pre-filled. Skips the manager-lookup step. Same role-scoped contact endpoint serves both the welcome header line and the compose dropdown.

Tip
Announcements + direct threads share the same messages table. The discriminator is type (announcement / direct). Read tracking via message_reads works the same way for both — opening a thread marks every unread message in it as read.