## Create a new chat

`client.Chats.New(ctx, body) (*ChatNewResponse, error)`

**post** `/v3/chats`

Create a new chat with specified participants and send an initial message.
The initial message is required when creating a chat.

## Message Effects

You can add iMessage effects to make your messages more expressive. Effects are
optional and can be either screen effects (full-screen animations) or bubble effects
(message bubble animations).

**Screen Effects:** `confetti`, `fireworks`, `lasers`, `sparkles`, `celebration`,
`hearts`, `love`, `balloons`, `happy_birthday`, `echo`, `spotlight`

**Bubble Effects:** `slam`, `loud`, `gentle`, `invisible`

Only one effect type can be applied per message.

## Inline Text Decorations (iMessage only)

Use the `text_decorations` array on a text part to apply styling and animations to character ranges.

Each decoration specifies a `range: [start, end)` and exactly one of `style` or `animation`.

**Styles:** `bold`, `italic`, `strikethrough`, `underline`
**Animations:** `big`, `small`, `shake`, `nod`, `explode`, `ripple`, `bloom`, `jitter`

```json
{
  "type": "text",
  "value": "Hello world",
  "text_decorations": [
    { "range": [0, 5], "style": "bold" },
    { "range": [6, 11], "animation": "shake" }
  ]
}
```

**Note:** Style ranges (bold, italic, etc.) may overlap, but animation ranges must not overlap with other animations or styles. Decorations render per recipient, not per message:
in a group with both iMessage and SMS/RCS participants, iMessage recipients see the decorations and SMS/RCS recipients receive the same message as plain text.

## First-Message Link Restriction

To protect sender deliverability, the **first outbound message** of a new chat cannot be a link.
The request is rejected with `400` (error code `1005`) when:

- The message contains a `link` part (explicit rich-preview link), or
- Any `text` part contains a URL.

This rule applies only to `POST /v3/chats`. Follow-up messages on an existing chat
(`POST /v3/chats/{chatId}/messages`) are not subject to this restriction.

## Reusing an Existing Chat

Chats are keyed on the `from` line plus the exact set of `to` handles. Repeating this
request with the same `from` and `to` returns the **existing** chat and sends the message
into it instead of starting a second conversation.

A group chat that has a `display_name` is excluded from that matching. To run several
parallel groups over the same participants, name each one with `PUT /v3/chats/{chatId}`
before creating the next: the following `POST /v3/chats` with the same `to` then returns a
new, separate `chat_id`. Two other cases also produce a new chat instead of reusing one —
the participant set changed (a participant was added or removed), or the `from` line left
the group.

Whenever the response is a new chat, the first-message rules above apply to that request:
no link in the first message, and no `reply_to` or message effect. To send into a chat you
already know, use `POST /v3/chats/{chatId}/messages` with its `chat_id`.

### Parameters

- `body ChatNewParams`

  - `From param.Field[string]`

    Sender phone number in E.164 format. Must be a phone number that the
    authenticated partner has permission to send from.

  - `Message param.Field[MessageContent]`

    Message content container. Groups all message-related fields together,
    separating the "what" (message content) from the "where" (routing fields like from/to).

    A message carries EITHER `parts` — text and attachments, which compose
    into one bubble — or a single `experience` invocation, which renders an
    experience inside Linq's iMessage app. Never both: an app card is the whole message
    (Apple's `MSMessage` cannot coexist with text), so copy and a card are
    two sends, not one.

  - `To param.Field[[]string]`

    Array of recipient handles (phone numbers in E.164 format or email addresses).
    For individual chats, provide one recipient. For group chats, provide multiple.

  - `OverrideOptout param.Field[bool]`

    Send even though the recipient asked you to stop (`403`, error code
    `2024`). Applies to this request only: the opt-out stays in place, so
    the next send without this flag is rejected again. Every override is
    recorded against your API key.

### Returns

- `type ChatNewResponse struct{…}`

  Response for creating a new chat with an initial message

  - `Chat ChatNewResponseChat`

    - `ID string`

      Unique identifier for the created chat (UUID)

    - `DisplayName string`

      Display name for the chat. Defaults to a comma-separated list of recipient handles. Can be updated for group chats.

    - `Handles []ChatHandle`

      List of participants in the chat. Always contains at least two handles (your phone number and the other participant).

      - `ID string`

        Unique identifier for this handle

      - `Handle string`

        Phone number (E.164) or email address of the participant

      - `JoinedAt Time`

        When this participant joined the chat

      - `Service ServiceType`

        Messaging service type

        - `const ServiceTypeIMessage ServiceType = "iMessage"`

        - `const ServiceTypeSMS ServiceType = "SMS"`

        - `const ServiceTypeRCS ServiceType = "RCS"`

      - `IsMe bool`

        Whether this handle belongs to the sender (your phone number)

      - `LeftAt Time`

        When they left (if applicable)

      - `Status ChatHandleStatus`

        Participant status

        - `const ChatHandleStatusActive ChatHandleStatus = "active"`

        - `const ChatHandleStatusLeft ChatHandleStatus = "left"`

        - `const ChatHandleStatusRemoved ChatHandleStatus = "removed"`

    - `HealthStatus ChatNewResponseChatHealthStatus`

      **[BETA]** Current health for a chat. Always present — chats start at `HEALTHY` and may shift based on engagement and delivery signals on the conversation. Many `AT_RISK` or `CRITICAL` chats on a single line increase the risk of line flagging.

      Switch on `status` to surface chat and line health in your UI — the enum is the long-term contract. Each status carries a `doc_url` that deep-links to the relevant section of the Chat Health guide. To gate a send, act on the response rather than the status: a `403` is the authoritative answer.

      See the [Chat Health guide](/channel/imessage/guides/chats/chat-health) for what each status means and how to react.

      - `DocURL string`

        Deep-link to the relevant section of the Chat Health guide for this status.

      - `Status string`

        Current health bucket for the chat. See the [Chat Health guide](/channel/imessage/guides/chats/chat-health) for what each value means and how to react. `doc_url` deep-links to the relevant section.

        `OPTED_OUT` — the recipient sent `STOP`, `UNSUBSCRIBE`, `OPTOUT`, `CANCEL`, `END`, or `QUIT`.
        The keyword must be the whole trimmed message, never part of a longer one: `STOP` counts, `please stop`
        does not. Most keywords must match exactly, including case. `OPT OUT` is the exception — it matches in any
        casing, with or without the space or a hyphen, so `opt out`, `Opt-Out` and `optout` all count. It clears as
        soon as they reply again: any later message from them that is not itself an opt-out keyword opts them back
        in immediately — a reply in any conversation with you counts, the same way the block does.

        `OPTED_OUT` marks only the conversation the keyword arrived in. The block below is wider than the mark, so
        a conversation still reading `HEALTHY` can be blocked as well — gate on the `403`, not on the status.
        Group threads are never marked and are never blocked.

        Linq enforces this: while a recipient is opted out, every send to them is rejected with `403` (error code
        `2024`) before the message is queued, across every chat and every line on your account. Nothing is
        delivered, including a final courtesy message — to send one, set `override_optout: true` on that single
        request.

        - `const ChatNewResponseChatHealthStatusStatusHealthy ChatNewResponseChatHealthStatusStatus = "HEALTHY"`

        - `const ChatNewResponseChatHealthStatusStatusAtRisk ChatNewResponseChatHealthStatusStatus = "AT_RISK"`

        - `const ChatNewResponseChatHealthStatusStatusCritical ChatNewResponseChatHealthStatusStatus = "CRITICAL"`

        - `const ChatNewResponseChatHealthStatusStatusOptedOut ChatNewResponseChatHealthStatusStatus = "OPTED_OUT"`

      - `UpdatedAt Time`

        When this status last changed.

    - `IsGroup bool`

      Whether this is a group chat

    - `Message SentMessage`

      A message that was sent (used in CreateChat and SendMessage responses)

      - `ID string`

        Message identifier (UUID)

      - `CreatedAt Time`

        When the message was created

      - `DeliveryStatus SentMessageDeliveryStatus`

        Current delivery status of a message

        - `const SentMessageDeliveryStatusPending SentMessageDeliveryStatus = "pending"`

        - `const SentMessageDeliveryStatusQueued SentMessageDeliveryStatus = "queued"`

        - `const SentMessageDeliveryStatusSent SentMessageDeliveryStatus = "sent"`

        - `const SentMessageDeliveryStatusDelivered SentMessageDeliveryStatus = "delivered"`

        - `const SentMessageDeliveryStatusReceived SentMessageDeliveryStatus = "received"`

        - `const SentMessageDeliveryStatusRead SentMessageDeliveryStatus = "read"`

        - `const SentMessageDeliveryStatusFailed SentMessageDeliveryStatus = "failed"`

      - `IsRead bool`

        DEPRECATED: Use `delivery_status == "read"` instead. Whether the message has been read.

      - `Parts []SentMessagePartUnion`

        Message parts in order (text, media, and link)

        - `type TextPartResponse struct{…}`

          A text message part

          - `Reactions []Reaction`

            Reactions on this message part

            - `Handle ChatHandle`

              - `ID string`

                Unique identifier for this handle

              - `Handle string`

                Phone number (E.164) or email address of the participant

              - `JoinedAt Time`

                When this participant joined the chat

              - `Service ServiceType`

                Messaging service type

              - `IsMe bool`

                Whether this handle belongs to the sender (your phone number)

              - `LeftAt Time`

                When they left (if applicable)

              - `Status ChatHandleStatus`

                Participant status

            - `IsMe bool`

              Whether this reaction is from the current user

            - `Type ReactionType`

              Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
              Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
              Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

              - `const ReactionTypeLove ReactionType = "love"`

              - `const ReactionTypeLike ReactionType = "like"`

              - `const ReactionTypeDislike ReactionType = "dislike"`

              - `const ReactionTypeLaugh ReactionType = "laugh"`

              - `const ReactionTypeEmphasize ReactionType = "emphasize"`

              - `const ReactionTypeQuestion ReactionType = "question"`

              - `const ReactionTypeCustom ReactionType = "custom"`

              - `const ReactionTypeSticker ReactionType = "sticker"`

            - `ID string`

              Identifier for this reaction. Pass it to
              `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

              Stickers placed before this API shipped can be read but not moved: the
              device-side reference needed to reposition them was never recorded, so
              `PATCH` returns 404 for those.

            - `CustomEmoji string`

              Custom emoji if type is "custom", null otherwise

            - `Sticker ReactionSticker`

              Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

              - `FileName string`

                Filename of the sticker

              - `Height int64`

                Sticker image height in pixels

              - `MimeType string`

                MIME type of the sticker image

              - `URL string`

                Presigned URL for downloading the sticker image (expires in 1 hour).

              - `Width int64`

                Sticker image width in pixels

          - `Type TextPartResponseType`

            Indicates this is a text message part

            - `const TextPartResponseTypeText TextPartResponseType = "text"`

          - `Value string`

            The text content

          - `Mention string`

            DEPRECATED: Use `mentions` instead. Handle (E.164 phone number or Apple ID email)
            of the **first** mention on this part. A part may carry several mentions; this
            field shows only the first in `value` order, so it cannot be used to determine
            whether a given participant was mentioned. `null` when the part carries no mention.

          - `MentionRange []int64`

            DEPRECATED: Use `mentions[].range` instead. Character range `[start, end)` in
            `value` highlighted as the **first** mention only. `null` when the range was
            omitted (the whole `value` is highlighted) or the part carries no mention.
            *Characters are measured as UTF-16 code units. Most characters count as 1; some emoji count as 2.*

          - `Mentions []TextPartResponseMention`

            Every mention on this part, in the order they appear in `value`. `null` when the
            part carries no mention. A part can carry several mentions of different people —
            check `is_me` to tell whether this line was one of them.

            Only iMessage carries mentions. On a received message this is populated when the
            sender was on iMessage; SMS and RCS have no way to mark a mention, so a message
            from an SMS or RCS participant arrives as plain text with `mentions` null, even in
            a group where other participants are on iMessage.

            - `Handle string`

              Address of the mentioned participant, exactly as the device recorded it — an E.164
              phone number or an email address.

            - `IsMe bool`

              Whether the mentioned participant is this line.

            - `Range []int64`

              Character range `[start, end)` in `value` highlighted as this mention.
              *Characters are measured as UTF-16 code units. Most characters count as 1; some emoji count as 2.*

          - `TextDecorations []TextDecoration`

            Text decorations applied to character ranges in the value

            - `Range []int64`

              Character range `[start, end)` in the `value` string where the decoration applies.
              `start` is inclusive, `end` is exclusive.
              *Characters are measured as UTF-16 code units. Most characters count as 1; some emoji count as 2.*

            - `Animation TextDecorationAnimation`

              Animated text effect to apply. Mutually exclusive with `style`.

              - `const TextDecorationAnimationBig TextDecorationAnimation = "big"`

              - `const TextDecorationAnimationSmall TextDecorationAnimation = "small"`

              - `const TextDecorationAnimationShake TextDecorationAnimation = "shake"`

              - `const TextDecorationAnimationNod TextDecorationAnimation = "nod"`

              - `const TextDecorationAnimationExplode TextDecorationAnimation = "explode"`

              - `const TextDecorationAnimationRipple TextDecorationAnimation = "ripple"`

              - `const TextDecorationAnimationBloom TextDecorationAnimation = "bloom"`

              - `const TextDecorationAnimationJitter TextDecorationAnimation = "jitter"`

            - `Style TextDecorationStyle`

              Text style to apply. Mutually exclusive with `animation`.

              - `const TextDecorationStyleBold TextDecorationStyle = "bold"`

              - `const TextDecorationStyleItalic TextDecorationStyle = "italic"`

              - `const TextDecorationStyleStrikethrough TextDecorationStyle = "strikethrough"`

              - `const TextDecorationStyleUnderline TextDecorationStyle = "underline"`

        - `type MediaPartResponse struct{…}`

          A media attachment part

          - `ID string`

            Unique attachment identifier

          - `Filename string`

            Original filename

          - `MimeType string`

            MIME type of the file

          - `Reactions []Reaction`

            Reactions on this message part

            - `Handle ChatHandle`

            - `IsMe bool`

              Whether this reaction is from the current user

            - `Type ReactionType`

              Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
              Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
              Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

            - `ID string`

              Identifier for this reaction. Pass it to
              `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

              Stickers placed before this API shipped can be read but not moved: the
              device-side reference needed to reposition them was never recorded, so
              `PATCH` returns 404 for those.

            - `CustomEmoji string`

              Custom emoji if type is "custom", null otherwise

            - `Sticker ReactionSticker`

              Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

          - `SizeBytes int64`

            File size in bytes

          - `Type MediaPartResponseType`

            Indicates this is a media attachment part

            - `const MediaPartResponseTypeMedia MediaPartResponseType = "media"`

          - `URL string`

            Presigned URL for downloading the attachment (expires in 1 hour).

        - `type LinkPartResponse struct{…}`

          A rich link preview part

          - `Reactions []Reaction`

            Reactions on this message part

            - `Handle ChatHandle`

            - `IsMe bool`

              Whether this reaction is from the current user

            - `Type ReactionType`

              Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
              Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
              Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

            - `ID string`

              Identifier for this reaction. Pass it to
              `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

              Stickers placed before this API shipped can be read but not moved: the
              device-side reference needed to reposition them was never recorded, so
              `PATCH` returns 404 for those.

            - `CustomEmoji string`

              Custom emoji if type is "custom", null otherwise

            - `Sticker ReactionSticker`

              Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

          - `Type LinkPartResponseType`

            Indicates this is a rich link preview part

            - `const LinkPartResponseTypeLink LinkPartResponseType = "link"`

          - `Value string`

            The URL

        - `type SentMessagePartIMessageAppPartResponse struct{…}`

          An iMessage app card part.

          - `App SentMessagePartIMessageAppPartResponseApp`

            Identifies the iMessage app (Messages app extension) that backs the card.

            - `BundleID string`

              Bundle identifier of the Messages app extension. Must not contain `:`.

            - `Name string`

              Display name of the app, shown by Messages' fallback UI.

            - `TeamID string`

              The app's 10-character uppercase alphanumeric team identifier.

            - `AppStoreID int64`

              The owning app's App Store id (optional). When set, recipients without the iMessage app
              installed see a "Get the app" affordance.

          - `Layout SentMessagePartIMessageAppPartResponseLayout`

            Visible layout of the card. At least one of
            `caption`, `subcaption`, `trailing_caption`, `trailing_subcaption`, or `image_url` must be
            set, otherwise the card renders as an empty bubble.

            `image_url` displays a preview image at the top of the card. The image renders on the
            recipient's card whether or not they have your app installed. The small icon beside the
            caption is the app's own icon and is not settable here.

            `* Note - requires a trusted chat w/ inbound activity`

            `image_title` and `image_subtitle` render as text overlaid on the image (title bold, subtitle
            beneath it). They only appear when `image_url` is set — without an image there is nothing to
            overlay — so setting either without `image_url` is rejected.

            - `Caption string`

              Primary label, top-left and bold.

            - `ImageSubtitle string`

              Text shown below `image_title`, overlaid on the card image. Requires `image_url`.

            - `ImageTitle string`

              Bold text overlaid on the card image. Requires `image_url` (rejected without it).

            - `ImageURL string`

              URL of an image (JPEG, PNG, HEIF, or WebP) to display as the card's preview image; an unreachable or non-image URL returns a validation error. Renders for all recipients regardless of whether they have the app. Note - requires a trusted chat w/ inbound activity. In responses, this is the re-hosted `cdn.linqapp.com` copy of the image you supplied, not your original URL.

            - `Subcaption string`

              Secondary label, below `caption` on the left.

            - `TrailingCaption string`

              Label shown top-right.

            - `TrailingSubcaption string`

              Label shown below `trailing_caption`, on the right.

          - `Reactions []Reaction`

            Reactions on this message part

            - `Handle ChatHandle`

            - `IsMe bool`

              Whether this reaction is from the current user

            - `Type ReactionType`

              Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
              Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
              Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

            - `ID string`

              Identifier for this reaction. Pass it to
              `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

              Stickers placed before this API shipped can be read but not moved: the
              device-side reference needed to reposition them was never recorded, so
              `PATCH` returns 404 for those.

            - `CustomEmoji string`

              Custom emoji if type is "custom", null otherwise

            - `Sticker ReactionSticker`

              Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

          - `Type string`

            Indicates this is an iMessage app card part.

            - `const SentMessagePartIMessageAppPartResponseTypeIMessageApp SentMessagePartIMessageAppPartResponseType = "imessage_app"`

          - `URL string`

            The URL delivered to the iMessage app on tap.

          - `FallbackText string`

            Fallback text for surfaces that cannot render the card.

        - `type SentMessagePartAppClipPartResponse struct{…}`

          An App Clip card part

          - `Reactions []Reaction`

            Reactions on this message part

            - `Handle ChatHandle`

            - `IsMe bool`

              Whether this reaction is from the current user

            - `Type ReactionType`

              Type of reaction. Standard iMessage tapbacks are love, like, dislike, laugh, emphasize, question.
              Custom emoji reactions have type "custom" with the actual emoji in the custom_emoji field.
              Sticker reactions have type "sticker" with sticker attachment details in the sticker field.

            - `ID string`

              Identifier for this reaction. Pass it to
              `PATCH /v3/messages/{messageId}/reactions/{reactionId}` to move a sticker.

              Stickers placed before this API shipped can be read but not moved: the
              device-side reference needed to reposition them was never recorded, so
              `PATCH` returns 404 for those.

            - `CustomEmoji string`

              Custom emoji if type is "custom", null otherwise

            - `Sticker ReactionSticker`

              Sticker attachment details when reaction_type is "sticker". Null for non-sticker reactions.

          - `Type string`

            Indicates this is an App Clip card part

            - `const SentMessagePartAppClipPartResponseTypeAppClip SentMessagePartAppClipPartResponseType = "app_clip"`

          - `Value string`

            The App Clip link the card opens

          - `Description string`

            The card's summary line, composed by Linq from the App Clip page

          - `ImageURL string`

            The card's preview image

          - `Title string`

            The card's headline, composed by Linq from the App Clip page

      - `SentAt Time`

        When the message was actually sent (null if still queued)

      - `DeliveredAt Time`

        When the message was delivered

      - `Effect MessageEffect`

        iMessage effect applied to a message (screen or bubble effect)

        - `Name string`

          Name of the effect. Common values:

          - Screen effects: confetti, fireworks, lasers, sparkles, celebration, hearts, love, balloons, happy_birthday, echo, spotlight
          - Bubble effects: slam, loud, gentle, invisible

        - `Type MessageEffectType`

          Type of effect

          - `const MessageEffectTypeScreen MessageEffectType = "screen"`

          - `const MessageEffectTypeBubble MessageEffectType = "bubble"`

      - `FromHandle ChatHandle`

        The sender of this message as a full handle object

      - `PreferredService ServiceType`

        Messaging service type

      - `ReplyTo ReplyTo`

        Indicates this message is a threaded reply to another message

        - `MessageID string`

          The ID of the message to reply to

        - `PartIndex int64`

          The specific message part to reply to (0-based index).
          Defaults to 0 (first part) if not provided.
          Use this when replying to a specific part of a multipart message.

      - `Service ServiceType`

        Messaging service type

    - `Service ServiceType`

      Messaging service type

### Example

```go
package main

import (
  "context"
  "fmt"

  "github.com/linq-team/linq-go"
  "github.com/linq-team/linq-go/option"
)

func main() {
  client := linqgo.NewClient(
    option.WithAPIKey("My API Key"),
  )
  chat, err := client.Chats.New(context.TODO(), linqgo.ChatNewParams{
    From: "+12052535597",
    Message: linqgo.MessageContentParam{
      Parts: []linqgo.MessageContentPartUnionParam{linqgo.MessageContentPartUnionParam{
        OfText: &linqgo.TextPartParam{
          Type: linqgo.TextPartTypeText,
          Value: "Hello! How can I help you today?",
        },
      }},
    },
    To: []string{"+12052532136"},
  })
  if err != nil {
    panic(err.Error())
  }
  fmt.Printf("%+v\n", chat.Chat)
}
```

#### Response

```json
{
  "chat": {
    "id": "94c6bf33-31d9-40e3-a0e9-f94250ecedb9",
    "display_name": "+14155551234, +14155559876",
    "handles": [
      {
        "id": "550e8400-e29b-41d4-a716-446655440010",
        "handle": "+14155551234",
        "joined_at": "2025-05-21T15:30:00.000Z",
        "service": "iMessage",
        "is_me": true,
        "left_at": "2019-12-27T18:11:19.117Z",
        "status": "active"
      },
      {
        "id": "550e8400-e29b-41d4-a716-446655440011",
        "handle": "+14155559876",
        "joined_at": "2025-05-21T15:30:00.000Z",
        "service": "iMessage",
        "is_me": false,
        "left_at": "2019-12-27T18:11:19.117Z",
        "status": "active"
      }
    ],
    "health_status": {
      "doc_url": "https://docs.linqapp.com/channel/imessage/guides/chats/chat-health#at-risk",
      "status": "AT_RISK",
      "updated_at": "2026-05-01T18:28:25Z"
    },
    "is_group": false,
    "message": {
      "id": "69a37c7d-af4f-4b5e-af42-e28e98ce873a",
      "created_at": "2025-10-23T13:07:55.019-05:00",
      "delivery_status": "pending",
      "is_read": false,
      "parts": [
        {
          "reactions": [
            {
              "handle": {
                "id": "69a37c7d-af4f-4b5e-af42-e28e98ce873a",
                "handle": "+15551234567",
                "joined_at": "2025-05-21T15:30:00.000-05:00",
                "service": "iMessage",
                "is_me": false,
                "left_at": "2019-12-27T18:11:19.117Z",
                "status": "active"
              },
              "is_me": false,
              "type": "love",
              "id": "9f8b1c2d-3e4f-5061-7283-94a5b6c7d8e9",
              "custom_emoji": null,
              "sticker": {
                "file_name": "sticker.png",
                "height": 420,
                "mime_type": "image/png",
                "url": "https://cdn.linqapp.com/attachments/a1b2c3d4/sticker.png?signature=...",
                "width": 420
              }
            }
          ],
          "type": "text",
          "value": "Hello!",
          "mention": "+14155551234",
          "mention_range": [
            4,
            9
          ],
          "mentions": [
            {
              "handle": "+14155550123",
              "is_me": true,
              "range": [
                4,
                9
              ]
            }
          ],
          "text_decorations": [
            {
              "range": [
                0,
                5
              ],
              "animation": "shake",
              "style": "bold"
            }
          ]
        }
      ],
      "sent_at": null,
      "delivered_at": null,
      "effect": {
        "name": "confetti",
        "type": "screen"
      },
      "from_handle": {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "handle": "+15551234567",
        "joined_at": "2025-05-21T15:30:00.000-05:00",
        "service": "iMessage",
        "is_me": false,
        "left_at": "2019-12-27T18:11:19.117Z",
        "status": "active"
      },
      "preferred_service": "iMessage",
      "reply_to": {
        "message_id": "550e8400-e29b-41d4-a716-446655440000",
        "part_index": 0
      },
      "service": "iMessage"
    },
    "service": "iMessage"
  }
}
```
