---
title: PostableMessage
description: The union type accepted by thread.post() for sending messages.
type: reference
---

`PostableMessage` is the union of all message formats accepted by `thread.post()` and `sent.edit()`.

```typescript
type PostableMessage = AdapterPostableMessage | AsyncIterable<string | StreamChunk | StreamEvent>;
```

## String

Raw text passed through as-is to the platform.

```typescript
await thread.post("Hello world");
```

## PostableRaw

Explicit raw text — behaves the same as a plain string.

```typescript
await thread.post({ raw: "Hello world" });
```

<TypeTable
  type={{
    raw: {
      description: 'Text passed through as-is to the platform.',
      type: 'string',
    },
    attachments: {
      description: 'File/image attachments.',
      type: 'Attachment[]',
    },
    files: {
      description: 'Files to upload.',
      type: 'FileUpload[]',
    },
  }}
/>

## PostableMarkdown

Markdown converted to each platform's native format.

```typescript
await thread.post({ markdown: "**Bold** and _italic_" });
```

<TypeTable
  type={{
    markdown: {
      description: 'Markdown text, converted to platform format via mdast.',
      type: 'string',
    },
    attachments: {
      description: 'File/image attachments.',
      type: 'Attachment[]',
    },
    files: {
      description: 'Files to upload.',
      type: 'FileUpload[]',
    },
  }}
/>

## PostableAst

mdast AST converted to each platform's native format. See [Markdown](/docs/api/markdown) for builder functions.

```typescript
import { root, paragraph, text, strong } from "chat";

await thread.post({
  ast: root([paragraph([strong([text("Hello")])])]),
});
```

<TypeTable
  type={{
    ast: {
      description: 'mdast AST root node.',
      type: 'Root',
    },
    attachments: {
      description: 'File/image attachments.',
      type: 'Attachment[]',
    },
    files: {
      description: 'Files to upload.',
      type: 'FileUpload[]',
    },
  }}
/>

## PostableCard

Rich card with interactive elements. See [Cards](/docs/api/cards) for components.

```typescript
import { Card, Text } from "chat";

await thread.post(Card({ title: "Hello", children: [Text("World")] }));
```

You can also pass a card with explicit fallback text:

```typescript
await thread.post({
  card: Card({ title: "Hello", children: [Text("World")] }),
  fallbackText: "Hello — World",
});
```

<TypeTable
  type={{
    card: {
      description: 'Rich card element.',
      type: 'CardElement',
    },
    fallbackText: {
      description: 'Plain text fallback for clients that cannot render cards.',
      type: 'string | undefined',
    },
    files: {
      description: 'Files to upload.',
      type: 'FileUpload[]',
    },
  }}
/>

## AsyncIterable (streaming)

An async iterable of strings, `StreamChunk` objects, or stream events. The SDK streams the message in real time using platform-native APIs where available.

You can yield structured `StreamChunk` objects for rich content like task progress cards on platforms that support it (Slack). See [Streaming](/docs/streaming#structured-streaming-chunks-slack-only) for details.

Both AI SDK stream types are supported:

```typescript
// fullStream (recommended) — preserves step boundaries in multi-step agents
const result = await agent.stream({ prompt: message.text });
await thread.post(result.fullStream);

// textStream — plain string chunks
await thread.post(result.textStream);
```

When using `fullStream`, the SDK auto-detects `text-delta` and `finish-step` events, extracting text and inserting paragraph breaks between agent steps.

## FileUpload

Used in the `files` field of any structured message format.

<TypeTable
  type={{
    data: {
      description: 'Binary file data.',
      type: 'Buffer | Blob | ArrayBuffer',
    },
    filename: {
      description: 'Filename.',
      type: 'string',
    },
    mimeType: {
      description: 'MIME type (inferred from filename if not provided).',
      type: 'string | undefined',
    },
  }}
/>
