> ## Documentation Index
> Fetch the complete documentation index at: https://docs.masivo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Tickets & communication

> How tickets, stages, assignees, and multi-channel threads fit together in Masivo.

## What is a ticket?

A **ticket** is a work item your team tracks from first contact until it is resolved or closed. Think of it as a case file: it has a title, a priority, an owner, a **pipeline**, a current **stage** on the board, and links to related records (reviews, customers, journeys, campaigns, or events).

Tickets live in the **CRM → Tickets** area of the dashboard. You can also create and update them through the Storefront API if you integrate Masivo with your own tools (help desk, inbox connector, custom portal, etc.).

## The three layers you need to understand

Masivo separates **case management** from **conversation storage**. This keeps the ticket board fast while still storing full message history.

| Layer                     | What it is                                                               | Where you see it                  |
| ------------------------- | ------------------------------------------------------------------------ | --------------------------------- |
| **Ticket**                | The case: stage, priority, assignees, resolution fields                  | Ticket board, ticket detail page  |
| **Communication thread**  | One conversation channel on a ticket (email thread, WhatsApp chat, etc.) | Conversation tab, thread list API |
| **Communication message** | A single inbound/outbound/internal message inside a thread               | Timeline, messages API            |

```mermaid theme={null}
flowchart TB
  subgraph ticket [Ticket]
    T[Title, stage, priority, assignees]
  end
  subgraph threads [Communication threads]
    E[Email thread]
    W[WhatsApp thread]
    R[Review thread]
  end
  subgraph messages [Messages per thread]
    M1[Inbound customer email]
    M2[Agent reply]
    M3[Internal note]
  end
  ticket --> E
  ticket --> W
  ticket --> R
  E --> M1
  E --> M2
  E --> M3
```

**Important:** listing threads returns **thread shells** (summary fields only). To read the full conversation, call the **messages** endpoint for each thread.

## Ticket lifecycle (pipelines, stages, and status)

Every ticket belongs to one **pipeline** and one stage inside that pipeline. An account starts with a default **Support Pipeline**. Create another pipeline only when the support process has genuinely different stages; teams that share a process should share a pipeline.

Each stage has a stable internal `code`, a display name, an order, and an internal `stage_type`:

| `stage_type` | Effect                                                                  |
| ------------ | ----------------------------------------------------------------------- |
| `open`       | The ticket remains active. Moving from a closed stage records a reopen. |
| `closed`     | Entering the stage closes the ticket and records `closed_at`.           |

```mermaid theme={null}
stateDiagram-v2
  [*] --> OpenStage: pipeline default stage
  OpenStage --> OpenStage: another open stage
  OpenStage --> ClosedStage: stage_type = closed
  ClosedStage --> OpenStage: reopen
```

**Status** (`open`, `resolved`, `closed`) is derived from the stage. The stage's internal type—not its name—decides whether the ticket is open. You normally change **pipeline/stage**, not status directly.

The default pipeline includes `new`, `in_progress`, `waiting_customer`, `waiting_internal`, `resolved`, and `closed`. Custom pipelines receive their own stable stage codes.

Some stages require extra text before the move is allowed:

| Target stage | Required field in `required_fields` | Purpose                                      |
| ------------ | ----------------------------------- | -------------------------------------------- |
| `resolved`   | `resolution_summary`                | Short explanation of how the issue was fixed |
| `closed`     | `closure_reason`                    | Why the ticket was closed                    |

Configure pipelines under **CRM → Pipelines**. Every active pipeline must keep exactly one open default stage, at least one open stage, and at least one closed stage. A used stage cannot be removed or change its internal state until its tickets are moved.

When an inbound customer message arrives on a closed ticket, Masivo reopens the same ticket in the **first open stage of its current pipeline**. It does not create a replacement ticket.

## Ticket sources

| `source_type` | Meaning                                   |
| ------------- | ----------------------------------------- |
| `manual`      | Created by a user or API call             |
| `review`      | Created from a customer review            |
| `journey`     | Created by a marketing automation journey |
| `system`      | Created automatically by Masivo           |

Review-sourced tickets automatically get a **review communication thread** with the review answers as the first inbound message.

## Assignees

Each ticket can have team members in two roles:

| Role       | Meaning                                   |
| ---------- | ----------------------------------------- |
| `assignee` | Primary owner responsible for the ticket  |
| `watcher`  | Gets visibility but is not the main owner |

Assignee changes emit journey events (`TICKET_ASSIGNEE_ADDED`, `TICKET_ASSIGNEE_REMOVED`) that you can use in [Ticket Journey Triggers](/guides/ticket-journey-triggers).

## Communication threads

A **thread** represents one external conversation identity, for example:

* One email mailbox thread (`channel: email`, `external_thread_id` from your provider)
* One WhatsApp number conversation (`channel: whatsapp`)
* One web chat session (`channel: chat`)
* The review submission tied to a ticket (`channel: review`, created automatically)

### Thread fields that matter day to day

| Field                      | What it tells you                                                          |
| -------------------------- | -------------------------------------------------------------------------- |
| `latest_message_preview`   | Short text shown on the board card                                         |
| `latest_message_at`        | When the last message arrived                                              |
| `latest_message_direction` | `inbound`, `outbound`, `internal`, or `system`                             |
| `unread_count`             | Inbound messages not marked read (increments on inbound without `read_at`) |
| `external_thread_id`       | Your provider's thread id — use for idempotent sync                        |
| `channel_account_id`       | Mailbox, phone number, or inbox identifier                                 |

### Thread uniqueness (avoid duplicates)

If you sync from an external system, always send **`external_thread_id`** (and usually **`channel_account_id`**) when creating threads and messages. Masivo deduplicates on:

* Thread: `(account, channel, channel_account_id, external_thread_id)`
* Message: `(account, channel, external_message_id)`

Sending the same external ids again returns the existing record instead of creating duplicates.

## Communication messages

Each message has:

| Concept             | Values                                                              | Plain English                                                              |
| ------------------- | ------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| `direction`         | `inbound`, `outbound`, `internal`, `system`                         | Who the message is "from" relative to your team                            |
| `message_type`      | `message`, `reply`, `internal_note`, `system_event`, `review_event` | Shape of the message                                                       |
| `sender_type`       | `customer`, `agent`, `email`, `system`, …                           | Who sent it                                                                |
| `parent_message_id` | UUID or null                                                        | If set, this message is a **reply** to that message **in the same thread** |

**Rules to remember:**

* Replies must reference a `parent_message_id` that exists **in the same thread**. Cross-thread replies are rejected.
* At least one of `subject`, `body_text`, or `body_html` is required when creating a message.
* **Internal notes** (`direction: internal`, `message_type: internal_note`) are visible to agents only. They do **not** emit customer reply journey events.

## Ticket `communication_summary`

When you fetch a single ticket (`GET /tickets/{id}`), the response includes **`communication_summary`**: a rollup across all threads on that ticket (thread count, latest preview, total unread, dominant channel). Use this for list views and board cards without loading every message.

## Events emitted (for journeys and webhooks)

Ticket and communication actions publish internal behavior events. Common ones:

| Event                                                   | When it fires                         |
| ------------------------------------------------------- | ------------------------------------- |
| `TICKET_CREATED`                                        | New ticket                            |
| `TICKET_STAGE_CHANGED`                                  | Stage move                            |
| `TICKET_ASSIGNEE_ADDED` / `TICKET_ASSIGNEE_REMOVED`     | Assignee changes                      |
| `TICKET_RESOLVED` / `TICKET_CLOSED` / `TICKET_REOPENED` | Stage transitions with those outcomes |
| `COMMUNICATION_THREAD_CREATED`                          | New thread                            |
| `TICKET_THREAD_ATTACHED`                                | Thread linked to a ticket             |
| `COMMUNICATION_MESSAGE_CREATED`                         | Any new message                       |
| `TICKET_MESSAGE_RECEIVED`                               | Inbound message on a ticket thread    |
| `TICKET_CUSTOMER_REPLIED`                               | Inbound message on a ticket thread    |
| `TICKET_AGENT_REPLIED`                                  | Outbound message on a ticket thread   |

See [Ticket Journey Triggers](/guides/ticket-journey-triggers) to automate follow-ups when these events occur.

## Dashboard vs API

| Task                                        | Dashboard                                         | API                                                             |
| ------------------------------------------- | ------------------------------------------------- | --------------------------------------------------------------- |
| Discover pipelines and internal stage codes | CRM → Pipelines                                   | `GET /ticket-pipelines`                                         |
| Move ticket on board                        | Drag card or change pipeline/stage in detail view | `PATCH /tickets/{id}` with `to_pipeline_id` and `to_stage_code` |
| Read conversation                           | Conversation tab on ticket detail                 | `GET /communication/threads?ticket_id=…` then `GET …/messages`  |
| Sync inbound email from provider            | —                                                 | `POST /communication/threads` then `POST …/messages`            |
| Assign teammate                             | Ticket detail sidebar                             | `PATCH /tickets/{id}` or assignee endpoints                     |

For step-by-step API recipes and copy-paste examples, continue with the [Ticket management guide](/guides/ticket-management).
