# Feature: Messaging

## Purpose

Team-scoped in-app conversations (staff ↔ staff, staff ↔ portal customers) plus queued Email-to-SMS for **service** triggers with configurable templates. Chat messages do not send SMS.

## Boundaries

- **Owns:** `conversations`, `conversation_participants`, `messages`, `sms_templates`, `sms_dispatches`; Messaging Actions, Policies, queued jobs, Inertia pages under `Pages/Messaging/`
- **Does not own:** CRM phones (reads `customers.phone` via soft DB), Jobs appointment lifecycle (soft-consumed by Jobs via `AppointmentSmsHooks`)
- **Depends on (platform only):** User / Team / auth / Mail / Queue
- **Depends on (other domains):** soft — CompanySettings `sms_gateway_email` (via EnsureCompanySettings or DB); soft customer phone/name
- **Consumed by (soft):** Jobs hooks after create/update/cancel appointment when Messaging is registered
- **Platform UI:** Messages nav in `AppLayout.vue`; SMS templates linked from Company Settings

## Models

| Model | Table | Notes |
|-------|-------|-------|
| `Conversation` | `conversations` | `team_id`, `created_by_user_id`, optional `subject` |
| `ConversationParticipant` | `conversation_participants` | `user_id`, `last_read_at` |
| `Message` | `messages` | `sender_user_id`, `body` |
| `SmsTemplate` | `sms_templates` | `trigger_key`, `body`, `is_enabled`, optional `reminder_hours` |
| `SmsDispatch` | `sms_dispatches` | audit: `queued` / `sent` / `failed` / `skipped` |

Migration: `database/migrations/2026_08_12_160000_create_messaging_tables_and_sms_settings.php` (also adds `company_settings.sms_gateway_email`).

### Permissions

- Owners / Jetstream `admin`: view all team conversations; can reply (auto-joined on send)
- Others: only conversations they participate in
- Portal `customer`: reply only (cannot start); staff/admin/editor/owner start threads
- SMS templates: same restriction as company settings (not staff/customer)

### SMS triggers

| Key | When |
|-----|------|
| `appointment.scheduled` | Appointment created as scheduled |
| `appointment.en_route` | Status → `en_route` |
| `appointment.completed` | Status → `completed` |
| `appointment.cancelled` | Cancelled |
| `appointment.reminder` | Delayed queue job at `starts_at - reminder_hours` (default 24) |

Email-to-SMS: **To** = `sms_gateway_email`, **Subject** = recipient mobile, **Body** = rendered template. Sent via queued `SendServiceSms`.

Placeholders: `{{customer_name}}`, `{{job_title}}`, `{{appointment_starts_at}}`, `{{team_name}}`, `{{staff_name}}`.

## Routes

### Web

| Method | URI | Name |
|--------|-----|------|
| GET | `/messaging` | `messaging.index` |
| POST | `/messaging` | `messaging.store` |
| GET | `/messaging/{conversation}` | `messaging.show` |
| POST | `/messaging/{conversation}/messages` | `messaging.messages.store` |
| GET | `/messaging/sms-templates` | `messaging.sms-templates.index` |
| PUT | `/messaging/sms-templates/{smsTemplate}` | `messaging.sms-templates.update` |

### API

| Method | URI | Name |
|--------|-----|------|
| GET | `/api/messaging` | `api.messaging.index` |
| POST | `/api/messaging` | `api.messaging.store` |
| GET | `/api/messaging/{conversation}` | `api.messaging.show` |
| POST | `/api/messaging/{conversation}/messages` | `api.messaging.messages.store` |

## Tests

- Path: `laravel/tests/Feature/Domains/Messaging/`
- Cover: ACL, send message, SMS queue when gateway+phone+enabled, skip when disabled/missing, reminder scheduled

## Add / remove checklist

### Add

- [x] Domain + provider registered
- [x] Wiki linked from README
- [x] Feature tests

### Remove

- [ ] Unregister provider
- [ ] Delete domain, wiki, tests, nav link, Jobs soft hooks `use`/`class_exists` blocks
- [ ] Drop messaging migration columns/tables (careful with `sms_gateway_email`)
- [ ] Suite still green
