# Mobile support chat: add image sending

## App changes

- Add an **Attach image** button. Allow one image, show a preview and Remove button, and allow an optional caption.
- Allow Send when text or an image is present. Show uploading/failed states and a Retry button.
- Display received images inside chat bubbles; tap to enlarge.
- Disable sending when the conversation is resolved or closed.

## Upload request

Use the existing endpoints with `multipart/form-data`:

- **Send message:** `POST /api/v1/support/conversations/{uuid}/messages`
- **Start chat:** `POST /api/v1/support/conversations` (also accepts `category`, `subject`, `priority`).

Headers: `Authorization: Bearer {token}` and `Accept: application/json`.
Let the HTTP library generate the multipart boundary.

| Field | Value |
| --- | --- |
| `image` | Actual image file; not base64 or a URL |
| `body` | Optional caption; required without an image |
| `client_message_id` | UUID generated once per message |

Text-only requests can keep using JSON. Success returns **201**: `message` for a reply, or `conversation` with `messages` for a new chat.

## Image rules

- JPEG, PNG, or WebP only; convert HEIC before uploading.
- Maximum **2 MB**, width and height each **4096 pixels**; resize/compress on mobile if needed.
- Maximum **20 images per conversation**, shared by borrower and support.
- Captions: maximum **5,000 characters**.

## Response and display

Read these fields from both REST and realtime messages:

```json
{
  "id": 391,
  "body": "Optional caption",
  "attachment_url": "SIGNED_IMAGE_URL",
  "attachment_type": "image/jpeg",
  "attachment_expired": false,
  "client_message_id": "MESSAGE_UUID"
}
```

Show an image when `attachment_type` starts with `image/`. Image-only messages have an empty `body`. Default missing `attachment_expired` to false.

## Retries and expiry

- Keep the file/caption on failure. Retry an existing-chat message with the **same `client_message_id`** to avoid duplicates. Use a new UUID if the content changes.
- New-chat creation is **not idempotent**: after a timeout, check the conversation list before submitting again.
- Keep the existing realtime event `support.conversation.changed`; upsert messages by `id` and match pending uploads by `client_message_id`.
- Image URLs expire after **1 hour**. Use the returned URL unchanged; no auth header is needed for that signed URL. If loading fails, refetch the conversation and retry once with its refreshed URL.
- Images expire after **90 days**. When `attachment_expired` is true, show **Image expired** and keep the caption. Avoid permanent local image copies.
- Show server `422` validation errors, including image limits. Preserve drafts on network errors.

## Before release

Backend migrations must be applied, private storage writable, and the cleanup scheduler running. Verify text-only, image-only, captioned images, failed-upload retries, realtime reception, and expired images. Local backend activation still needs the missing support tables/migration history fixed.
