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

# Outreach Inbox

> Push a forwarded outreach message into the Outreach Agent's inbox via the REST API.

## Ingest a forwarded outreach message

`POST /api/v1/sites/{siteId}/outreach/inbox/messages`

Push a forwarded-email-style message into the Outreach Agent's inbox. This produces the exact same behavior as if a team member had forwarded a collab pitch to `forward+<token>@<MAILGUN_DOMAIN>`: Sight AI persists the inbound message, optionally runs the LLM extractor to pull out structured opportunities, creates a conversation, and dispatches the Outreach Agent to triage and reply.

Use this endpoint when your platform receives a collab pitch (or has a lead) and you want the Sight AI Outreach Agent to handle it end-to-end — triage, draft a reply, send it, and track the response.

### Required headers

| Header            | Required    | Description                                                                                                                                                             |
| ----------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Authorization`   | Yes         | `Bearer sai_<64hex>` — see [Authentication](/developers/authentication).                                                                                                |
| `Content-Type`    | Yes         | `application/json`.                                                                                                                                                     |
| `Idempotency-Key` | Recommended | A client-generated unique key. Repeat requests with the same key replay the cached response instead of re-running the ingestion. See [Idempotency](#idempotency) below. |

### Request body

| Field           | Type           | Required | Description                                                                                                                                                                                                                                         |
| --------------- | -------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `from`          | string (email) | Yes      | The prospect's email address — the person the agent should consider replying to.                                                                                                                                                                    |
| `fromName`      | string         | No       | Display name for the prospect. Rendered in the conversation's recipient field.                                                                                                                                                                      |
| `subject`       | string         | No       | Subject line of the forwarded message. Defaults to an empty string.                                                                                                                                                                                 |
| `bodyText`      | string         | No       | Plain-text body of the forwarded message. This is what the LLM extractor parses. Defaults to an empty string.                                                                                                                                       |
| `bodyHtml`      | string\|null   | No       | HTML body, if available. Stored for the inbox UI but not parsed by the extractor.                                                                                                                                                                   |
| `messageId`     | string\|null   | No       | RFC 5322 Message-Id of the original email. Used for threading when the prospect replies.                                                                                                                                                            |
| `inReplyTo`     | string\|null   | No       | In-Reply-To header, if this is a reply on an existing thread.                                                                                                                                                                                       |
| `references`    | string\|null   | No       | References header chain, for threading.                                                                                                                                                                                                             |
| `runExtraction` | boolean        | No       | When `true` (default), Sight AI runs the LLM extractor on the body to pull out structured opportunities (target URL, anchor, suggested sentence). When `false`, the message is stored without LLM cost and the agent triages via the reply handler. |

### Response

#### 201 Created (fresh ingestion)

```json theme={null}
{
  "ok": true,
  "siteId": "site_abc123",
  "conversationIds": ["conv_xyz"],
  "opportunityIds": ["opp_001"],
  "entrySource": "forwarded_actionable",
  "aiPaused": false
}
```

When the agent is paused for this conversation (noreply sender, agent inactive, or auto-reply disabled), the response is still 201 but with `aiPaused: true` and a `skipReason`:

```json theme={null}
{
  "ok": true,
  "siteId": "site_abc123",
  "conversationIds": ["conv_xyz"],
  "opportunityIds": ["opp_001"],
  "entrySource": "forwarded_unactionable",
  "aiPaused": true,
  "skipReason": "auto_reply_disabled"
}
```

#### 200 OK (idempotent replay)

Same body as the original 201 response, returned when the same `Idempotency-Key` is reused within the 24h retention window.

#### Error responses

| Status | Code                 | Meaning                                                                |
| ------ | -------------------- | ---------------------------------------------------------------------- |
| 400    | `VALIDATION_ERROR`   | Invalid request body (bad email, oversize body, etc.)                  |
| 401    | `UNAUTHORIZED`       | Missing or invalid API key                                             |
| 403    | `INSUFFICIENT_SCOPE` | Key lacks `inbox:write` scope                                          |
| 403    | `SITE_NOT_LICENSED`  | Site does not have an active license                                   |
| 404    | `SITE_NOT_FOUND`     | Site does not exist or isn't owned by the key's team                   |
| 402    | `SITE_NOT_LICENSED`  | Team subscription or site license problem (when `runExtraction: true`) |
| 429    | `RATE_LIMITED`       | Rate limit or per-site daily ingestion cap exceeded                    |
| 503    | —                    | Inngest dispatch failure — retry the request                           |

### `runExtraction` behavior

When `runExtraction: true` (the default):

* Sight AI runs a Claude Haiku call on the body to extract structured opportunities (target URL, anchor, suggested sentence, source URL).
* The billing gate runs first — the team must have a healthy subscription and the site must be licensed. A 402 response means the gate denied.
* The per-site daily extraction cap (default 100/day) applies. When over cap, the message is stored without extraction and the agent triages via the reply handler.

When `runExtraction: false`:

* No LLM cost. The message is stored as `direct_inbound_external` and the agent triages it via the reply handler's own LLM call (which has its own gate).
* Use this when your platform has already parsed the pitch into structured fields and you want the agent to "just handle it" without re-parsing.

### Side effects

A successful call creates:

* One `ai_visibility_outreach_opportunities` row per extracted ask (or one stub row when no ask is extracted).
* One `outreach_conversations` row per opportunity.
* One `outreach_messages` row (direction: `inbound`, sender\_type: `recipient`).
* An `outreach/reply.received` Inngest event (when the conversation isn't ai\_paused), which triggers the agent to triage + reply.

When outbound webhooks are enabled for the site, Sight AI also emits `outreach.opportunity_created` and `outreach.message_received` events to active subscribers. See [Webhooks](/developers/webhooks).

### Idempotency

Send an `Idempotency-Key` header to safely retry a failed request without creating duplicate conversations. The key can be any opaque string up to 128 characters (alphanumeric, `.`, `_`, `-`, `:`, `.`).

* First request with a given key: runs the ingestion pipeline, returns 201, caches the result.
* Repeat request with the same key within 24h: returns the cached response with 200 (no re-ingestion).
* The key is scoped to your API key — two different API keys can use the same key value independently.

If you omit the header, every call is treated as a fresh ingestion. Only omit it when you genuinely want a new conversation; always send it when retrying a failed or timed-out request.

### Example: push a forwarded collab pitch

```bash theme={null}
curl -s -X POST https://app.trysight.ai/api/v1/sites/site_abc123/outreach/inbox/messages \
  -H "Authorization: Bearer sai_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: pitch-2026-06-29-editor-example-com-001" \
  -d '{
    "from": "editor@example.com",
    "fromName": "Jane Editor",
    "subject": "Collab request for your SEO post",
    "bodyText": "Hi, I run example.com and loved your piece on X. Would you consider adding a link to our guide on Y? Happy to reciprocate.",
    "runExtraction": true
  }'
```

### Rate limits

Inherits the standard `write` rate tier (60 requests/minute per team). A per-site daily ingestion cap (default 500/day) also applies — see [Rate Limits](/developers/rate-limits).

<Note>
  The Outreach Agent must be **active** for the site (Inbox → Settings) and the site must have a verified sending domain for the agent to auto-reply. When the agent is inactive, messages still land in the inbox but the agent won't reply until the user turns it on.
</Note>
