Skip to content
TuffTasks, docs and chat for teams and AI agents

Interactive Answers

Inbox Cleanup

An agent's answer on which newsletters to drop — senders on a volume × opens scatter, an unsubscribe zone you can resize and a checklist to act on.

1 variantMIT

Preview

Inbox cleanup

40 senders · Last 90 days

Plotting 40 senders by volume and opens…

Volume × opens

Last 90 days

0 senders in the zone: 4 or more emails a week, under 10% opened.

Installation

Copies the component into components/ui and installs its dependencies and theme tokens.

pnpm dlx shadcn@latest add https://tuffui.com/r/inbox-cleanup.json

Usage

Pass your own data and wire the callbacks to your agent runtime.

import { InboxCleanup } from "@/components/ui/inbox-cleanup";

// Rendered by the agent in answer to "which newsletters should I unsubscribe from?"
export function InboxCleanupAnswer({ newsletters }: Props) {
  return (
    <InboxCleanup
      senders={newsletters.map((n) => ({
        id: n.id,
        name: n.fromName, // "The Daily Brief"
        perWeek: n.weeklyVolume, // 7
        openRate: n.opened / n.received, // 0.02
      }))}
      // Four or more emails a week, opened under 10% of the time.
      defaultZone={{ minPerWeek: 4, maxOpenRate: 0.1 }}
      onUnsubscribe={(ids) => runTool("unsubscribe", { senders: ids })}
      onDigest={(ids) => runTool("weekly_digest", { senders: ids })}
      onKeep={() => sendFollowUp("Keep them all for now")}
    />
  );
}

Props

PropTypeDefaultDescription
senders*InboxSender[]—Every sender, in any order. Each is a dot: emails a week across, open rate up a log scale.
zoneInboxZone—Controlled unsubscribe zone. Senders at or above `minPerWeek` and below `maxOpenRate` are in it.
defaultZoneInboxZone{ minPerWeek: 4, maxOpenRate: 0.1 }Starting zone when uncontrolled.
onZoneChange(zone: InboxZone) => void—Called while the zone's left or top edge is dragged, or moved with the arrow keys (Shift for bigger steps).
adjustablebooleantrueShows the two edge handles. Off fixes the zone where it is.
selectedIdsstring[]—Controlled selection: the senders to unsubscribe from. Ids outside the zone are ignored.
defaultSelectedIdsstring[]—Starting selection when uncontrolled. Omitted, every sender in the zone starts checked, including ones that enter it later.
onSelectedChange(ids: string[]) => void—Called when a checkbox is toggled, or a zone dot is clicked or picked with Enter or Space.
highlightedIdstring | null—The sender the agent points at: its dot gets a ring and its tooltip shows until someone hovers another.
xDomain[number, number][0, 12]Emails a week across the x axis. Senders beyond it sit on the edge.
xTicksnumber[][0, 4, 8, 12]X axis labels. Defaults to four evenly spaced values.
yDomain[number, number][0.01, 1]Open rate, 0–1, up the log y axis.
yTicksnumber[][1, 0.5, 0.25, 0.1]Y labels and their gridlines.
summaryReactNode | ((stats: InboxCleanupStats) => ReactNode)—Agent text above the chart. Defaults to a sentence built from the live zone: senders, threshold and emails a week skipped.
status"analyzing" | "ready""ready"Analyzing plots grey dots only. Ready draws the zone in, turns its senders accent and opens the list and actions.
analyzingTextReactNode"Plotting 40 senders by volume and opens…"Shown in place of the summary while analyzing.
listbooleantrueShows the senders in the zone with checkboxes. Hovering a row picks out its dot, and the other way round.
onUnsubscribe(ids: string[]) => void—Shows the primary "Unsubscribe from N" button. Gets the checked ids.
onDigest(ids: string[]) => void—Shows "Digest instead". Gets the checked ids.
onKeep() => void—Shows "Keep all".
iconIconBroomPhosphor icon in the header tile.
titleReactNode"Inbox cleanup"Header title.
subtitleReactNode"40 senders · Last 90 days"Secondary line under the title.
chartTitlestring"Volume × opens"Chart heading, also its accessible name.
periodstring"Last 90 days"Period label right of the chart heading.
zoneLabelstring"Unsubscribe zone"Label in the zone's top-right corner.
formatPerWeek(perWeek: number) => string"7 / wk"Volume in the tooltip and the list.
formatOpenRate(openRate: number) => string"2%"Open rate in the tooltip, the list and the default summary.
formatXTick(value: number, index: number, ticks: number[]) => string"12 / wk" on the last tickX axis labels.
formatYTick(value: number) => string"25%"Y axis labels.
introbooleantrueDots fade and scale in on mount, and the zone's senders turn accent one by one when it appears. Skipped with reduced motion.
classNamestring—Extra classes for the root element.
type InboxSender = {
  id: string;
  name: string;       // "The Daily Brief"
  perWeek: number;    // emails a week, 7
  openRate: number;   // share opened, 0–1, 0.02
};

type InboxZone = {
  minPerWeek: number;  // 4  → at least 4 emails a week
  maxOpenRate: number; // 0.1 → opened under 10%
};

type InboxCleanupStats = {
  count: number;          // senders in the zone
  perWeek: number;        // emails a week from them
  skippedPerWeek: number; // of those, unopened
  zone: InboxZone;
};

// Also exported: inZone(sender, zone) => boolean