Messaging System
Internal messaging for your veterinary clinic — send direct messages or broadcast to your whole team, attach files, track read receipts, and manage your inbox all from within the clinic portal.
Overview
The Messaging System provides a private, internal communication channel for all staff within a veterinary clinic. Messages are scoped to a clinic's director group — staff can only communicate with others under the same director and cannot see messages from other clinics using the same platform.
Every logged-in user (regardless of role) can send and receive messages. Broadcasts let you send one message to your entire team at once. Attachments support images, PDFs, Word, Excel, ZIP, and more. Sent messages include read receipts so you can see whether each recipient has opened the message.
Clinic-scoped
Messages are isolated per clinic. Staff only ever see colleagues under the same director — no cross-clinic visibility.
Broadcast
One checkbox sends a message to every staff member in your group at once, without selecting individuals.
File attachments
Attach any file type — images, PDF, Word, Excel, PowerPoint, ZIP, video, audio, CSV and more.
Read receipts
Opening a sent message's detail view shows each recipient with a ✓✓ Read or ✓ Sent status.
WhatsApp-style delete
Delete a message for yourself only, or delete it for everyone — shown as "⊘ This message was deleted" to all parties.
Inbox widget
A compact unread-messages widget that can be placed on any page — shows only unread messages with one-click read/delete.
Who Can Use It
All logged-in users can send and receive messages. There are no role restrictions on access to the messaging pages. However, the recipient list excludes clinic_pet_owner accounts — pet owners do not appear as selectable message recipients.
Clinic Director
Can message any individual staff member or broadcast to the entire clinic group. Sees messages sent and received between all interactions with the system.
Veterinarian
Can message any colleague in the same clinic group — Director, other Vets, Para Vets, Receptionists. Can send broadcasts.
Para Vet
Full messaging access — can compose, receive, and reply. Para Vets appear as selectable recipients for others in the group.
Receptionist
Full messaging access — can compose, receive, and reply. Receptionists appear as selectable recipients for others in the group.
Pet owners are excluded from the recipient picker. Even if a pet owner account belongs to the same director group, they will not appear in the "To" user list and cannot be messaged through this system.
The Two Pages
The Messaging System provides two shortcodes that can be placed on any page.
| Shortcode | Name | Purpose |
|---|---|---|
| Message Center | Full messaging hub — Sent and Received tabs, Compose button, search, filters, message cards, detail popups, and delete. This is the primary page for all messaging activity. |
| Inbox Widget | A compact widget that shows only unread messages. Designed to be placed on a dashboard or home page as a quick-glance notification panel. |
The Inbox widget and the Message Center are fully independent — each can be placed on a separate page. The unread badge count shown in the Inbox widget updates live when messages are marked as read.
Clinic Scoping
Every message is stamped with a director_id at send time. This ID is the sender's own user ID if they are a Director, or the clinic_director_id stored in their user meta if they are a staff member.
When fetching received messages, the system returns messages where:
- The
recipient_idexactly matches the current user, OR - The
recipient_idis0(broadcast) AND the message'sdirector_idmatches the user's own director group
This means a broadcast from one clinic will never appear in another clinic's received list, even if both clinics are on the same installation. And a direct message sent to one user will only ever appear in that user's received list.
If a staff member has no clinic_director_id in their user meta, they will not be correctly scoped to a clinic group and may not be able to send or receive messages properly. Ensure all staff are assigned a director via the clinic setup process.
Message Center
The Message Center () is the main messaging interface. It displays your name and avatar in a welcome bar at the top, with a Compose button on the right. Below that, two tabs — Sent and Received — let you switch between your outgoing and incoming messages.
Sent & Received Tabs
| Tab | Shows | Excludes |
|---|---|---|
| ✈️ Sent | All messages you have sent (direct or broadcast), excluding ones you have deleted for yourself. | Messages deleted by sender (deleted_by_sender = 1) |
| 🏠 Received | All messages sent to you directly, plus any broadcasts from your clinic group — excluding ones you have deleted. | Messages deleted by recipient (deleted_by_recipient = 1), and messages you sent yourself |
Both tabs load automatically when the page opens. Switching tabs reloads the list for that tab. All active search terms and filter settings apply to whichever tab is currently selected.
Message Cards
Each message in the list is shown as a card with the following information:
Sender avatar & initials
The sender's Gravatar or profile image is shown on the left. If no image is available, a circle with the sender's first initial is used instead.
Subject line
The message subject is shown prominently. If a message has been deleted for everyone, the subject is replaced with "⊘ This message was deleted" in a muted style.
Time, From/To label, and action buttons
The timestamp is shown in your local timezone. In the Sent tab, it shows "To: [recipient name]" or "To: Everyone (Broadcast)". In the Received tab, it shows "From: [sender name]". Unread messages show a ✓ Read button; sent messages show a 🗑 delete button.
Message preview & tags
The first 90 characters of the message body are shown as a preview. Tags appear on the right side of the bottom row (see Card Tags below).
Clicking anywhere on a card (except on a button) opens the full message in a Detail Modal. Unread cards are visually highlighted.
Card Tags & Badges
Small coloured tags appear on message cards to convey status at a glance:
Tags are not shown on deleted messages (⊘ deleted cards).
Search & Filters
The search bar and filter button sit at the top right of the Message Center, alongside the tabs.
Search — type to find messages by subject, body text, or sender name. Results update automatically after a 350 ms debounce (or immediately on pressing Enter). Search applies to the currently active tab.
Filter — click the funnel icon to open the Filter modal. A blue dot on the button indicates active filters. The filter modal contains:
| Filter | Options | Applies to |
|---|---|---|
| Status | All Messages, Unread Only, Read Only | Received tab only |
| Sort Order | Newest First (default), Oldest First | Both tabs |
| From Date | Date picker — filters messages on or after this date | Both tabs |
| To Date | Date picker — filters messages on or before this date | Both tabs |
Click Apply Filters to reload the current tab with the new settings. Click Reset All to clear all filters and reload. Pressing Esc closes the filter popup without applying changes.
The Status filter (Unread/Read Only) only has an effect on the Received tab. Applying it while on the Sent tab has no visible impact because sent messages do not have a read/unread state from the sender's perspective.
Composing a Message
Click the Compose button in the top-right of the Message Center to open the compose modal. Fill in the form and click Send Message →.
Selecting Recipients
The "To" section shows a search box and a scrollable list of all users in your clinic group (excluding pet owners and yourself). Each user is shown with their name and email. Click a user's row to toggle their checkbox — selected users are highlighted. You can select multiple recipients to send the same message to several people at once; a separate message row is created in the database for each recipient.
Type in the recipient search box to filter the user list by name or email. The filter is instant and case-insensitive.
Broadcast to Everyone
Check the "Send to everyone in my group" checkbox at the top of the "To" section. The individual user picker hides automatically. When sent, one message row is inserted with recipient_id = 0. This single row is visible to every member of the clinic group in their Received tab.
A broadcast is stored as a single database row, not one row per user. This means the read receipt for a broadcast shows the overall is_read status of the row — not individual per-person read tracking. It shows all group members but their "read" status reflects the most recent update to that single row.
Message Fields
| Field | Required? | Notes |
|---|---|---|
| To (recipient selection) | Required | At least one checkbox ticked, OR "Send to everyone" checked |
| Subject | Required | Plain text, up to 255 characters |
| Message | Required | Free text, any length |
| Attach File | Optional | Any file type — see Attachments section |
The Send Message → button shows a spinner while the message is being sent. A green toast notification confirms success and the compose modal closes automatically. The Sent tab reloads immediately to include the new message.
File Attachments
The attachment area in the compose form accepts any file type. You can attach a file in two ways:
- Click the attachment area to open a file browser
- Drag and drop a file directly onto the attachment area
The file is uploaded to the server immediately after selection (before you send the message). A spinner shows while uploading. On success, a preview appears in the attachment area showing either a thumbnail (for images) or a file type icon with the filename and MIME type. Click ✕ Remove to detach the file before sending.
Supported file types include (but are not limited to):
Only one file can be attached per message. If you need to send multiple files, consider zipping them first or sending multiple messages.
Message Detail View
Clicking a message card opens the Detail Modal — a full-screen overlay showing the complete message. The modal displays:
- The sender's avatar and name
- "To: [recipient]" and the sent timestamp (in your local timezone)
- A divider, then the full message body
- If a file was attached — an inline image preview for images, or a download link panel for other file types
- For sent messages — a Read Receipts section
Close the modal by clicking ✕, clicking outside the modal panel, or pressing Esc.
Marking as Read
Unread messages (in the Received tab) show a ✓ Mark as Read button both on the card and in the detail modal footer. Clicking it sends a read request to the server and immediately:
- Removes the "New" tag and the unread highlight from the card
- Removes the Mark as Read button from the card and the modal
- Replaces it with a small "Read ✓" timestamp in the modal footer
- Refreshes the Inbox widget count (if the Inbox shortcode is on the same page)
Read status is tracked per message in the database (is_read = 1). Once marked as read, a message cannot be manually marked unread.
Read Receipts
When you open a message in the Sent tab, the detail modal includes a "Read Receipts" section below the message body. This section lists each recipient with their avatar, name, email, and a status indicator:
For a direct message, the receipts section shows the one recipient. For a broadcast, it lists all members of your clinic group (excluding yourself) with the shared read status of the broadcast row.
Broadcast read receipts show all group members, but because broadcasts are stored as a single row, the read status shown is the same for all listed members — it reflects when any member last marked it read, not each individual's read time.
Deleting Messages
Messages are deleted in a WhatsApp-style soft-delete system — the database row is never physically removed. Instead, flags are set to hide the message from the relevant party's view. Click the 🗑 button on a sent message card (or inside the detail modal for sent messages) to open the Delete confirmation modal.
Delete Options
| Option | What happens | Visible to sender? | Visible to recipient? |
|---|---|---|---|
| 🗑️ Delete for Me | Sets deleted_by_sender = 1 (if you are the sender). The card fades out and disappears from your Sent list. The recipient is unaffected. | ❌ Hidden immediately | ✅ Still visible |
| ⛔ Delete for Everyone | Sets both deleted_by_sender = 1 and deleted_by_recipient = 1. The card stays visible but shows "⊘ This message was deleted" with the subject, preview, and tags removed. | ⊘ Shown as deleted | ⊘ Shown as deleted |
Only the sender sees the 🗑 delete button. Recipients do not have a delete button in the current version — they can only delete a message from their own view through the "Delete for Me" flow if a delete button were exposed. Currently, deletion is only exposed to the message sender.
Deleted messages (for everyone) remain in the database for auditing. The "⊘ This message was deleted" state is a display-level flag — the underlying data is preserved.
Inbox Widget
The Inbox () is a lightweight, standalone widget designed to be embedded on a dashboard page. It shows only unread messages from your clinic group.
The widget displays:
- A header with an envelope icon, "Inbox" title, and an unread count badge (hidden when zero)
- A sub-heading: "Unread messages awaiting your attention"
- The list of unread message cards, built identically to the Message Center cards
Each card in the inbox includes a ✓ Read button and a 🗑 delete button. Clicking Read marks the message and removes it from the inbox card list, updating the badge count. Clicking a card (not a button) opens the full Detail Modal directly in the inbox view.
The inbox is loaded at page load and does not auto-refresh. After marking messages as read, the badge updates live in the same page session. A full page refresh will re-query from the database.
The Inbox widget and the Message Center can be placed on the same page or separate pages. If both shortcodes are present together, the inbox count badge updates live when messages are marked read via either interface.
FAQ
No. The system is fully clinic-scoped. You can only message users who share the same director group as you. Users from other clinics will never appear in your recipient list and will never receive your messages.
No. The recipient picker explicitly excludes accounts with the clinic_pet_owner role. Pet owners will not appear as selectable recipients regardless of whether they belong to the same director group.
A separate message row is inserted into the database for each recipient. Each recipient sees their own individual copy — they cannot see who else received the same message. Each copy has its own independent read/delete state.
A broadcast (checked "Send to everyone") inserts a single database row with recipient_id = 0. All group members see this one row. A multi-recipient message inserts one row per selected recipient, and each recipient sees their own private copy. Broadcasts are more efficient for true all-staff announcements; individual rows give each recipient private control over their copy.
No. Only one file can be attached per message. If you need to share multiple files, zip them into one archive first, then attach the zip file. Alternatively, send multiple messages each with a different attachment.
Currently the delete button is only shown to the message sender. The soft-delete system does technically support recipient-side deletion (deleted_by_recipient flag), but the UI only exposes this action to senders in the current version.
No. It sets both deleted_by_sender and deleted_by_recipient to 1 in the database row. The record is preserved for auditing. The visual presentation changes to "⊘ This message was deleted" for all parties, and the subject, preview, and action buttons are hidden.
Messages are stored with UTC timestamps. When displayed in the browser, they are automatically converted to the user's local timezone using the browser's Date.toLocaleString() function. This means two users in different timezones will see the same message with a different local time, both of which are correct for their region.
No. The inbox loads once when the page loads. Within the same page session, marking messages as read removes them from the inbox list and updates the badge count live. But new messages arriving from other users will only appear after a manual page refresh. There is no polling or websocket live-update in the current version.
Yes, all group members receive the broadcast because it is scoped by director group. The receipt list shows all members as a convenience, but the "Read/Sent" status shown is the same for everyone (it reflects the single row's is_read flag). True per-user read tracking for broadcasts would require one row per recipient, which is how individual multi-recipient messages work.
No file types are blocked. The attachment handler uses a broad MIME allow-list that covers all common types, and a fallback that assigns application/octet-stream for any type not explicitly listed. Any file can be uploaded and attached to a message.
No. Once a message is marked as read (is_read = 1), there is no way to revert it to unread status in the current version. The system only supports one-way read tracking.
If no other users are found in your director group (or you are the only staff member), the compose modal will show "No users in your group." in the recipient list. You can still send a broadcast, but there will be no individual recipients to select. Ensure all staff are properly linked to the director via their user meta clinic_director_id.