Skip to main content

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

HeaderRequiredDescription
AuthorizationYesBearer sai_<64hex> — see Authentication.
Content-TypeYesapplication/json.
Idempotency-KeyRecommendedA 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

FieldTypeRequiredDescription
fromstring (email)YesThe prospect’s email address — the person the agent should consider replying to.
fromNamestringNoDisplay name for the prospect. Rendered in the conversation’s recipient field.
subjectstringNoSubject line of the forwarded message. Defaults to an empty string.
bodyTextstringNoPlain-text body of the forwarded message. This is what the LLM extractor parses. Defaults to an empty string.
bodyHtmlstring|nullNoHTML body, if available. Stored for the inbox UI but not parsed by the extractor.
messageIdstring|nullNoRFC 5322 Message-Id of the original email. Used for threading when the prospect replies.
inReplyTostring|nullNoIn-Reply-To header, if this is a reply on an existing thread.
referencesstring|nullNoReferences header chain, for threading.
runExtractionbooleanNoWhen 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

StatusCodeMeaning
400VALIDATION_ERRORInvalid request body (bad email, oversize body, etc.)
401UNAUTHORIZEDMissing or invalid API key
403INSUFFICIENT_SCOPEKey lacks inbox:write scope
403SITE_NOT_LICENSEDSite does not have an active license
404SITE_NOT_FOUNDSite does not exist or isn’t owned by the key’s team
402SITE_NOT_LICENSEDTeam subscription or site license problem (when runExtraction: true)
429RATE_LIMITEDRate limit or per-site daily ingestion cap exceeded
503Inngest 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.

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.