> ## 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.

# User.Inbox

> Approval, notice, and connection-fix cards filed in the current end user's inbox

`User.Inbox.push()` files a card in the current end user's inbox and returns a receipt at once; the end user acts on the card whenever they next open it. It takes no recipient. The card goes to the end user of the execution context, so it works in tools, dynamic jobs, and trigger-fired turns, and fails with `no_user_context` from a webhook's `execute` or a `LuaJob` (see [execution contexts](/concepts/execution-contexts)). To send a message instead of filing a card, use [`Channels`](/reference/sdk/channels).

*Verified against lua-cli 3.33.0.*

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

## Quick example

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

const receipt = await User.Inbox.push({
  title: 'Approve the Q3 renewal quote',
  body: 'Northstar Ltd renewal is ready to send at $48,000.',
  actions: ['approve'],
  key: 'northstar-q3-renewal',
});

if (receipt.outcome === 'capped') {
  console.warn(`Not filed: ${receipt.reason}`);
}
```

## Methods

### push()

Files a card, or revises the card that already carries the same `key`.

```ts theme={null}
User.Inbox.push(input: InboxPushInput): Promise<InboxPushReceipt>
```

<ParamField path="title" type="string" required>
  The card's first line. Trimmed; the first 140 characters are kept.
</ParamField>

<ParamField path="body" type="string" required>
  The card's context line and the prose of its detail pane. The first 1,000 characters are kept.
</ParamField>

<ParamField path="detail" type="string">
  Longer text shown only when the end user opens the card. The first 4,000 characters are kept.
</ParamField>

<ParamField path="deeplink" type="string">
  A source link rendered on the card and never opened automatically. Must start with `http://` or `https://`.
</ParamField>

<ParamField path="priority" type="'urgent' | 'high' | 'normal' | 'low'">
  How loudly the card announces itself. Urgent pushes are limited per day; see [Limits](#limits).
</ParamField>

<ParamField path="actions" type="('approve' | 'redirect' | 'fix')[]">
  What the card offers. `approve` without `options` synthesizes an Approve/Decline pair; `fix` requires `connection`.
</ParamField>

<ParamField path="options" type="{ label: string; description?: string }[]">
  One-click answers; two or more make the card a question. Labels keep their first 60 characters and descriptions their first 200. Entries with an empty label are ignored, and entries past the fourth are dropped.
</ParamField>

<ParamField path="connection" type="{ type: string; name: string }">
  Required with `actions: ['fix']`. `type` is the integration's catalog slug and `name` its display name; each keeps its first 60 characters.
</ParamField>

<ParamField path="key" type="string">
  Idempotency and revision key: 1 to 120 characters from `A-Z`, `a-z`, `0-9`, `.`, `_`, `:`, and `-`. A push with an existing key revises that card in place. Omit it for one-shot notices.
</ParamField>

<ParamField path="resolvesQuestion" type="boolean">
  On a notice push that carries a `key`, the literal `true` declares that this notice resolves the open question card with the same key; that card is settled and no further notification is sent. An answer the end user already gave stands. Ignored without `key`, and on question and fix pushes.
</ParamField>

<ParamField path="threadId" type="string">
  The conversation the card hands off into when the end user acts. Defaults to the current thread. The first 200 characters are kept.
</ParamField>

**Returns**

<ResponseField name="receipt" type="InboxPushReceipt">
  What happened to the push.

  <Expandable title="properties">
    <ResponseField name="outcome" type="'deposited' | 'updated' | 'exists' | 'capped'">
      `deposited` filed a card, `updated` revised the card with this `key`, `exists` found it unchanged, and `capped` filed nothing because a limit was reached.
    </ResponseField>

    <ResponseField name="kind" type="'input_request' | 'connection_fix' | 'agent_notice'">
      The card kind the push routed to. Reported on `capped` too.
    </ResponseField>

    <ResponseField name="key" type="string">
      The key the card is filed under: yours, or a generated one. Reuse it to revise the card.
    </ResponseField>

    <ResponseField name="reason" type="string">
      Present on `capped`: why nothing was filed.
    </ResponseField>
  </Expandable>
</ResponseField>

**Example**

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

const filed = await User.Inbox.push({
  key: 'weekly-pipeline-review',
  title: 'Pipeline review ready',
  body: '12 deals need a status update.',
});

const revised = await User.Inbox.push({
  key: 'weekly-pipeline-review',
  title: 'Pipeline review ready',
  body: '9 deals need a status update.',
});

console.log(filed.outcome, revised.outcome); // deposited updated
```

**Errors** — a reached limit resolves `capped`; everything else throws an error whose `code` you can branch on:

* `invalid_input` — `title and body are required`, `deeplink must be an http(s) URL`, `key must be 1..120 chars of [A-Za-z0-9._:-]`, or `fix` without a `connection`.
* `no_user_context` — `Inbox.push requires a user context`: the execution context has no end user.
* `deposit_failed` — the card could not be stored. Safe to retry.

## Card kinds

The kind follows from the input, checked in this order.

| Input                                                                        | Kind             | What the end user sees                      |
| ---------------------------------------------------------------------------- | ---------------- | ------------------------------------------- |
| `options` with two or more entries, or `actions` with `approve` and no `fix` | `input_request`  | A question answered in one click            |
| `actions` with `fix` and a `connection`                                      | `connection_fix` | A prompt to reconnect the named integration |
| Anything else, including `actions: ['redirect']`                             | `agent_notice`   | A read-only notice                          |

## Limits

| Limit                                                    | Default | When exceeded                                                            |
| -------------------------------------------------------- | ------- | ------------------------------------------------------------------------ |
| Cards per agent, per end user, per day, counted per kind | 500     | The push resolves `capped` and files nothing                             |
| `priority: 'urgent'` per agent, per end user, per day    | 5       | The card lands demoted to `high`                                         |
| Organization switch for agent pushes                     | On      | When an admin turns it off, every push resolves `capped` with a `reason` |

Defaults are platform settings and can change; the outcomes are the contract. When the platform cannot read the day's count, the push is treated as capped.

## Types

`InboxPushInput` and `InboxPushReceipt` are not exported. Derive them from the method when you need to name them.

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

type InboxPushInput = Parameters<typeof User.Inbox.push>[0];
type InboxPushReceipt = Awaited<ReturnType<typeof User.Inbox.push>>;

export async function notify(input: InboxPushInput): Promise<InboxPushReceipt> {
  return User.Inbox.push(input);
}
```

## See also

* [`User`](/reference/sdk/user) — the record and conversation behind the card
* [`Channels`](/reference/sdk/channels) — send a message instead of filing a card
* [`Jobs`](/reference/sdk/jobs) — dynamic jobs run as the end user who created them
* [Add human handoff](/build/add-human-handoff) — how-to
* [Approvals and signals](/build/workflows/approvals-and-signals) — approval steps inside a workflow
