Back to Novu

Reply

docs/agents/custom-code-agent/building-blocks/reply.mdx

3.19.06.7 KB
Original Source

A reply sends a message back into the conversation. Agents can reply with plain text, markdown with files, or interactive cards, depending on provider capabilities.

Interactive cards include buttons, dropdowns, links, and text inputs. When a user interacts with a card, the onAction handler fires with the action ID and selected value.

Use replies when the agent needs to communicate something to the participant in the conversation.

Replies are user-facing messages your agent sends back into the conversation. Use signals when you need to update conversation state, trigger workflows, or resolve the thread without messaging the user.

The public API is ctx.reply(content, options?). You can also return a string or JSX card from a handler instead of calling ctx.reply() directly.

To send a reply from your backend without going through the bridge handler, use the Send an agent reply API.

Channel support

CapabilitySlackTeamsWhatsAppTelegramEmail
Plain / rich textYesYesBold / italicsYesHTML
Tap-to-respond buttonsYesYesYes (up to 3)YesAction links
Outbound file attachmentsYesYesNot yetNot yetNot yet

See Channels overview for the full matrix.

Reply types

The following table summarizes the reply types your agent can send:

TypeContentAttachmentsUser interaction
Plain textStringVia options.filesNone
MarkdownString with markdownVia options.filesNone
Interactive cardsCard and child componentsNot on cardsButtons, dropdowns, links, inputs trigger onAction

To change a message after you send it, see Edit sent messages.

Plain text

Send a simple string reply with ctx.reply():

typescript
await ctx.reply('Hello! How can I help?');

Markdown

Send formatted text by passing a markdown string to ctx.reply():

typescript
await ctx.reply('**Report generated.** See the attached PDF.');

Sending attachments

Include files with string or markdown replies via the optional second argument.

<Note> Outbound file attachments are delivered on **Slack** and **Microsoft Teams**. WhatsApp, Telegram, and email do not support outbound files yet. See [Channels overview](/agents/channels/overview). </Note>

When sending attachments, keep these limits in mind:

  • Attachments are limited to 25 MB per file.
  • Files are only supported with string or markdown replies, not card replies.
  • Provide each file with exactly one of url or data.
  • Prefer url for larger files. Inline data is capped at 5 MB per file (and 5 MB aggregate per message).

File reference type

Each file uses a FileRef object with the following shape:

tsx
type FileRef = {
  filename: string;
  mimeType?: string;
  data?: string | Uint8Array | ArrayBuffer | Blob;
  url?: string;
};

Use url for larger files. Novu fetches public HTTP(S) URLs server-side. Use data for small generated files in memory.

The following examples show both approaches:

<CodeGroup> ```tsx title="URL attachment" await ctx.reply('Here is your report.', { files: [{ filename: 'report.pdf', mimeType: 'application/pdf', url: reportUrl }], }); ``` ```tsx title="In-memory attachment" const csv = new TextEncoder().encode('name,total\nNovu,42'); await ctx.reply('CSV generated.', { files: [{ filename: 'report.csv', mimeType: 'text/csv', data: csv }], }); ``` </CodeGroup>

Interactive cards

Cards are structured messages with buttons, dropdowns, links, and more. Build them with function calls or JSX.

<Tabs> <Tab title="Function call API">

The following example builds a card with the function call API:

tsx
import {
  Card, Button, CardText, Actions,
  Select, SelectOption, Divider, CardLink,
} from '@novu/framework';

await ctx.reply(Card({ title: 'Order #1234', children: [
  CardText('Your order is ready for pickup.'),
  Divider(),
  Actions([
    Button({ id: 'ack', label: 'Acknowledge' }),
    Button({ id: 'escalate', label: 'Escalate', style: 'danger' }),
  ]),
  CardLink({ url: 'https://example.com/order/1234', children: 'View details' }),
] }));
</Tab> <Tab title="JSX API">

Add "jsxImportSource": "@novu/framework" to tsconfig.json, then return JSX from a handler or pass it to ctx.reply():

tsx
/** @jsxImportSource @novu/framework */
import {
  Actions, agent, Button, Card, CardLink, CardText, Divider,
} from '@novu/framework';

export const myAgent = agent('my-agent', {
  onMessage: async (message, ctx) => {
    return (
      <Card title="Order #1234">
        <CardText>Your order is ready for pickup.</CardText>
        <Divider />
        <Actions>
          <Button id="ack" label="Acknowledge" />
          <Button id="escalate" label="Escalate" style="danger" />
        </Actions>
        <CardLink url="https://example.com/order/1234">View details</CardLink>
      </Card>
    );
  },
});

You can also pass JSX to ctx.reply():

tsx
await ctx.reply(
  <Card title="Order #1234">
    <CardText>Your order is ready for pickup.</CardText>
  </Card>,
);

When a user clicks a button or selects a dropdown value, onAction fires with action.id and action.value. See Handlers and context.

</Tab> </Tabs>

Available card components

The following table lists the card components you can use in replies:

ComponentDescription
CardContainer with an optional title
CardTextText block inside a card
ButtonInteractive button; id maps to action.id in onAction
ActionsRequired wrapper around Button elements
Select / SelectOptionDropdown; triggers onAction with selected value
DividerVisual separator
CardLinkClickable link
TextInputText input field
<Columns cols={2}> <Card icon="pencil" href="/agents/custom-code-agent/building-blocks/edit-sent-messages" title="Edit sent messages"> Update a message in place after sending it with `ReplyHandle`. </Card> <Card icon="signal" href="/agents/custom-code-agent/building-blocks/signals" title="Signals"> Metadata, workflow triggers, and conversation resolution. </Card> <Card icon="sparkles" href="/agents/custom-code-agent/frameworks/ai-sdk" title="AI SDK"> Build end to end with `@novu/framework/ai-sdk`. </Card> <Card icon="code" href="/api-reference/agents/send-an-agent-reply" title="Send an agent reply API"> Send replies from your backend without a bridge handler. </Card> </Columns>