## Send a message (auto-selected from-number)

`client.Messages.New(ctx, params) (*MessageNewResponse, error)`

**post** `/v3/messages`

Send a message to one or more recipients **without supplying a `from`
number**. Linq resolves both the sending line and the target chat for you,
then returns exactly which line was used, which chat the message landed in,
whether a new chat was created, and every resulting message id.

This fuses "create chat" and "send message" behind a single
message-centric resource. Provide only the recipients (`to`) and the
`message`; the platform decides the rest.

## How the from-number and chat are chosen

- **Reuse** — if a chat with exactly these recipients already exists on a
  line that can still send, the message is sent into that chat on its
  existing line (`from_selection.reason = reused_active_chat`). The
  most-recently-active such chat wins; chats stranded on flagged lines
  (e.g. by an earlier failover) are skipped.
- **New** — if no such chat exists, a new chat is created on the best
  available line (`from_selection.reason = new_best_number`).
- **Failover** — if matching chats exist but none is on a line that can
  send, a **new** chat is created on a fresh best line and the flagged chat
  is abandoned (`from_selection.reason = failover_flagged`,
  `previous_chat_id` set). If you supply `continuation_message`, that
  text is sent as the single message INSTEAD of `message` (useful as a
  fresh-number-appropriate opener). Exactly one message is sent either way.

Recipients (`to`) are an order-independent set: a single handle is a direct
chat, multiple handles a group chat.

## Excluding lines

`exclude_from` keeps specific lines out of **this** send's line pick. It
only affects picking a line for a new chat — an existing chat is always
reused on its own line, preferring a chat on a non-excluded line when the
recipients have more than one. An exclusion never abandons a live chat or
moves it to a new number, so if the only chat these recipients have is on
an excluded line, that chat is still used. `from` tells you the line that
was actually used.

## Differences from POST /v3/chats

- The first message **may contain a link** (including for a newly created
  chat). Note: sending a link as the very first message on a freshly
  selected line can elevate that line's flagging risk — it is allowed, not
  recommended.
- Voice memos are **not** supported here. To send an iMessage voice-memo
  bubble, use `POST /v3/chats/{chatId}/voicememo` with a known chat id.

## Service preference, effects, decorations

Set `message.preferred_service` (`iMessage` | `RCS` | `SMS`), `message.effect`,
and per-part `text_decorations` exactly as on the other send endpoints.

Always responds `202 Accepted` — chat creation is incidental to the send.

### Parameters

- `params MessageNewParams`

  - `Message param.Field[MessageContent]`

    Body param: 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]`

    Body param: Recipient handles (E.164 phone numbers or email addresses). One handle
    is a direct chat; multiple handles a group chat. Order-independent — the
    set identifies the chat.

  - `ContinuationMessage param.Field[MessageNewParamsContinuationMessage]`

    Body param: Text-only fallback that **replaces** `message` ONLY on the failover branch —
    when a chat with these recipients already existed but its line was flagged,
    so a new chat is created on a fresh line. On that branch this text is sent as
    the single message instead of `message` (the recipient is on a new number, so
    you typically want a fresh-number-appropriate opener rather than the original
    content). Ignored otherwise (a healthy reuse, or genuine first contact).
    Carries no parts, media, or effects — exactly one message is ever sent.

    - `Text string`

      The replacement message text, sent as the single message on failover.

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

    Body param: Lines (E.164) not to pick for this send. Applies for this request
    only — nothing is remembered between calls.

    **Exclusion only affects picking a line for a new chat.** If `to`
    already has a chat, that chat is reused on its own line, and a chat on
    a non-excluded line is preferred when there is more than one. If the
    only chat these recipients have is on an excluded line, it is still
    reused — an exclusion never abandons a live chat or moves it to a new
    number. Check `from` in the response to see the line that was actually
    used.

    Numbers that are not your lines are ignored. Every entry must be
    E.164 — a value like `4155551234` is rejected rather than silently
    skipped. Excluding every one of your available lines returns 400 when
    a line has to be picked.

  - `OverrideOptout param.Field[bool]`

    Body param: 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.

  - `IdempotencyKey param.Field[string]`

    Header param: Optional idempotency key for the send. Reuse the same key to safely
    retry without sending twice. May also be supplied as
    `message.idempotency_key`.

### Returns

- `type MessageNewResponse struct{…}`

  Result of an auto-from send. Self-describing: which line was used, which
  chat the message landed in, whether a new chat was created, and the
  resulting message id(s).

  - `ChatID string`

    The resolved chat (reused or newly created) the message landed in.

  - `CreatedNewChat bool`

    True when a new chat was created (new or failover), false on reuse.

  - `From string`

    The line (E.164) the message was actually sent from.

  - `FromSelection MessageNewResponseFromSelection`

    Why this line/chat was chosen.

    - `Reason string`

      - `reused_active_chat` — reused an existing chat on its healthy line
      - `new_best_number` — created a new chat on the best available line
      - `failover_flagged` — no existing chat for these recipients was on
        a line that could send; created a new chat on a fresh line

      - `const MessageNewResponseFromSelectionReasonReusedActiveChat MessageNewResponseFromSelectionReason = "reused_active_chat"`

      - `const MessageNewResponseFromSelectionReasonNewBestNumber MessageNewResponseFromSelectionReason = "new_best_number"`

      - `const MessageNewResponseFromSelectionReasonFailoverFlagged MessageNewResponseFromSelectionReason = "failover_flagged"`

    - `ReusedExistingChat bool`

      True only when an existing chat was reused.

  - `Handles []ChatHandle`

    Participants of the resolved chat.

    - `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"`

  - `IsGroup bool`

    Whether the resolved chat 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

  - `PreviousChatID string`

    Set ONLY on `failover_flagged`: the abandoned flagged chat that was NOT
    sent into. Null otherwise.

### 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"),
  )
  message, err := client.Messages.New(context.TODO(), linqgo.MessageNewParams{
    Message: linqgo.MessageContentParam{
      Parts: []linqgo.MessageContentPartUnionParam{linqgo.MessageContentPartUnionParam{
        OfText: &linqgo.TextPartParam{
          Type: linqgo.TextPartTypeText,
          Value: "Hi! Thanks for reaching out — how can we help?",
        },
      }},
    },
    To: []string{"+14155559876"},
  })
  if err != nil {
    panic(err.Error())
  }
  fmt.Printf("%+v\n", message.ChatID)
}
```

#### Response

```json
{
  "chat_id": "94c6bf33-31d9-40e3-a0e9-f94250ecedb9",
  "created_new_chat": false,
  "from": "+12052535597",
  "from_selection": {
    "reason": "reused_active_chat",
    "reused_existing_chat": true
  },
  "handles": [
    {
      "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"
    }
  ],
  "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",
  "previous_chat_id": null
}
```
