Skip to main content

agentmail/client/
messages.rs

1use crate::client::NoBody;
2use crate::client::scope::{InboxScope, Scoped};
3use crate::{Error, types::*, util::urlish};
4
5/// Messages exist only within an inbox, so these methods live on
6/// `client.inbox(id)`.
7impl Scoped<'_, InboxScope<'_>> {
8    /// POST `/v0/inboxes/{inbox_id}/messages/send`.
9    pub async fn send_message(&self, message: SendMessage) -> Result<SentMessage, Error> {
10        self.client
11            .request(
12                reqwest::Method::POST,
13                &format!("{}/messages/send", self.base()),
14                &[],
15                Some(&message),
16            )
17            .await
18    }
19
20    /// Send a plain-text message to a single recipient: the common case, in one
21    /// line. For anything richer (HTML, cc/bcc, attachments, labels), build a
22    /// [`SendMessage`] and call [`Scoped::send_message`].
23    pub async fn send_text(
24        &self,
25        to: &str,
26        subject: &str,
27        text: &str,
28    ) -> Result<SentMessage, Error> {
29        self.send_message(SendMessage {
30            to: vec![to.to_string()],
31            subject: Some(subject.to_string()),
32            text: Some(text.to_string()),
33            ..Default::default()
34        })
35        .await
36    }
37
38    /// GET `/v0/inboxes/{inbox_id}/messages`, one page. Pass
39    /// [`MessageListFilters::default`] for the first unfiltered page.
40    pub async fn list_messages(&self, filters: MessageListFilters) -> Result<MessageList, Error> {
41        self.client
42            .request(
43                reqwest::Method::GET,
44                &format!("{}/messages", self.base()),
45                &filters.query(),
46                None::<&NoBody>,
47            )
48            .await
49    }
50
51    /// Every message matching `filters`, draining pagination.
52    pub async fn list_all_messages(
53        &self,
54        mut filters: MessageListFilters,
55    ) -> Result<Vec<Message>, Error> {
56        let mut out = Vec::new();
57        loop {
58            let page = self.list_messages(filters.clone()).await?;
59            let next = page.next_page_token;
60            out.extend(page.messages);
61            match next {
62                Some(token) => filters.page_token = Some(token),
63                None => return Ok(out),
64            }
65        }
66    }
67
68    /// GET `/v0/inboxes/{inbox_id}/messages/search` (`q` required).
69    pub async fn search_messages(
70        &self,
71        query: &str,
72        filters: MessageListFilters,
73    ) -> Result<MessageList, Error> {
74        let mut q = filters.query();
75        q.push(("q", query.to_string()));
76        self.client
77            .request(
78                reqwest::Method::GET,
79                &format!("{}/messages/search", self.base()),
80                &q,
81                None::<&NoBody>,
82            )
83            .await
84    }
85
86    /// Every message matching a search, draining pagination.
87    pub async fn search_all_messages(
88        &self,
89        query: &str,
90        mut filters: MessageListFilters,
91    ) -> Result<Vec<Message>, Error> {
92        let mut out = Vec::new();
93        loop {
94            let page = self.search_messages(query, filters.clone()).await?;
95            let next = page.next_page_token;
96            out.extend(page.messages);
97            match next {
98                Some(token) => filters.page_token = Some(token),
99                None => return Ok(out),
100            }
101        }
102    }
103
104    /// GET `/v0/inboxes/{inbox_id}/messages/{message_id}`.
105    pub async fn get_message(&self, message_id: &str) -> Result<Message, Error> {
106        self.client
107            .request(
108                reqwest::Method::GET,
109                &format!("{}/messages/{}", self.base(), urlish(message_id)),
110                &[],
111                None::<&NoBody>,
112            )
113            .await
114    }
115
116    /// PATCH `/v0/inboxes/{inbox_id}/messages/{message_id}`, add/remove labels
117    /// (read state is a label). Returns the id and labels after the change.
118    pub async fn update_message(
119        &self,
120        message_id: &str,
121        update: UpdateMessage,
122    ) -> Result<UpdatedMessage, Error> {
123        self.client
124            .request(
125                reqwest::Method::PATCH,
126                &format!("{}/messages/{}", self.base(), urlish(message_id)),
127                &[],
128                Some(&update),
129            )
130            .await
131    }
132
133    /// DELETE `/v0/inboxes/{inbox_id}/messages/{message_id}`.
134    pub async fn delete_message(&self, message_id: &str) -> Result<(), Error> {
135        self.client
136            .request(
137                reqwest::Method::DELETE,
138                &format!("{}/messages/{}", self.base(), urlish(message_id)),
139                &[],
140                None::<&NoBody>,
141            )
142            .await
143    }
144
145    /// POST `/v0/inboxes/{inbox_id}/messages/{message_id}/reply`, reply to a
146    /// message (recipients derived from the parent).
147    pub async fn reply_to_message(
148        &self,
149        message_id: &str,
150        reply: ReplyToMessage,
151    ) -> Result<SentMessage, Error> {
152        self.client
153            .request(
154                reqwest::Method::POST,
155                &format!("{}/messages/{}/reply", self.base(), urlish(message_id)),
156                &[],
157                Some(&reply),
158            )
159            .await
160    }
161
162    /// POST `/v0/inboxes/{inbox_id}/messages/{message_id}/reply-all`.
163    pub async fn reply_all_to_message(
164        &self,
165        message_id: &str,
166        reply: ReplyToMessage,
167    ) -> Result<SentMessage, Error> {
168        self.client
169            .request(
170                reqwest::Method::POST,
171                &format!("{}/messages/{}/reply-all", self.base(), urlish(message_id)),
172                &[],
173                Some(&reply),
174            )
175            .await
176    }
177
178    /// POST `/v0/inboxes/{inbox_id}/messages/{message_id}/forward`. Reuses
179    /// [`SendMessage`] for the new recipients and any added body.
180    pub async fn forward_message(
181        &self,
182        message_id: &str,
183        message: SendMessage,
184    ) -> Result<SentMessage, Error> {
185        self.client
186            .request(
187                reqwest::Method::POST,
188                &format!("{}/messages/{}/forward", self.base(), urlish(message_id)),
189                &[],
190                Some(&message),
191            )
192            .await
193    }
194
195    /// GET `/v0/inboxes/{inbox_id}/messages/{message_id}/raw`, a presigned URL
196    /// for the raw RFC 822 (`.eml`) source; fetch the bytes with
197    /// [`Client::download_raw`](crate::Client::download_raw).
198    pub async fn get_raw_message(&self, message_id: &str) -> Result<RawMessage, Error> {
199        self.client
200            .request(
201                reqwest::Method::GET,
202                &format!("{}/messages/{}/raw", self.base(), urlish(message_id)),
203                &[],
204                None::<&NoBody>,
205            )
206            .await
207    }
208
209    /// GET `/v0/inboxes/{inbox_id}/messages/{message_id}/attachments/{attachment_id}`.
210    pub async fn get_message_attachment(
211        &self,
212        message_id: &str,
213        attachment_id: &str,
214    ) -> Result<Attachment, Error> {
215        self.client
216            .request(
217                reqwest::Method::GET,
218                &format!(
219                    "{}/messages/{}/attachments/{}",
220                    self.base(),
221                    urlish(message_id),
222                    urlish(attachment_id),
223                ),
224                &[],
225                None::<&NoBody>,
226            )
227            .await
228    }
229
230    /// POST `/v0/inboxes/{inbox_id}/messages/batch-get`, fetch many messages by
231    /// id in one call.
232    pub async fn batch_get_messages(
233        &self,
234        message_ids: Vec<String>,
235    ) -> Result<BatchGetMessagesResponse, Error> {
236        self.client
237            .request(
238                reqwest::Method::POST,
239                &format!("{}/messages/batch-get", self.base()),
240                &[],
241                Some(&BatchGetMessages { message_ids }),
242            )
243            .await
244    }
245
246    /// POST `/v0/inboxes/{inbox_id}/messages/batch-update`, apply the same label
247    /// changes to many messages at once.
248    pub async fn batch_update_messages(
249        &self,
250        update: BatchUpdateMessages,
251    ) -> Result<BatchUpdateMessagesResponse, Error> {
252        self.client
253            .request(
254                reqwest::Method::POST,
255                &format!("{}/messages/batch-update", self.base()),
256                &[],
257                Some(&update),
258            )
259            .await
260    }
261}