SMS Enrichment
SMS enrichment analyzes a contact’s message history to extract profile data — name, email, company, sentiment, and more. It fills empty contact fields without overwriting user-entered data, then creates an AI Enrichment note and updates the contact memory.
How It Works
Section titled “How It Works”Trigger enrichment (individual or bulk) → Load all SMS messages for the contact → Format as chronological transcript (INBOUND/OUTBOUND labels) → Send to Gemini 3.1 Flash Lite via OpenRouter → Parse structured response (15 extracted fields) → Fill empty contact fields (never overwrite existing) → Create AiEnrichment note with extracted insights → Update contact memory with conversation analysisExtraction Schema
Section titled “Extraction Schema”The AI extracts a ConversationContactInfo struct with these fields:
| Field | Type | Fill Rule |
|---|---|---|
first_name | String | Only if contact’s first_name is empty |
last_name | String | Only if contact’s last_name is empty |
email | Option<String> | Only if contact has no existing emails |
company | Option<String> | Only if company is None |
job_title | Option<String> | Only if job_title is None |
preferred_language | Option<String> | Only if preferred_language is None |
timezone | Option<String> | Only if timezone is None |
gender | String | Only if gender is None and confidence is "high" |
pipeline_stage | String | Only if pipeline_stage is None; validated against: lead, prospect, qualified, customer, churned |
ai_summary | Option<String> | Always overwritten (derived AI field) |
interests | Vec<String> | Note only (not stored on contact) |
next_steps | Vec<String> | Note only |
key_dates | Vec<String> | Note only |
pain_points | Vec<String> | Note only |
sentiment | String | Note only ("positive", "neutral", "negative") |
Fields like interests, next_steps, key_dates, pain_points, and sentiment are written to the AiEnrichment note and contact memory but not stored as contact fields.
API Endpoints
Section titled “API Endpoints”Individual Enrichment
Section titled “Individual Enrichment”POST /api/contacts/{contact_id}/enrich-from-messagesEnriches a single contact. Any authenticated member can call this. Returns 200 OK on success. Fails with 400 if the contact has no messages with text content.
Service: enrich_contact_from_messages(contact_id, organization_id) in src/mods/contact/services/ai/enrich_contact_from_messages_service.rs
Bulk Enrichment
Section titled “Bulk Enrichment”POST /api/contacts/bulk-enrich-from-messagesEnriches multiple contacts in parallel. Owner or super admin only.
Request:
pub struct BulkEnrichRequest { pub contact_ids: Vec<Uuid>, // max 50 pub batch_id: Uuid, // client-generated, used to track progress}Response:
pub struct BulkEnrichResult { pub batch_id: Uuid, pub total: usize, pub succeeded: usize, pub failed: usize, pub failed_ids: Vec<Uuid>,}Validation: All contact IDs must belong to the caller’s organization. Maximum 50 contacts per request.
Concurrency: Processes up to 10 contacts simultaneously using futures_util::stream::buffer_unordered. Individual failures are caught and counted — they don’t abort the batch.
WebSocket Progress Events
Section titled “WebSocket Progress Events”During bulk enrichment, the server publishes a progress event after each contact completes:
Event type: contact.enrich.progress
pub struct BulkEnrichProgress { pub batch_id: Uuid, pub contact_id: Uuid, pub succeeded: bool, pub completed: usize, pub total: usize,}Events are published via event_hub().publish_org(org_id, ...) and delivered to all connected clients in the organization through the existing WebSocket infrastructure.
UI Components
Section titled “UI Components”ContactEnrichSection
Section titled “ContactEnrichSection”An “Enrich from SMS” button in the contact detail sidebar. Disabled when the contact has no messages.
#[component]pub fn ContactEnrichSection( contact_id: Uuid, has_messages: bool, on_enriched: EventHandler,) -> ElementLocated in src/mods/contact/components/ai/contact_enrich_section_component.rs.
BulkEnrichBar
Section titled “BulkEnrichBar”A floating bottom bar that appears when contacts are selected in the assignments view. Shows selection count → progress bar → completion summary.
#[component]pub fn BulkEnrichBar( selected_ids: Signal<Vec<Uuid>>, on_enriched: EventHandler,) -> ElementThe bar has three states:
- Idle — shows count and “Enrich from SMS” button
- Processing — shows a
ProgressBardriven by WebSocket events - Done — shows succeeded/failed counts with a dismiss button
Only renders for owners and super admins (matches the API restriction).
Located in src/mods/contact/components/ai/bulk_enrich_bar_component.rs.
ProgressBar
Section titled “ProgressBar”A reusable horizontal progress bar component:
#[component]pub fn ProgressBar( value: f64, // 0.0 to 1.0 #[props(default)] class: String, #[props(default)] label: Option<String>,) -> ElementLocated in src/ui/progress_bar_ui.rs.
Configuration
Section titled “Configuration”| Setting | Value |
|---|---|
| AI model | google/gemini-3.1-flash-lite-preview via OpenRouter |
| API key | openrouter_api_key in core_conf table |
| Bulk concurrency | 10 (hardcoded as BULK_ENRICH_CONCURRENCY) |
| Max batch size | 50 contacts |
| Message truncation | 500 chars per message body |
Side Effects
Section titled “Side Effects”Each enrichment creates two artifacts:
- AiEnrichment note — a user-visible note with
origin: Workflow,area: AiEnrichment, containing updated fields, summary, sentiment, interests, next steps, key dates, and pain points - Contact memory update — appends the full extraction to the contact’s system memory note via
update_contact_memory_after_call
Related
Section titled “Related”- Contact — parent module with call-based enrichment
- Contact Notes — where enrichment notes appear
- Contact Memory — updated after each enrichment
- Real-Time WebSocket — delivers bulk progress events