car-integrations 0.6.0

OS-native account-bound integrations (Calendar, Contacts, Mail) for CAR
Documentation
//! Mail capability — enumerate accounts, summarize inbox, send/draft.
//!
//! Unlike Calendar + Contacts, Mail has no clean OS-native API on any
//! platform that CAR targets. Real implementations will be a mix of:
//!
//! - **protocol client** — IMAP + SMTP against accounts known to the OS
//!   (via [`car-accounts`](car_accounts)) with credentials from
//!   [`car-secrets`](car_secrets)
//! - **platform-specific inspection** — AppleScript on macOS for Mail.app,
//!   MAPI on Windows for Outlook, Evolution Data Server on Linux
//!
//! Side-effecting operations (send, draft, delete) should always be
//! approval-gated at the product layer — this crate exposes them but does
//! not enforce consent UX.

use serde::{Deserialize, Serialize};

use super::{Availability, IntegrationError};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MailAccount {
    pub id: String,
    pub address: String,
    pub display_name: Option<String>,
    pub provider_hint: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InboxSummary {
    pub account_id: String,
    pub unread: u32,
    pub total: u32,
    pub most_recent_subject: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccountListing {
    #[serde(flatten)]
    pub availability: Availability,
    pub accounts: Vec<MailAccount>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InboxListing {
    #[serde(flatten)]
    pub availability: Availability,
    pub summaries: Vec<InboxSummary>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SendRequest {
    pub account_id: String,
    pub to: Vec<String>,
    #[serde(default)]
    pub cc: Vec<String>,
    #[serde(default)]
    pub bcc: Vec<String>,
    pub subject: String,
    pub body: String,
    /// When `true`, just save the message as a draft — don't send.
    #[serde(default)]
    pub draft_only: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SendResult {
    #[serde(flatten)]
    pub availability: Availability,
    /// `true` if the message was actually sent or drafted.
    pub sent: bool,
    pub message_id: Option<String>,
}

pub fn list_accounts() -> Result<AccountListing, IntegrationError> {
    Ok(AccountListing {
        availability: current_backend_pending(),
        accounts: vec![],
    })
}

/// Short inbox summary per account. For accounts where the backend can't
/// reach the server, the summary is omitted.
pub fn list_inbox(_account_ids: &[String]) -> Result<InboxListing, IntegrationError> {
    Ok(InboxListing {
        availability: current_backend_pending(),
        summaries: vec![],
    })
}

/// Send or draft a message. Returns `sent=false` until backends land.
pub fn send(_req: SendRequest) -> Result<SendResult, IntegrationError> {
    Ok(SendResult {
        availability: current_backend_pending(),
        sent: false,
        message_id: None,
    })
}

fn current_backend_pending() -> Availability {
    Availability::pending(
        "imap_smtp",
        "Mail requires IMAP/SMTP client + per-OS Mail app inspection, \
         not yet wired. Depends on car-accounts (which accounts to use) \
         and car-secrets (credentials). API shape is stable.",
    )
}