Last Contacted Tracking
The last_contacted_at field on every contact record tracks when you last communicated — by message or call. It updates automatically and requires no manual input.
How It Works
Section titled “How It Works”Outbound SMS sent ─┐Inbound SMS received ─┤── tokio::spawn → update_contact_last_contacted(contact_id)Inbound call started ─┘ │ ▼ contact.last_contacted_at = NOW()Each trigger spawns an async task that updates the timestamp without blocking the main request. Errors are logged but never propagate — the primary operation (sending a message, receiving a call) always succeeds regardless of the tracking update.
Service
Section titled “Service”pub async fn update_contact_last_contacted( contact_id: Uuid,) -> Result<(), AppError>The service:
- Loads the contact by ID
- If not found (e.g. deleted between event and update), returns
Ok(())silently - Sets
last_contacted_attochrono::Utc::now().fixed_offset() - Saves the updated record
Trigger Points
Section titled “Trigger Points”The service is called via tokio::spawn in three places:
| Trigger | File | When |
|---|---|---|
| Outbound SMS | messaging/api/send_message_api.rs | After the message is persisted |
| Inbound SMS | twilio/api/twilio_events_api.rs | After the inbound message is saved |
| Inbound call | twilio/services/handle_twilio_stream_in_event_start.rs | After the call record is inserted |
Each call site follows the same pattern:
let contact_id_for_update = contact_id;tokio::spawn(async move { if let Err(e) = update_contact_last_contacted(contact_id_for_update).await { tracing::warn!(error = %e, "Failed to update last_contacted_at"); }});Contact Resolution
Section titled “Contact Resolution”When resolve_contact creates a new stub contact from an inbound call or message, it sets last_contacted_at to the current time immediately — so the contact is born with a valid timestamp.
Manually created contacts start with last_contacted_at: None until their first interaction.
Sorting
Section titled “Sorting”The contacts list endpoint supports sorting by last contacted date:
GET /api/contacts/list?sort=last_contacted_atThis sorts contacts by most recently contacted first, with contacts that have never been contacted (NULL) appearing last. The sort uses a composite database index on (organization_id, last_contacted_at DESC NULLS LAST) for fast org-scoped queries.
Migration
Section titled “Migration”Migration m20260302_000006_contact_add_last_contacted_at adds the column and backfills existing contacts:
- Adds
last_contacted_atas a nullableTIMESTAMP WITH TIME ZONEcolumn - Backfills the value using
GREATEST()across the most recent call and message timestamps for each contact - Creates the composite index
idx_contact_org_last_contacted