1use std::sync::Arc;
2
3use reqwest::Method;
4
5use crate::{
6 Config, Result,
7 list_opts::{ListOptions, ListResponse},
8 types::{InboundAttachment, InboundEmail},
9};
10
11#[derive(Clone, Debug)]
13pub struct ReceivingSvc(pub(crate) Arc<Config>);
14
15impl ReceivingSvc {
16 #[maybe_async::maybe_async]
20 pub async fn get(&self, email_id: &str) -> Result<InboundEmail> {
21 let path = format!("/emails/receiving/{email_id}");
22
23 let request = self.0.build(Method::GET, &path);
24 let response = self.0.send(request).await?;
25 let content = response.json::<InboundEmail>().await?;
26
27 Ok(content)
28 }
29
30 #[maybe_async::maybe_async]
34 #[allow(clippy::needless_pass_by_value)]
35 pub async fn list<T>(&self, list_opts: ListOptions<T>) -> Result<ListResponse<InboundEmail>> {
36 let request = self
37 .0
38 .build(Method::GET, "/emails/receiving")
39 .query(&list_opts);
40 let response = self.0.send(request).await?;
41 let content = response.json::<ListResponse<InboundEmail>>().await?;
42
43 Ok(content)
44 }
45
46 #[maybe_async::maybe_async]
50 pub async fn get_attachment(
51 &self,
52 attachment_id: &str,
53 email_id: &str,
54 ) -> Result<InboundAttachment> {
55 let path = format!("/emails/receiving/{email_id}/attachments/{attachment_id}");
56
57 let request = self.0.build(Method::GET, &path);
58 let response = self.0.send(request).await?;
59 let content = response.json::<InboundAttachment>().await?;
60
61 Ok(content)
62 }
63
64 #[maybe_async::maybe_async]
68 #[allow(clippy::needless_pass_by_value)]
69 pub async fn list_attachments<T>(
70 &self,
71 email_id: &str,
72 list_opts: ListOptions<T>,
73 ) -> Result<ListResponse<InboundAttachment>> {
74 let path = format!("/emails/receiving/{email_id}/attachments");
75
76 let request = self.0.build(Method::GET, &path).query(&list_opts);
77 let response = self.0.send(request).await?;
78 let content = response.json::<ListResponse<InboundAttachment>>().await?;
79
80 Ok(content)
81 }
82}
83
84#[allow(unreachable_pub)]
85pub mod types {
86 use std::collections::HashMap;
87
88 use serde::Deserialize;
89
90 crate::define_id_type!(InboundEmailId);
91 crate::define_id_type!(InboundAttatchmentId);
92
93 #[must_use]
94 #[derive(Debug, Clone, Deserialize)]
95 pub struct InboundEmail {
96 pub id: InboundEmailId,
97 pub to: Vec<String>,
98 pub from: String,
99 pub created_at: String,
100 pub subject: String,
101 #[serde(default)]
102 pub bcc: Vec<String>,
103 #[serde(default)]
104 pub cc: Vec<String>,
105 #[serde(default)]
106 pub reply_to: Vec<String>,
107 pub html: Option<String>,
108 pub text: Option<String>,
109 #[serde(default)]
110 pub headers: HashMap<String, String>,
111 pub message_id: String,
112 #[serde(default)]
113 pub attachments: Vec<InboundAttachment>,
114 }
115
116 #[must_use]
117 #[derive(Debug, Clone, Deserialize)]
118 pub struct InboundAttachment {
119 pub id: InboundAttatchmentId,
120 pub filename: String,
121 pub content_type: String,
122 pub content_id: Option<String>,
123 pub content_disposition: String,
124 pub size: u32,
125 }
126}
127
128#[cfg(test)]
129#[allow(clippy::unwrap_used)]
130#[allow(clippy::needless_return)]
131mod test {
132 use crate::{list_opts::ListOptions, types::InboundEmail};
133 use crate::{
134 list_opts::ListResponse,
135 test::{CLIENT, DebugResult},
136 };
137
138 #[ignore = "At the moment, we can't programmatically send inbound emails and since said inbound emails are only retained for 2 weeks, this cannot be automatically tested."]
139 #[tokio_shared_rt::test(shared = true)]
140 #[cfg(not(feature = "blocking"))]
141 async fn all() -> DebugResult<()> {
142 let resend = &*CLIENT;
143
144 let emails = resend.receiving.list(ListOptions::default()).await?;
147
148 let email_id = &emails.data.first().unwrap().id;
149
150 let _email = resend.receiving.get(email_id).await?;
151
152 let attachments = resend
153 .receiving
154 .list_attachments(email_id, ListOptions::default())
155 .await?;
156
157 let attachment_id = &attachments.data.first().unwrap().id;
158
159 let _attachment = resend
160 .receiving
161 .get_attachment(attachment_id, email_id)
162 .await?;
163
164 Ok(())
165 }
166
167 #[test]
168 fn deserialize_test() {
169 let emails = r#"{
170 "object": "list",
171 "has_more": true,
172 "data": [
173 {
174 "id": "a39999a6-88e3-48b1-888b-beaabcde1b33",
175 "to": ["recipient@example.com"],
176 "from": "sender@example.com",
177 "created_at": "2025-10-09 14:37:40.951732+00",
178 "subject": "Hello World",
179 "bcc": [],
180 "cc": [],
181 "reply_to": [],
182 "message_id": "<111-222-333@email.provider.example.com>",
183 "attachments": [
184 {
185 "filename": "example.txt",
186 "content_type": "text/plain",
187 "content_id": null,
188 "content_disposition": "attachment",
189 "id": "47e999c7-c89c-4999-bf32-aaaaa1c3ff21",
190 "size": 13
191 }
192 ]
193 }
194 ]
195}"#;
196
197 let res = serde_json::from_str::<ListResponse<InboundEmail>>(emails);
198 assert!(res.is_ok());
199 }
200}