Message Draft

Review and approve messages before an AI agent sends them.

Updated proposal attached

Fromsarah.mitchell@acme.co
Tomarcus.chen@acme.co

Hi Marcus, I've attached the revised proposal with the changes we discussed. The new timeline reflects the Q2 launch date, and I've adjusted the budget breakdown in section 3. Let me know if you have any questions. Best, Sarah

<MessageDraft  id="message-draft-email"  channel="email"  subject="Updated proposal attached"  from="sarah.mitchell@acme.co"  to={["marcus.chen@acme.co"]}  body={`Hi Marcus,I've attached the revised proposal with the changes we discussed. The new timeline reflects the Q2 launch date, and I've adjusted the budget breakdown in section 3.Let me know if you have any questions.Best,Sarah`}  onSend={() => console.log("Message sent")}  onCancel={() => console.log("Message cancelled")}/>

Getting Started

Run this once from your project root.

npx shadcn@latest add @tool-ui/message-draft

Render MessageDraft in your UI with tool-compatible props.

    import { MessageDraft } from "@/components/tool-ui/message-draft";    export function Example() {      return (        <MessageDraft          id="message-draft-example"          channel="email"          subject="Q4 Planning Follow-up"          to={["sarah.chen@company.com"]}          body={`Hi Sarah,Thanks for joining the planning meeting today. I've attached the updated timeline.Best,Alex`}onSend={() => console.log("Sent")}onCancel={() => console.log("Cancelled")}/>);}

Register this renderer so tool results display as MessageDraft.

"use client";import { type Toolkit } from "@assistant-ui/react";import { MessageDraft } from "@/components/tool-ui/message-draft";import {  safeParseSerializableMessageDraft,  SerializableMessageDraftSchema,} from "@/components/tool-ui/message-draft/schema";import { createArgsToolRenderer } from "@/components/tool-ui/shared";export const toolkit: Toolkit = {  draftMessage: {    description: "Draft a message for the user to review before sending.",    parameters: SerializableMessageDraftSchema,    render: createArgsToolRenderer({      safeParse: safeParseSerializableMessageDraft,      idPrefix: "message-draft",      render: (parsedArgs, { result, addResult }) =>        result ? (            <MessageDraft {...parsedArgs} outcome={result} />          ) : (            <MessageDraft              {...parsedArgs}              onSend={async () => {                // Perform actual send logic here                await sendMessage(parsedArgs);                await addResult?.("sent");              }}              onCancel={() => addResult?.("cancelled")}            />          ),    }),  },};

These snippets use assistant-ui for end-to-end wiring, but the MessageDraft component itself is framework-agnostic at the UI layer: you can use it in any React codebase with any LLM SDK or tool-calling interface that can provide compatible props.

Key Features

Human-in-the-loop

Review messages before the AI sends them on your behalf

Undo grace period

Cancel within a configurable window after clicking Send

Channel skins

Email and Slack with channel-specific metadata display

Receipt states

Compact confirmation after send or cancel

Channel Skins

The component supports multiple channel types, each with its own metadata fields.

Email

Shows subject line, To/CC/BCC recipients, and message body.

Slack

Shows channel or DM target with the Slack icon.

Send Flow

After clicking Send, there's a grace period where the user can undo:

  1. Review - User reviews the draft with Send/Cancel buttons
  2. Sending - Countdown with Undo button (default 5 seconds)
  3. Sent - Compact receipt confirming the message was sent

The grace period is configurable via undoGracePeriod (in milliseconds).

Receipt States

When outcome is set, the component renders a compact receipt showing the result. This is useful for displaying the outcome in conversation history.

Props

Prop

Type

Email-specific Props

Prop

Type

Slack-specific Props

Prop

Type

Accessibility

  • Uses article with aria-labelledby for the draft content
  • Keyboard navigation:
    • Tab to move between buttons
    • Enter or Space to activate focused button
    • Escape triggers cancel while in review state
  • Sending state announces countdown via aria-live="polite"
  • Receipt state uses role="status" for screen reader announcements
  • Focus moves to Undo button when entering sending state