Skip to content

AI Intelligence Service

Cora uses an external Intelligence Service (at INTELLIGENT_SERVICE_URL) to power three AI-driven capabilities: reply generation, sentiment analysis, and voicemail generation. The service client lives in packages/lib/src/services/intelligence.service.ts.

Model

SettingValue
ModelGPT-4o-mini
ProviderVercel AI SDK (@ai-sdk/openai)
Output modegenerateObject() — structured JSON only, no streaming
Hosted bySeparate Intelligence Service (INTELLIGENT_SERVICE_URL)

Authentication

The Intelligence Service uses email/password auth with JWT tokens. The client handles token lifecycle transparently:

  • Tokens are cached in memory with a 1-minute expiry buffer
  • Falls back to DB (AppSettings) if no in-memory token
  • Auto-refreshes using the refresh token (5-minute buffer before expiry)

Features

1. Reply Generation

Generates a contextually appropriate email or SMS reply for a lead based on conversation history.

Endpoint: POST /intelligence/reply

Input:

chatHistory: [{ role: "agent" | "lead", message: string }]
leadId: string
messageType: "email" | "sms"
sentimentScore: number (0–1)
sender: { firstName, lastName }
campaignId, campaignType, leadType: "BUYER" | "SELLER" | "OWNER"

How chat history is built:

  • OUTBOUND messages in the DB → role: "agent"
  • INBOUND messages in the DB → role: "lead"
  • Empty messages are filtered out

Output: Suggested reply text, or null if insufficient context.

Used in: Conversation inbox — shown as an AI-suggested draft the agent can edit before sending.


2. Sentiment Analysis

Classifies how a lead responded — crucial for deciding whether to continue or stop outreach.

Two-tier approach:

Output schema:

typescript
{
  score: -1 | -0.5 | 0 | 0.5 | 1,
  sentiment: "very negative" | "negative" | "neutral" | "positive" | "very positive",
  optout: boolean,
  explanation: string
}

Score meanings:

ScoreSentimentExample
1Very positive"Yes I'm interested, tell me more"
0.5Positive"Maybe, what's the timeline?"
0Neutral"I'll think about it"
-0.5Negative"Not right now, too busy"
-1Very negative"Stop texting me" / opt-out

Key rules from the system prompt:

  • Polite refusals ("I'm not interested") → negative, not neutral
  • Factual statements that end eligibility ("I already sold") → negative
  • Future openness ("maybe next year") → positive
  • Opt-outs are hard-coded via keyword matching (fast path) — never sent to the LLM
  • Unrelated sentiment (e.g., "I'm having a bad day") is ignored when scoring

Used in: Automatically applied to every inbound reply. Score stored on CampaignLeads. High negative scores suppress further outreach.


3. Double Tap (Bulk Follow-up Generation)

Generates follow-up SMS messages for multiple leads in a single call — used for "double tap" sequences where a follow-up is sent if no reply was received.

Endpoint: POST /intelligence/double-tap

Input: Array of leads, each with:

  • Chat history (same format as reply generation)
  • Sentiment score
  • Lead stage

Output: Array of follow-up messages, one per lead.

Used in: The worker cron job that identifies leads past their follow-up window.


4. Voicemail Generation

Generates voicemail copy and converts it to audio (stored in S3).

Endpoint: POST /intelligence/voicemail

Input:

lead: { firstName, lastName, phone }
brand: { name, personality, voice }
campaign: { type, leadType }
customContent?: string  (optional override)

Output:

  • Generated voicemail script
  • S3 URL of the audio file

Used in: Campaign steps of type VOICEMAIL — the worker fetches the generated audio and delivers it via Twilio voicemail drop.


Integration with Campaign Execution

The Intelligence Service is called at different points in the campaign pipeline:

Configuration

VariableDescription
INTELLIGENT_SERVICE_URLBase URL of the Intelligence Service
ENRICHMENT_ADMIN_EMAILAuth credential (shared with enrichment service)
ENRICHMENT_ADMIN_PASSWORDAuth credential
OPENAI_API_KEYUsed directly for local generateObject() calls (sentiment)