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.
| Header | Required | Description |
|---|
Authorization | Yes | Bearer sai_<64hex> — see 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 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)
{
"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:
{
"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 |
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.
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
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.
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.