Skip to main content

agentmail/client/
messages.rs

1use crate::client::NoBody;
2use crate::{Client, Error, Page, types::*, util::urlish};
3
4impl Client {
5    /// POST /v0/inboxes/{inbox_id}/messages/send
6    pub async fn send_message(
7        &self,
8        inbox_id: &str,
9        message: SendMessage,
10    ) -> Result<SentMessage, Error> {
11        self.request(
12            reqwest::Method::POST,
13            &format!("/v0/inboxes/{}/messages/send", urlish(inbox_id)),
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 [`Client::send_message`].
23    pub async fn send_text(
24        &self,
25        inbox_id: &str,
26        to: &str,
27        subject: &str,
28        text: &str,
29    ) -> Result<SentMessage, Error> {
30        self.send_message(
31            inbox_id,
32            SendMessage {
33                to: vec![to.to_string()],
34                subject: Some(subject.to_string()),
35                text: Some(text.to_string()),
36                ..Default::default()
37            },
38        )
39        .await
40    }
41
42    /// GET /v0/inboxes/{inbox_id}/messages (first page; see
43    /// [`Client::list_messages_page`]).
44    pub async fn list_messages(&self, inbox_id: &str) -> Result<MessageList, Error> {
45        self.list_messages_page(inbox_id, Page::default()).await
46    }
47
48    /// GET /v0/inboxes/{inbox_id}/messages with pagination. Feed
49    /// [`MessageList::next_page_token`] back in as [`Page::page_token`]
50    /// until it comes back `None`.
51    pub async fn list_messages_page(
52        &self,
53        inbox_id: &str,
54        page: Page,
55    ) -> Result<MessageList, Error> {
56        self.request(
57            reqwest::Method::GET,
58            &format!("/v0/inboxes/{}/messages", urlish(inbox_id)),
59            &page.query(),
60            None::<&NoBody>,
61        )
62        .await
63    }
64
65    /// GET /v0/inboxes/{inbox_id}/messages/{message_id}
66    pub async fn get_message(&self, inbox_id: &str, message_id: &str) -> Result<Message, Error> {
67        self.request(
68            reqwest::Method::GET,
69            &format!(
70                "/v0/inboxes/{}/messages/{}",
71                urlish(inbox_id),
72                urlish(message_id),
73            ),
74            &[],
75            None::<&NoBody>,
76        )
77        .await
78    }
79
80    /// POST /v0/inboxes/{inbox_id}/messages/{message_id}/reply -- reply to a
81    /// message. The `to` field is derived from the parent message; only
82    /// `text`/`html`/`cc`/`bcc` are caller-supplied.
83    pub async fn reply_to_message(
84        &self,
85        inbox_id: &str,
86        message_id: &str,
87        reply: ReplyToMessage,
88    ) -> Result<SentMessage, Error> {
89        self.request(
90            reqwest::Method::POST,
91            &format!(
92                "/v0/inboxes/{}/messages/{}/reply",
93                urlish(inbox_id),
94                urlish(message_id),
95            ),
96            &[],
97            Some(&reply),
98        )
99        .await
100    }
101
102    /// POST /v0/inboxes/{inbox_id}/messages/{message_id}/reply-all -- reply
103    /// to all recipients of a message.
104    pub async fn reply_all_to_message(
105        &self,
106        inbox_id: &str,
107        message_id: &str,
108        reply: ReplyToMessage,
109    ) -> Result<SentMessage, Error> {
110        self.request(
111            reqwest::Method::POST,
112            &format!(
113                "/v0/inboxes/{}/messages/{}/reply-all",
114                urlish(inbox_id),
115                urlish(message_id),
116            ),
117            &[],
118            Some(&reply),
119        )
120        .await
121    }
122
123    /// PATCH /v0/inboxes/{inbox_id}/messages/{message_id} -- update a
124    /// message's labels (read state is a label, e.g. add/remove `unread`).
125    /// Returns the message id and its labels after the change, not the full
126    /// message body.
127    pub async fn update_message(
128        &self,
129        inbox_id: &str,
130        message_id: &str,
131        update: UpdateMessage,
132    ) -> Result<UpdatedMessage, Error> {
133        self.request(
134            reqwest::Method::PATCH,
135            &format!(
136                "/v0/inboxes/{}/messages/{}",
137                urlish(inbox_id),
138                urlish(message_id),
139            ),
140            &[],
141            Some(&update),
142        )
143        .await
144    }
145
146    /// DELETE /v0/inboxes/{inbox_id}/messages/{message_id} -- permanently
147    /// deletes a message.
148    pub async fn delete_message(&self, inbox_id: &str, message_id: &str) -> Result<(), Error> {
149        self.request(
150            reqwest::Method::DELETE,
151            &format!(
152                "/v0/inboxes/{}/messages/{}",
153                urlish(inbox_id),
154                urlish(message_id),
155            ),
156            &[],
157            None::<&NoBody>,
158        )
159        .await
160    }
161
162    /// GET /v0/inboxes/{inbox_id}/messages with filters and pagination.
163    /// Pass [`MessageListFilters`] with any filter fields set to narrow
164    /// results. Feed [`MessageList::next_page_token`] back in as
165    /// [`MessageListFilters::page_token`] until it comes back `None`.
166    pub async fn list_messages_filtered(
167        &self,
168        inbox_id: &str,
169        filters: MessageListFilters,
170    ) -> Result<MessageList, Error> {
171        self.request(
172            reqwest::Method::GET,
173            &format!("/v0/inboxes/{}/messages", urlish(inbox_id)),
174            &filters.query(),
175            None::<&NoBody>,
176        )
177        .await
178    }
179
180    /// GET /v0/inboxes/{inbox_id}/messages/search with filters. Feed
181    /// [`MessageList::next_page_token`] back in as
182    /// [`MessageListFilters::page_token`] until it comes back `None`.
183    pub async fn search_messages(&self, inbox_id: &str, query: &str) -> Result<MessageList, Error> {
184        self.search_messages_page(inbox_id, query, MessageListFilters::default())
185            .await
186    }
187
188    /// GET /v0/inboxes/{inbox_id}/messages/search with pagination.
189    pub async fn search_messages_page(
190        &self,
191        inbox_id: &str,
192        query: &str,
193        filters: MessageListFilters,
194    ) -> Result<MessageList, Error> {
195        let mut q = filters.query();
196        q.push(("q", query.to_string()));
197        self.request(
198            reqwest::Method::GET,
199            &format!("/v0/inboxes/{}/messages/search", urlish(inbox_id)),
200            &q,
201            None::<&NoBody>,
202        )
203        .await
204    }
205
206    /// POST /v0/inboxes/{inbox_id}/messages/{message_id}/forward, forward a
207    /// message. Reuses [`SendMessage`] for the new recipients and any added
208    /// body.
209    pub async fn forward_message(
210        &self,
211        inbox_id: &str,
212        message_id: &str,
213        message: SendMessage,
214    ) -> Result<SentMessage, Error> {
215        self.request(
216            reqwest::Method::POST,
217            &format!(
218                "/v0/inboxes/{}/messages/{}/forward",
219                urlish(inbox_id),
220                urlish(message_id),
221            ),
222            &[],
223            Some(&message),
224        )
225        .await
226    }
227
228    /// GET /v0/inboxes/{inbox_id}/messages/{message_id}/raw, a presigned URL for
229    /// the message's raw RFC 822 (`.eml`) source. Fetch the bytes with
230    /// [`Client::download_raw`].
231    pub async fn get_raw_message(
232        &self,
233        inbox_id: &str,
234        message_id: &str,
235    ) -> Result<RawMessage, Error> {
236        self.request(
237            reqwest::Method::GET,
238            &format!(
239                "/v0/inboxes/{}/messages/{}/raw",
240                urlish(inbox_id),
241                urlish(message_id),
242            ),
243            &[],
244            None::<&NoBody>,
245        )
246        .await
247    }
248
249    /// Download the raw `.eml` bytes from a [`RawMessage`]'s presigned URL,
250    /// without the API bearer token (the URL is already authenticated).
251    pub async fn download_raw(&self, raw: &RawMessage) -> Result<Vec<u8>, Error> {
252        let resp = self.http.get(&raw.download_url).send().await?;
253        let status = resp.status();
254        let bytes = resp.bytes().await?;
255        if !status.is_success() {
256            return Err(Error::Api {
257                status,
258                body: String::from_utf8_lossy(&bytes).into_owned(),
259            });
260        }
261        Ok(bytes.to_vec())
262    }
263
264    /// POST /v0/inboxes/{inbox_id}/messages/batch-get, fetch many messages by id
265    /// in one call.
266    pub async fn batch_get_messages(
267        &self,
268        inbox_id: &str,
269        message_ids: Vec<String>,
270    ) -> Result<BatchGetMessagesResponse, Error> {
271        self.request(
272            reqwest::Method::POST,
273            &format!("/v0/inboxes/{}/messages/batch-get", urlish(inbox_id)),
274            &[],
275            Some(&BatchGetMessages { message_ids }),
276        )
277        .await
278    }
279
280    /// POST /v0/inboxes/{inbox_id}/messages/batch-update, apply the same label
281    /// changes to many messages at once.
282    pub async fn batch_update_messages(
283        &self,
284        inbox_id: &str,
285        update: BatchUpdateMessages,
286    ) -> Result<BatchUpdateMessagesResponse, Error> {
287        self.request(
288            reqwest::Method::POST,
289            &format!("/v0/inboxes/{}/messages/batch-update", urlish(inbox_id)),
290            &[],
291            Some(&update),
292        )
293        .await
294    }
295}