Skip to main content

agentmail/client/
attachments.rs

1use crate::client::NoBody;
2use crate::{Client, Error, types::*, util::urlish};
3
4impl Client {
5    /// GET /v0/inboxes/{inbox_id}/messages/{message_id}/attachments/{attachment_id}.
6    /// The returned [`Attachment`] carries a short-lived `download_url`; pass it
7    /// to [`Client::download_attachment`] to fetch the bytes.
8    pub async fn get_message_attachment(
9        &self,
10        inbox_id: &str,
11        message_id: &str,
12        attachment_id: &str,
13    ) -> Result<Attachment, Error> {
14        self.request(
15            reqwest::Method::GET,
16            &format!(
17                "/v0/inboxes/{}/messages/{}/attachments/{}",
18                urlish(inbox_id),
19                urlish(message_id),
20                urlish(attachment_id),
21            ),
22            &[],
23            None::<&NoBody>,
24        )
25        .await
26    }
27
28    /// GET /v0/inboxes/{inbox_id}/threads/{thread_id}/attachments/{attachment_id}.
29    pub async fn get_thread_attachment(
30        &self,
31        inbox_id: &str,
32        thread_id: &str,
33        attachment_id: &str,
34    ) -> Result<Attachment, Error> {
35        self.request(
36            reqwest::Method::GET,
37            &format!(
38                "/v0/inboxes/{}/threads/{}/attachments/{}",
39                urlish(inbox_id),
40                urlish(thread_id),
41                urlish(attachment_id),
42            ),
43            &[],
44            None::<&NoBody>,
45        )
46        .await
47    }
48
49    /// GET /v0/inboxes/{inbox_id}/drafts/{draft_id}/attachments/{attachment_id}.
50    pub async fn get_draft_attachment(
51        &self,
52        inbox_id: &str,
53        draft_id: &str,
54        attachment_id: &str,
55    ) -> Result<Attachment, Error> {
56        self.request(
57            reqwest::Method::GET,
58            &format!(
59                "/v0/inboxes/{}/drafts/{}/attachments/{}",
60                urlish(inbox_id),
61                urlish(draft_id),
62                urlish(attachment_id),
63            ),
64            &[],
65            None::<&NoBody>,
66        )
67        .await
68    }
69
70    /// Download an attachment's bytes from its presigned `download_url`. The URL
71    /// is a short-lived S3 link fetched without the API bearer token, so obtain
72    /// the [`Attachment`] from one of the `get_*_attachment` calls first (a
73    /// plain response attachment carries no `download_url` and yields
74    /// [`Error::NoDownloadUrl`]).
75    pub async fn download_attachment(&self, attachment: &Attachment) -> Result<Vec<u8>, Error> {
76        let url = attachment
77            .download_url
78            .as_deref()
79            .ok_or(Error::NoDownloadUrl)?;
80        // No bearer_auth: download_url is already an authenticated presigned URL.
81        let resp = self.http.get(url).send().await?;
82        let status = resp.status();
83        let bytes = resp.bytes().await?;
84        if !status.is_success() {
85            return Err(Error::Api {
86                status,
87                body: String::from_utf8_lossy(&bytes).into_owned(),
88            });
89        }
90        Ok(bytes.to_vec())
91    }
92}