Skip to main content

car_integrations/mail/
mod.rs

1//! Mail capability — enumerate accounts, summarize inbox, send/draft.
2//!
3//! Unlike Calendar + Contacts, Mail has no clean OS-native API on any
4//! platform that CAR targets. Real implementations will be a mix of:
5//!
6//! - **protocol client** — IMAP + SMTP against accounts known to the OS
7//!   (via [`car-accounts`](car_accounts)) with credentials from
8//!   [`car-secrets`](car_secrets)
9//! - **platform-specific inspection** — AppleScript on macOS for Mail.app,
10//!   MAPI on Windows for Outlook, Evolution Data Server on Linux
11//!
12//! Side-effecting operations (send, draft, delete) should always be
13//! approval-gated at the product layer — this crate exposes them but does
14//! not enforce consent UX.
15
16use serde::{Deserialize, Serialize};
17
18use super::{Availability, IntegrationError};
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct MailAccount {
22    pub id: String,
23    pub address: String,
24    pub display_name: Option<String>,
25    pub provider_hint: Option<String>,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct InboxSummary {
30    pub account_id: String,
31    pub unread: u32,
32    pub total: u32,
33    pub most_recent_subject: Option<String>,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct AccountListing {
38    #[serde(flatten)]
39    pub availability: Availability,
40    pub accounts: Vec<MailAccount>,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct InboxListing {
45    #[serde(flatten)]
46    pub availability: Availability,
47    pub summaries: Vec<InboxSummary>,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct SendRequest {
52    pub account_id: String,
53    pub to: Vec<String>,
54    #[serde(default)]
55    pub cc: Vec<String>,
56    #[serde(default)]
57    pub bcc: Vec<String>,
58    pub subject: String,
59    pub body: String,
60    /// When `true`, just save the message as a draft — don't send.
61    #[serde(default)]
62    pub draft_only: bool,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct SendResult {
67    #[serde(flatten)]
68    pub availability: Availability,
69    /// `true` if the message was actually sent or drafted.
70    pub sent: bool,
71    pub message_id: Option<String>,
72}
73
74pub fn list_accounts() -> Result<AccountListing, IntegrationError> {
75    Ok(AccountListing {
76        availability: current_backend_pending(),
77        accounts: vec![],
78    })
79}
80
81/// Short inbox summary per account. For accounts where the backend can't
82/// reach the server, the summary is omitted.
83pub fn list_inbox(_account_ids: &[String]) -> Result<InboxListing, IntegrationError> {
84    Ok(InboxListing {
85        availability: current_backend_pending(),
86        summaries: vec![],
87    })
88}
89
90/// Send or draft a message. Returns `sent=false` until backends land.
91pub fn send(_req: SendRequest) -> Result<SendResult, IntegrationError> {
92    Ok(SendResult {
93        availability: current_backend_pending(),
94        sent: false,
95        message_id: None,
96    })
97}
98
99fn current_backend_pending() -> Availability {
100    Availability::pending(
101        "imap_smtp",
102        "Mail requires IMAP/SMTP client + per-OS Mail app inspection, \
103         not yet wired. Depends on car-accounts (which accounts to use) \
104         and car-secrets (credentials). API shape is stable.",
105    )
106}