Skip to main content

agentmail/client/
threads.rs

1use crate::client::NoBody;
2use crate::client::scope::{Scoped, Threads};
3use crate::{Error, types::*, util::urlish};
4
5impl<S: Threads> Scoped<'_, S> {
6    /// GET `{scope}/threads`, one page. Pass [`ThreadListFilters::default`] for
7    /// the first unfiltered page; carry `next_page_token` back in for more, or
8    /// use [`Scoped::list_all_threads`] to drain every page.
9    pub async fn list_threads(&self, filters: ThreadListFilters) -> Result<ThreadList, Error> {
10        self.client
11            .request(
12                reqwest::Method::GET,
13                &format!("{}/threads", self.base()),
14                &filters.query(),
15                None::<&NoBody>,
16            )
17            .await
18    }
19
20    /// Every thread matching `filters`, draining pagination.
21    pub async fn list_all_threads(
22        &self,
23        mut filters: ThreadListFilters,
24    ) -> Result<Vec<Thread>, Error> {
25        let mut out = Vec::new();
26        loop {
27            let page = self.list_threads(filters.clone()).await?;
28            let next = page.next_page_token;
29            out.extend(page.threads);
30            match next {
31                Some(token) => filters.page_token = Some(token),
32                None => return Ok(out),
33            }
34        }
35    }
36
37    /// GET `{scope}/threads/search`, full-text search (`q` required); matches
38    /// come back in [`Thread::highlights`].
39    pub async fn search_threads(
40        &self,
41        query: &str,
42        filters: ThreadListFilters,
43    ) -> Result<ThreadList, Error> {
44        let mut q = filters.query();
45        q.push(("q", query.to_string()));
46        self.client
47            .request(
48                reqwest::Method::GET,
49                &format!("{}/threads/search", self.base()),
50                &q,
51                None::<&NoBody>,
52            )
53            .await
54    }
55
56    /// Every thread matching a search, draining pagination.
57    pub async fn search_all_threads(
58        &self,
59        query: &str,
60        mut filters: ThreadListFilters,
61    ) -> Result<Vec<Thread>, Error> {
62        let mut out = Vec::new();
63        loop {
64            let page = self.search_threads(query, filters.clone()).await?;
65            let next = page.next_page_token;
66            out.extend(page.threads);
67            match next {
68                Some(token) => filters.page_token = Some(token),
69                None => return Ok(out),
70            }
71        }
72    }
73
74    /// GET `{scope}/threads/{thread_id}`, the full thread with its messages.
75    pub async fn get_thread(&self, thread_id: &str) -> Result<Thread, Error> {
76        self.client
77            .request(
78                reqwest::Method::GET,
79                &format!("{}/threads/{}", self.base(), urlish(thread_id)),
80                &[],
81                None::<&NoBody>,
82            )
83            .await
84    }
85
86    /// PATCH `{scope}/threads/{thread_id}`, add and/or remove labels across the
87    /// thread. Returns the thread id and its labels after the change.
88    pub async fn update_thread(
89        &self,
90        thread_id: &str,
91        update: UpdateThread,
92    ) -> Result<UpdatedThread, Error> {
93        self.client
94            .request(
95                reqwest::Method::PATCH,
96                &format!("{}/threads/{}", self.base(), urlish(thread_id)),
97                &[],
98                Some(&update),
99            )
100            .await
101    }
102
103    /// DELETE `{scope}/threads/{thread_id}`.
104    pub async fn delete_thread(&self, thread_id: &str) -> Result<(), Error> {
105        self.client
106            .request(
107                reqwest::Method::DELETE,
108                &format!("{}/threads/{}", self.base(), urlish(thread_id)),
109                &[],
110                None::<&NoBody>,
111            )
112            .await
113    }
114
115    /// GET `{scope}/threads/{thread_id}/attachments/{attachment_id}`, the
116    /// attachment metadata with a short-lived `download_url`; fetch the bytes
117    /// with [`Client::download_attachment`](crate::Client::download_attachment).
118    pub async fn get_thread_attachment(
119        &self,
120        thread_id: &str,
121        attachment_id: &str,
122    ) -> Result<Attachment, Error> {
123        self.client
124            .request(
125                reqwest::Method::GET,
126                &format!(
127                    "{}/threads/{}/attachments/{}",
128                    self.base(),
129                    urlish(thread_id),
130                    urlish(attachment_id),
131                ),
132                &[],
133                None::<&NoBody>,
134            )
135            .await
136    }
137}