Fresh Contacts Tool
The get_fresh_contacts tool lets users ask the assistant for contacts that have never received an outbound message from the organization. It helps sales teams identify leads needing initial outreach.
How It Works
Section titled “How It Works”When a user asks something like “Show me contacts I haven’t reached out to yet”, the assistant invokes get_fresh_contacts. The tool:
- Checks
Message::Collection::Listpermission - Parses the time range (defaults to
all_time) - Queries contacts with zero outbound messages using a
NOT EXISTSsubquery - Batch-fetches preferred phone numbers
- Formats timestamps in the organization’s timezone
- Injects deep links so each result is clickable
Input Parameters
Section titled “Input Parameters”{ time_range?: "today" | "yesterday" | "last_3_days" | "last_7_days" | "last_14_days" | "last_30_days" | "last_90_days" | "all_time" // Filters by contact creation date. Defaults to "all_time".}Response Shape
Section titled “Response Shape”{ contacts: [ { contact_id: "uuid", first_name: "Jane", last_name: "Doe", contact_phone: "+15551234567", // preferred phone, or null created_at: "2026-04-01 09:30 AM", // formatted in org timezone deep_link: "/contacts/{id}" } ], count: 25, // items on this page total_count: 142 // total matching contacts}Permission Model
Section titled “Permission Model”The tool uses two-layer permission enforcement:
| Layer | Permission | Effect |
|---|---|---|
| Registry | Message::Collection::List | Controls whether the tool appears |
| Service | Contact::Collection::List | Full org access |
| Service | Contact::Collection::ListAssigned | Scoped to assigned contacts only |
Users with ListAssigned only see fresh contacts assigned to them. The service adds a member-scoping filter automatically.
Query Strategy
Section titled “Query Strategy”The service uses raw SQL with a NOT EXISTS pattern because SeaORM cannot express subquery existence checks:
SELECT c.id, c.first_name, c.last_name, c.created_at, COUNT(*) OVER() AS total_countFROM contact cWHERE c.organization_id = $1 AND NOT EXISTS ( SELECT 1 FROM message m WHERE m.contact_id = c.id AND m.direction = 'outbound' )ORDER BY c.created_at DESCLIMIT $2 OFFSET $3Optional WHERE clauses are added for time range filtering (c.created_at >= $start) and member scoping (c.id IN (SELECT contact_id FROM contact_user WHERE member_id = $member)).
Phone numbers are batch-fetched in a single query to avoid N+1 issues, with is_preferred = true phones taking priority.
Shared Time Range Parser
Section titled “Shared Time Range Parser”PR #766 also extracted parse_assistant_time_range() into tools/time_range.rs as a shared utility. Both get_fresh_contacts and get_unanswered_contacts use it.
pub(crate) fn parse_assistant_time_range( value: &str,) -> (Option<NaiveDateTime>, Option<NaiveDateTime>)The function returns (start, end) as UTC midnight boundaries. "all_time" returns (None, None).
Key Files
Section titled “Key Files”| File | Purpose |
|---|---|
assistant/tools/messages/ai_get_fresh_contacts_tool.rs | Tool definition and handler |
messaging/services/get_fresh_contacts_service.rs | Service with raw SQL query |
messaging/types/fresh_contact_type.rs | FreshContactsFilter, FreshContact, FreshContactsResponse |
assistant/tools/time_range.rs | Shared parse_assistant_time_range() utility |
assistant/types/assistant_tool_name_type.rs | GetFreshContacts enum variant |