Skip to main content

agentmail/types/
threads.rs

1use crate::util::QueryBuilder;
2use serde::{Deserialize, Serialize};
3
4use super::{Attachment, Message};
5
6/// A conversation thread. List items omit `messages`; the get-thread shape
7/// includes them and search results carry `highlights`; every optional field
8/// defaults so all three parse into this one type.
9#[derive(Clone, Debug, Deserialize)]
10pub struct Thread {
11    /// Unique thread id.
12    pub thread_id: String,
13    /// Inbox the thread belongs to.
14    #[serde(default)]
15    pub inbox_id: Option<String>,
16    /// Labels on the thread.
17    #[serde(default)]
18    pub labels: Vec<String>,
19    /// Timestamp of the most recent activity (RFC 3339).
20    #[serde(default)]
21    pub timestamp: Option<String>,
22    /// Timestamp of the most recent received message (RFC 3339).
23    #[serde(default)]
24    pub received_timestamp: Option<String>,
25    /// Timestamp of the most recent sent message (RFC 3339).
26    #[serde(default)]
27    pub sent_timestamp: Option<String>,
28    /// Distinct sender addresses across the thread.
29    #[serde(default)]
30    pub senders: Vec<String>,
31    /// Distinct recipient addresses across the thread.
32    #[serde(default)]
33    pub recipients: Vec<String>,
34    /// Subject line.
35    #[serde(default)]
36    pub subject: Option<String>,
37    /// Short preview of the latest message.
38    #[serde(default)]
39    pub preview: Option<String>,
40    /// Attachments across the thread.
41    #[serde(default)]
42    pub attachments: Vec<Attachment>,
43    /// Id of the most recent message.
44    #[serde(default)]
45    pub last_message_id: Option<String>,
46    /// Number of messages in the thread.
47    #[serde(default)]
48    pub message_count: Option<u64>,
49    /// Total size of the thread in bytes.
50    #[serde(default)]
51    pub size: Option<u64>,
52    /// Timestamp the thread was last updated (RFC 3339).
53    #[serde(default)]
54    pub updated_at: Option<String>,
55    /// Timestamp the thread was created (RFC 3339).
56    #[serde(default)]
57    pub created_at: Option<String>,
58    /// Full messages, present in get-thread responses.
59    #[serde(default)]
60    pub messages: Vec<Message>,
61    /// Search-highlight fragments, present in search responses.
62    #[serde(default)]
63    pub highlights: Option<serde_json::Value>,
64}
65
66/// One page of threads from a list or search call.
67#[derive(Clone, Debug, Deserialize)]
68pub struct ThreadList {
69    /// Total threads matching the query (not just this page).
70    pub count: u64,
71    /// This page of threads.
72    #[serde(default)]
73    pub threads: Vec<Thread>,
74    /// Cursor for the next page; `None` on the last page.
75    #[serde(default)]
76    pub next_page_token: Option<String>,
77}
78
79/// Request body for [`Client::update_thread`]: labels to add and/or remove.
80#[derive(Clone, Debug, Default, Serialize)]
81pub struct UpdateThread {
82    /// Labels to add.
83    #[serde(skip_serializing_if = "Vec::is_empty")]
84    pub add_labels: Vec<String>,
85    /// Labels to remove.
86    #[serde(skip_serializing_if = "Vec::is_empty")]
87    pub remove_labels: Vec<String>,
88}
89
90/// The API's response to [`Client::update_thread`]: the thread id and its
91/// labels after the update.
92#[derive(Clone, Debug, Deserialize)]
93pub struct UpdatedThread {
94    /// Id of the updated thread.
95    pub thread_id: String,
96    /// The thread's labels after the update.
97    #[serde(default)]
98    pub labels: Vec<String>,
99}
100
101/// Filters for [`Client::list_threads_filtered`] and
102/// [`Client::search_threads_page`]. Pagination fields (`limit`, `page_token`)
103/// share the same query-parameter namespace as the filters.
104#[derive(Clone, Debug, Default)]
105pub struct ThreadListFilters {
106    /// Maximum items per page.
107    pub limit: Option<u32>,
108    /// Cursor from a previous response's `next_page_token`.
109    pub page_token: Option<String>,
110    /// Filter by labels.
111    pub labels: Vec<String>,
112    /// Only threads before this timestamp (RFC 3339).
113    pub before: Option<String>,
114    /// Only threads after this timestamp (RFC 3339).
115    pub after: Option<String>,
116    /// Return oldest first instead of newest first.
117    pub ascending: Option<bool>,
118    /// Include spam threads.
119    pub include_spam: Option<bool>,
120    /// Include blocked threads.
121    pub include_blocked: Option<bool>,
122    /// Include unauthenticated threads.
123    pub include_unauthenticated: Option<bool>,
124    /// Include trashed threads.
125    pub include_trash: Option<bool>,
126    /// Filter by sender substring (repeatable).
127    pub senders: Vec<String>,
128    /// Filter by recipient substring (repeatable).
129    pub recipients: Vec<String>,
130    /// Filter by subject substring (repeatable).
131    pub subject: Vec<String>,
132}
133
134impl ThreadListFilters {
135    pub(crate) fn query(&self) -> Vec<(&'static str, String)> {
136        QueryBuilder::new()
137            .opt("limit", self.limit.as_ref())
138            .opt("page_token", self.page_token.as_ref())
139            .many("labels", &self.labels)
140            .opt("before", self.before.as_ref())
141            .opt("after", self.after.as_ref())
142            .opt("ascending", self.ascending.as_ref())
143            .opt("include_spam", self.include_spam.as_ref())
144            .opt("include_blocked", self.include_blocked.as_ref())
145            .opt(
146                "include_unauthenticated",
147                self.include_unauthenticated.as_ref(),
148            )
149            .opt("include_trash", self.include_trash.as_ref())
150            .many("senders", &self.senders)
151            .many("recipients", &self.recipients)
152            .many("subject", &self.subject)
153            .build()
154    }
155}