//! Contact side-effects of sending: create implicit contacts for recipients //! that don't already have one. Best-effort, never fails the send. use super::{AppState, DESKTOP_USER_ID}; /// Create implicit contacts for recipients that don't have an existing contact. /// Errors are logged and swallowed, never fails the send. pub(super) async fn create_implicit_contacts( state: &std::sync::Arc, to: &str, cc: Option<&str>, bcc: Option<&str>, sender_email: &str, ) -> Vec { use goingson_core::{NewContact, NewContactEmail}; use goingson_db_sqlite::utils::is_valid_email; use std::collections::HashSet; // Collect all unique recipient addresses let mut addresses = HashSet::new(); for field in [Some(to), cc, bcc].into_iter().flatten() { for addr in field.split(',').map(str::trim).filter(|a| !a.is_empty()) { let lower = addr.to_lowercase(); if lower != sender_email.to_lowercase() && is_valid_email(addr) { addresses.insert((addr.to_string(), lower)); } } } let mut new_contacts = Vec::new(); for (addr, _lower) in addresses { // Check if a contact already exists for this address match state.contacts.find_by_email(DESKTOP_USER_ID, &addr).await { Ok(Some(_)) => continue, // already exists Ok(None) => {} // proceed to create Err(e) => { tracing::warn!("Failed to check contact for {}: {}", addr, e); continue; } } // Derive display name from the local part of the email address let display_name = addr .split('@') .next() .unwrap_or(&addr) .replace(['.', '_'], " ") .split_whitespace() .map(|w| { let mut c = w.chars(); match c.next() { None => String::new(), Some(f) => f.to_uppercase().to_string() + c.as_str(), } }) .collect::>() .join(" "); let new_contact = NewContact { display_name, nickname: None, company: None, title: None, notes: String::new(), tags: vec![], birthday: None, timezone: None, is_implicit: true, }; match state.contacts.create(DESKTOP_USER_ID, new_contact).await { Ok(contact) => { let email_entry = NewContactEmail { address: addr.clone(), label: String::new(), is_primary: true, }; if let Err(e) = state .contacts .add_email(contact.id, DESKTOP_USER_ID, email_entry) .await { tracing::warn!("Failed to add email to implicit contact: {}", e); } // Re-fetch to get hydrated contact with email sub-collection match state.contacts.get_by_id(contact.id, DESKTOP_USER_ID).await { Ok(Some(c)) => new_contacts.push(crate::commands::ContactResponse::from(c)), _ => new_contacts.push(crate::commands::ContactResponse::from(contact)), } } Err(e) => { tracing::warn!("Failed to create implicit contact for {}: {}", addr, e); } } } new_contacts }