1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
4pub struct EmailMessageId(String);
5
6#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
7pub struct EmailFolder(String);
8
9#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
10pub struct EmailAddress(String);
11
12#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13pub struct EmailInboxQuery {
14 pub folder: EmailFolder,
15 pub limit: crate::ids::PageLimit,
16 pub offset: u32,
17 pub unread_only: bool,
18}
19
20#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
21pub struct EmailMarkReadRequest {
22 pub message_ids: Vec<EmailMessageId>,
23 pub is_read: bool,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
27pub struct SendEmailRequest {
28 pub to: Vec<EmailAddress>,
29 pub cc: Vec<EmailAddress>,
30 pub subject: String,
31 pub body_text: String,
32 pub body_html: Option<String>,
33}
34
35#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
36pub struct EmailAttachmentDownloadRequest {
37 pub message_id: EmailMessageId,
38 pub attachment_index: u32,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
42pub struct EmailNotificationQuery {
43 pub limit: crate::ids::PageLimit,
44}
45
46#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
47pub struct EmailAccount {
48 pub mailbox_address: Option<EmailAddress>,
49 pub display_name: Option<String>,
50 pub status: Option<String>,
51 pub attributes: Vec<EmailAttribute>,
52}
53
54#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
55pub struct EmailMessageSummary {
56 pub id: EmailMessageId,
57 pub folder: Option<EmailFolder>,
58 pub from: Vec<EmailAddress>,
59 pub to: Vec<EmailAddress>,
60 pub cc: Vec<EmailAddress>,
61 pub subject: String,
62 pub preview: Option<String>,
63 pub received_at: Option<String>,
64 pub sent_at: Option<String>,
65 pub unread: bool,
66 pub has_attachments: bool,
67 pub attachment_count: Option<u32>,
68 pub attributes: Vec<EmailAttribute>,
69}
70
71#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
72pub struct EmailMessage {
73 pub summary: EmailMessageSummary,
74 pub body_text: Option<String>,
75 pub body_html: Option<String>,
76 pub attachments: Vec<EmailAttachmentMetadata>,
77}
78
79#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
80pub struct EmailAttachmentMetadata {
81 pub index: u32,
82 pub filename: Option<String>,
83 pub content_type: Option<String>,
84 pub size: Option<u64>,
85}
86
87#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
88pub struct EmailAttachmentContent {
89 pub message_id: EmailMessageId,
90 pub attachment_index: u32,
91 pub filename: String,
92 pub content_type: String,
93 pub size: Option<u64>,
94 pub bytes: Vec<u8>,
95}
96
97#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
98pub struct EmailMarkReadResult {
99 pub updated: u32,
100}
101
102#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
103pub struct SendEmailResult {
104 pub accepted: bool,
105 pub message_id: Option<EmailMessageId>,
106 pub warnings: Vec<String>,
107}
108
109#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
110pub struct EmailNotification {
111 pub id: crate::ids::MessageId,
112 pub mailbox_address: Option<EmailAddress>,
113 pub from_addr: Option<String>,
114 pub subject: String,
115 pub preview: Option<String>,
116 pub has_attachments: bool,
117 pub received_at: Option<String>,
118 pub attributes: Vec<EmailAttribute>,
119}
120
121#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
122pub struct EmailAttribute {
123 pub key: String,
124 pub value: String,
125}
126
127impl EmailMessageId {
128 pub fn parse(input: impl AsRef<str>) -> crate::ImResult<Self> {
129 parse_non_empty("message_id", input).map(Self)
130 }
131
132 pub fn as_str(&self) -> &str {
133 &self.0
134 }
135}
136
137impl EmailFolder {
138 pub fn inbox() -> Self {
139 Self("inbox".to_string())
140 }
141
142 pub fn parse(input: impl AsRef<str>) -> crate::ImResult<Self> {
143 parse_non_empty("folder", input).map(Self)
144 }
145
146 pub fn as_str(&self) -> &str {
147 &self.0
148 }
149}
150
151impl EmailAddress {
152 pub fn parse(input: impl AsRef<str>) -> crate::ImResult<Self> {
153 let value = parse_non_empty("email_address", input)?;
154 if !value.contains('@') {
155 return Err(crate::ImError::invalid_input(
156 Some("email_address".to_string()),
157 "email address must contain @",
158 ));
159 }
160 Ok(Self(value))
161 }
162
163 pub fn as_str(&self) -> &str {
164 &self.0
165 }
166}
167
168impl Default for EmailInboxQuery {
169 fn default() -> Self {
170 Self {
171 folder: EmailFolder::inbox(),
172 limit: crate::ids::PageLimit(20),
173 offset: 0,
174 unread_only: false,
175 }
176 }
177}
178
179impl Default for EmailNotificationQuery {
180 fn default() -> Self {
181 Self {
182 limit: crate::ids::PageLimit(20),
183 }
184 }
185}
186
187fn parse_non_empty(field: &'static str, input: impl AsRef<str>) -> crate::ImResult<String> {
188 let value = input.as_ref().trim();
189 if value.is_empty() {
190 return Err(crate::ImError::invalid_input(
191 Some(field.to_string()),
192 format!("{field} must not be empty"),
193 ));
194 }
195 Ok(value.to_string())
196}