> ## Documentation Index
> Fetch the complete documentation index at: https://docs.heylua.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Proactive Inbox

> A monitoring skill that pushes notices, approvals, and connection fixes to the user's desk

## What we're building

A refund-monitoring job that watches for anomalies and uses [`User.Inbox.push()`](/api/inbox) to reach the user proactively — the three card kinds working together:

1. A **notice** when a refund spike appears (revised in place as numbers change),
2. An **approval** when the agent has drafted a response and wants a go-ahead,
3. A **connection fix** when the data source it depends on disconnects.

Along the way it handles every receipt outcome — including being `capped` — so the skill degrades gracefully instead of erroring.

## The monitor

```typescript theme={null}
import { User } from 'lua-cli';

async function checkRefunds() {
  const spike = await detectRefundSpike(); // your own logic

  if (!spike) return { status: 'all clear' };

  // ── 1. The notice — keyed, so re-runs REVISE instead of re-knocking ──
  const receipt = await User.Inbox.push({
    title: `${spike.count} refunds on ${spike.sku} within the hour`,
    body: 'All from the same checkout flow — want a summary before it spreads?',
    deeplink: `https://yourdashboard.example.com/refunds?sku=${spike.sku}`,
    priority: spike.count > 5 ? 'urgent' : 'high',
    key: `refund-spike-${spike.sku}`, // stable per incident
  });

  // ── 2. Handle the receipt — capped is a NORMAL outcome ──
  if (receipt.outcome === 'capped') {
    // Budget spent or the org has disabled pushes. Don't retry, don't
    // throw — carry the finding in your run summary instead.
    return {
      status: 'anomaly found',
      note: `Refund spike on ${spike.sku} (${spike.count} in the last hour). ` +
            `Inbox push unavailable: ${receipt.reason}`,
    };
  }

  return { status: 'user notified', outcome: receipt.outcome };
}
```

Run it again while the incident is live and the same card updates silently:

```typescript theme={null}
// Ten minutes later, the spike grew — same key, so the card revises:
await User.Inbox.push({
  title: `9 refunds on ${spike.sku} within the hour`,
  body: 'Still climbing. Draft response ready when you are.',
  key: `refund-spike-${spike.sku}`,
});
// → { outcome: 'updated' } — the card changed, the user was NOT re-notified
```

## Asking for a go-ahead

When the agent has done the work and needs one human click, push an approval — `approve` without options becomes an Approve / Decline pair, and the user's pick resolves the card:

```typescript theme={null}
await User.Inbox.push({
  title: 'Pause the checkout flow for SKU-2481?',
  body: 'The refund spike traces to a broken discount code. I can disable it now.',
  actions: ['approve'],
  priority: 'urgent',
  threadId: currentThreadId, // acting hands off into this conversation
});
```

Or offer real choices:

```typescript theme={null}
await User.Inbox.push({
  title: 'How should I handle the affected orders?',
  body: '12 orders hit the broken discount before I caught it.',
  options: [
    { label: 'Refund all 12', description: 'Full refunds, apology email' },
    { label: 'Honor the discount', description: 'Keep the orders, eat the margin' },
    { label: 'Ask me per order' },
  ],
});
```

## When the data source breaks

If the integration your monitor depends on disconnects, don't fail silently — push the fix. The card deep-links the user straight into reconnecting:

```typescript theme={null}
try {
  await fetchRefundData();
} catch (err) {
  if (isAuthError(err)) {
    await User.Inbox.push({
      title: 'Stripe disconnected',
      body: "Refund monitoring is blind until it's reconnected.",
      actions: ['fix'],
      connection: { type: 'stripe', name: 'Stripe' },
    });
    return { status: 'blocked on connection' };
  }
  throw err;
}
```

## Design notes

* **Key everything that recurs.** A monitor without a `key` mints a new card per run and burns its daily budget by lunch. With a stable key, the whole incident is ONE card that stays current.
* **Let `capped` be boring.** Five pushes per day per user per card class is the contract; your skill should have a summary-shaped fallback ready, not a retry loop.
* **Don't lean on `urgent`.** It's capped at 2/day (the excess lands as `high`), and inside the user's quiet hours even urgent sends **no notification at all** — the card still lands, and that's what you should count on.
* **Test in `lua dev`.** Pushes from dev runs land in your own Inbox, so you can watch the full loop — card, notification, revision, resolution — before your users ever do.
