# Staff New Ticket (WhatsApp outbound) — Design

**Date:** 2026-08-05  
**Status:** Approved  
**Goal:** Let staff create a new ticket and send the first WhatsApp message from the console.

## Decisions

| Topic | Choice |
|-------|--------|
| Recipient | Search existing contact **or** type new phone (`findOrCreate`) |
| Channels | Connected **WhatsApp** only |
| Open ticket exists | Always create a **new** ticket |
| First message | Required text; send via Baileys like ticket reply |
| Media on create | Out of scope v1 |
| Email / portal from this modal | Out of scope v1 |

## API

`POST /api/v1/tickets` (staff: `admin` | `user`)

Body:

```json
{
  "channel_id": "<uuid>",
  "contact_id": "<uuid>",
  "phone_number": "+27…",
  "content": "Hello…"
}
```

Rules:

- Exactly one of `contact_id` or `phone_number` required (non-empty).
- Channel must exist, `kind === 'whatsapp'`, `status === 'connected'`.
- If `phone_number`: reject portal/email synthetic keys; normalize via `contactService.findOrCreate`.
- If `contact_id`: contact must be a WhatsApp phone key (not `portal:` / `email:`).
- Create ticket with `assigned_to =` current user; intake `done` (staff-initiated, skip onboarding).
- Append outbound message; attempt WA send; on send failure still return 201 with ticket + message and emit `message:sent` error (same pattern as reply).
- Emit `ticket:created` and `message:new`.

Response `201`: `{ ticket, message }` (ticket includes joined fields like `getById`).

## UI

- Enable topbar **New ticket** (Layout) for staff.
- Same modal available from Tickets page for staff.
- Fields: WhatsApp channel select, contact search + optional phone input, message textarea, Submit.
- On success → navigate to `/tickets/:id`.

## Non-goals

- Reuse open ticket / choose dialog
- Attachments on first message
- Starting email or portal tickets from this control
