Email Thread

An email conversation from the user's mailbox, with an optional reply the agent drafted for review before anything is sent.

Experience · Shares patterns with Draft Options, Citations

≈ 2,139 tokens per use (tool 1,375 · guidance 297 · typical payload 467)How token costs are estimated

Example: Email Thread

What did Dana say about the contract?

Northwind MSA renewal: redlines

Openin your mail app (opens in a new tab)

Dana Levi, Sam Ortiz, you · Work inbox

Sam Ortiz

sam@northwind.example, to you, cc Dana

Hi Alex, Attached is our redlined MSA. The main changes are in section 7 (liability) and section 11 (term). We'd like to sign by October 15. Sam
  • Northwind-MSA-redlines-v2.pdf412 KB
Dana Levi

dana@acme.example, to you

Alex, Section 11 is fine: a two-year term with 60 days' notice is standard. Section 7 is the problem. We can accept the redlines except the uncapped liability clause in 7.2, which needs to go back to the 12-month fee cap. (answers your question) My counter-proposal is in the matter notes: https://legal.acme.example/matters/4471 (opens in a new tab). Dana
Alex Kim

alex@acme.example, to Dana

Thanks, Dana. I'll suggest a call to Sam for later this week.

When to use

  • The user asks about an email conversation: what someone said, where a thread stands, or to catch up on it.
  • The user asks the agent to reply in a thread. Show the thread with the draft so they can review and edit it before anything is sent.

When not to use

  • The agent couldn't read the thread. Fetch it first; never reconstruct messages from memory.
  • The user wants a list of many emails, like a search or an inbox view. Use a Data Table.
  • The user is writing a new email, not replying in a thread. Use Draft Options.

How the agent should use it

  • Pass the messages verbatim from the mailbox, oldest first, as plain text. Split the quoted history into quoted.
  • Mark the mailbox owner in participants with self: true. They're shown as "you".
  • Keep summary to one or two sentences that only state what the messages say.
  • Set highlight to a sentence copied verbatim from that message's body. If it doesn't match exactly, nothing is highlighted.
  • Never send until the result is send, then send exactly the returned body. On revise, rewrite the draft from the feedback and show the thread again. On discard, send nothing.

Reading and replying

The summary, when given, leads the card as the answer. Every message is shown in full, with its sender, time, attachments and "Unread" or "Important" tags; the header carries one line of participants. Each message's recipients ("to Sam, cc Priya") and the sender's address are read to screen readers (the address is also the name's tooltip) but kept off screen. Only the quoted older text is collapsed, behind Show quoted, as in any mail client. Bodies keep their line breaks, and http(s) URLs become links that open in a new tab. Nothing is ever rendered as HTML.

Each message is an article named by its sender and time ("Dana Levi, Oct 6, 10:42 AM"). Times are relative ("Yesterday, 4:10 PM") with the full date in a tooltip, and are computed after mount so server and client markup match.

The highlighted sentence is wrapped in <mark> with a screen-reader note, "(answers your question)".

With draft, a Draft reply block follows the last message: recipients as plain text (name and address), and the body in an editable text area that grows with its content (its character count is read to screen readers). The footer offers Send, Discard, and Request changes (the message icon, which opens a feedback box). The component never sends anything: it returns the result, and the host sends, after its own confirmation if it needs one. Without a draft, the thread is display-only.

The helpers (participant lists, the autolinker, highlight splitting and relative time) are pure functions in email-thread-utils.ts.

Installation

npx shadcn@latest add https://your-site.com/r/email-thread.json

Usage

app/api/chat/route.ts
import { tool } from "ai"
import { emailThreadTool } from "@/components/genui/email-thread.schema"

const tools = {
  // With a draft, no execute(): the call is completed by the user's decision.
  // Without one, return immediately, e.g. execute: async () => ({ shown: true }).
  [emailThreadTool.name]: tool({
    description: emailThreadTool.description,
    inputSchema: emailThreadTool.inputSchema,
  }),
}
components/message.tsx
<EmailThread
  {...part.input}
  status={part.state === "input-streaming" ? "streaming" : undefined}
  result={part.state === "output-available" ? part.output : undefined}
  onResult={(result) =>
    addToolResult({ tool: "show_email_thread", toolCallId: part.toolCallId, output: result })
  }
/>

When there is a draft, the agent receives one of:

{ action: "send", body: "Hi Sam,\n\nThursday at 10 AM ET works…", edited: false }
{ action: "revise", feedback: "Mention that Dana will join too" }
{ action: "discard" }

If the user changed the body, edited is true and body holds their version. Send exactly that text. While the host sends, pass status="submitting".

With LangGraph

Push it from a graph node with ui.push({ name: "show_email_thread", props: { toolCallId, ...args } }, { message }), and render the thread's UI messages with the Renderer.

Restoring from history

Pass the stored result back as result. A sent reply appears as the last message with a "Sent" label, a discarded draft is removed with a quiet note, and requested changes show the feedback. The component never calls onResult again.

<EmailThread
  {...part.input}
  result={part.state === "output-available" ? part.output : undefined}
/>

Schema

PropTypeNotes
subjectstringData-bound. The subject line
mailbox{ label }?Data-bound. e.g. "Work inbox". Read to screen readers only
participants{ name, email, self? }[]?Data-bound. self marks the mailbox owner, shown as "you". Derived from the messages when omitted
messagesEmailMessage[]Data-bound. 1–50, oldest first
summarystring?One or two sentences that reflect the messages
highlight{ messageId, text }?A sentence copied verbatim from that message's body
draft{ to, cc?, body, replyToId }?A reply for review. Adds the actions
urlstring?Data-bound. Opens the thread in the mail app. http(s) only

EmailMessage

FieldTypeNotes
idstringThe mailbox's message id
from{ name, email }
to / cc{ name, email }[]cc is optional
sentAtstringISO 8601 with offset
bodystringPlain text, never HTML, without the quoted history
quotedstring?The quoted older text, collapsed by default
attachments{ name, sizeBytes?, kind?, url? }[]?kind: pdf, image, doc, sheet or other
unread / importantboolean?Shown as tags

Host props: status (streaming, ready, submitting, sent, revised, discarded), onResult, result and className.

Source

On this page