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.