nylas_types/
message.rs

1//! Message types for the Nylas API v3.
2
3use serde::{Deserialize, Serialize};
4
5use crate::{Attachment, EmailAddress, FolderId, GrantId, MessageId, ThreadId};
6
7/// A message object from the Nylas API.
8///
9/// Messages are email messages in a user's mailbox.
10///
11/// # Example
12///
13/// ```
14/// # use nylas_types::{Message, MessageId, GrantId, ThreadId};
15/// let message = Message {
16///     id: MessageId::new("msg_123"),
17///     grant_id: GrantId::new("grant_123"),
18///     thread_id: Some(ThreadId::new("thread_123")),
19///     subject: Some("Hello".to_string()),
20///     from: vec![],
21///     to: vec![],
22///     cc: vec![],
23///     bcc: vec![],
24///     reply_to: vec![],
25///     date: 1234567890,
26///     unread: Some(false),
27///     starred: Some(false),
28///     snippet: Some("Message snippet...".to_string()),
29///     body: Some("Message body".to_string()),
30///     attachments: vec![],
31///     folders: vec![],
32///     created_at: Some(1234567890),
33/// };
34/// ```
35#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
36pub struct Message {
37    /// Unique identifier for the message.
38    pub id: MessageId,
39
40    /// Grant ID associated with this message.
41    pub grant_id: GrantId,
42
43    /// Thread ID this message belongs to.
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub thread_id: Option<ThreadId>,
46
47    /// Subject line of the message.
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub subject: Option<String>,
50
51    /// Sender email address(es).
52    #[serde(default)]
53    pub from: Vec<EmailAddress>,
54
55    /// Recipient email address(es).
56    #[serde(default)]
57    pub to: Vec<EmailAddress>,
58
59    /// CC recipient email address(es).
60    #[serde(default)]
61    pub cc: Vec<EmailAddress>,
62
63    /// BCC recipient email address(es).
64    #[serde(default)]
65    pub bcc: Vec<EmailAddress>,
66
67    /// Reply-to email address(es).
68    #[serde(default)]
69    pub reply_to: Vec<EmailAddress>,
70
71    /// Unix timestamp when the message was sent/received.
72    pub date: i64,
73
74    /// Whether the message is unread.
75    #[serde(skip_serializing_if = "Option::is_none")]
76    pub unread: Option<bool>,
77
78    /// Whether the message is starred.
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub starred: Option<bool>,
81
82    /// Short snippet of the message body.
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub snippet: Option<String>,
85
86    /// Full body of the message.
87    #[serde(skip_serializing_if = "Option::is_none")]
88    pub body: Option<String>,
89
90    /// Attachments included with the message.
91    #[serde(default)]
92    pub attachments: Vec<Attachment>,
93
94    /// Folder IDs this message belongs to.
95    #[serde(default)]
96    pub folders: Vec<FolderId>,
97
98    /// Unix timestamp when the message was created.
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub created_at: Option<i64>,
101}
102
103/// Query parameters for listing messages.
104///
105/// # Example
106///
107/// ```
108/// # use nylas_types::MessageQueryParams;
109/// let params = MessageQueryParams::builder()
110///     .subject("invoice")
111///     .unread(true)
112///     .limit(50)
113///     .build();
114/// ```
115#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
116pub struct MessageQueryParams {
117    /// Filter by subject (substring match).
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub subject: Option<String>,
120
121    /// Filter by sender email.
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub from: Option<String>,
124
125    /// Filter by recipient email (to field).
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub to: Option<String>,
128
129    /// Filter by CC recipient email.
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub cc: Option<String>,
132
133    /// Filter by BCC recipient email.
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub bcc: Option<String>,
136
137    /// Filter by any email (to, from, cc, bcc).
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub any_email: Option<String>,
140
141    /// Filter by thread ID.
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub thread_id: Option<String>,
144
145    /// Filter by unread status.
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub unread: Option<bool>,
148
149    /// Filter by starred status.
150    #[serde(skip_serializing_if = "Option::is_none")]
151    pub starred: Option<bool>,
152
153    /// Filter by messages with attachments.
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub has_attachment: Option<bool>,
156
157    /// Filter messages received before this timestamp.
158    #[serde(skip_serializing_if = "Option::is_none")]
159    pub received_before: Option<i64>,
160
161    /// Filter messages received after this timestamp.
162    #[serde(skip_serializing_if = "Option::is_none")]
163    pub received_after: Option<i64>,
164
165    /// Filter by folder ID.
166    #[serde(skip_serializing_if = "Option::is_none")]
167    pub in_: Option<String>,
168
169    /// Maximum number of results to return.
170    #[serde(skip_serializing_if = "Option::is_none")]
171    pub limit: Option<u32>,
172
173    /// Page token for pagination.
174    #[serde(skip_serializing_if = "Option::is_none")]
175    pub page_token: Option<String>,
176}
177
178impl MessageQueryParams {
179    /// Create a new builder for message query parameters.
180    pub fn builder() -> MessageQueryParamsBuilder {
181        MessageQueryParamsBuilder::default()
182    }
183}
184
185/// Builder for message query parameters.
186#[derive(Debug, Clone, Default)]
187pub struct MessageQueryParamsBuilder {
188    params: MessageQueryParams,
189}
190
191impl MessageQueryParamsBuilder {
192    /// Filter by subject (substring match).
193    pub fn subject(mut self, subject: impl Into<String>) -> Self {
194        self.params.subject = Some(subject.into());
195        self
196    }
197
198    /// Filter by sender email.
199    pub fn from(mut self, from: impl Into<String>) -> Self {
200        self.params.from = Some(from.into());
201        self
202    }
203
204    /// Filter by recipient email (to field).
205    pub fn to(mut self, to: impl Into<String>) -> Self {
206        self.params.to = Some(to.into());
207        self
208    }
209
210    /// Filter by CC recipient email.
211    pub fn cc(mut self, cc: impl Into<String>) -> Self {
212        self.params.cc = Some(cc.into());
213        self
214    }
215
216    /// Filter by BCC recipient email.
217    pub fn bcc(mut self, bcc: impl Into<String>) -> Self {
218        self.params.bcc = Some(bcc.into());
219        self
220    }
221
222    /// Filter by any email (to, from, cc, bcc).
223    pub fn any_email(mut self, email: impl Into<String>) -> Self {
224        self.params.any_email = Some(email.into());
225        self
226    }
227
228    /// Filter by thread ID.
229    pub fn thread_id(mut self, thread_id: impl Into<String>) -> Self {
230        self.params.thread_id = Some(thread_id.into());
231        self
232    }
233
234    /// Filter by unread status.
235    pub fn unread(mut self, unread: bool) -> Self {
236        self.params.unread = Some(unread);
237        self
238    }
239
240    /// Filter by starred status.
241    pub fn starred(mut self, starred: bool) -> Self {
242        self.params.starred = Some(starred);
243        self
244    }
245
246    /// Filter by messages with attachments.
247    pub fn has_attachment(mut self, has_attachment: bool) -> Self {
248        self.params.has_attachment = Some(has_attachment);
249        self
250    }
251
252    /// Filter messages received before this timestamp.
253    pub fn received_before(mut self, timestamp: i64) -> Self {
254        self.params.received_before = Some(timestamp);
255        self
256    }
257
258    /// Filter messages received after this timestamp.
259    pub fn received_after(mut self, timestamp: i64) -> Self {
260        self.params.received_after = Some(timestamp);
261        self
262    }
263
264    /// Filter by folder ID.
265    pub fn in_folder(mut self, folder_id: impl Into<String>) -> Self {
266        self.params.in_ = Some(folder_id.into());
267        self
268    }
269
270    /// Maximum number of results to return.
271    pub fn limit(mut self, limit: u32) -> Self {
272        self.params.limit = Some(limit);
273        self
274    }
275
276    /// Page token for pagination.
277    pub fn page_token(mut self, token: impl Into<String>) -> Self {
278        self.params.page_token = Some(token.into());
279        self
280    }
281
282    /// Build the query parameters.
283    pub fn build(self) -> MessageQueryParams {
284        self.params
285    }
286}
287
288/// Request to update a message.
289///
290/// # Example
291///
292/// ```
293/// # use nylas_types::UpdateMessageRequest;
294/// let update = UpdateMessageRequest::builder()
295///     .unread(false)
296///     .starred(true)
297///     .build();
298/// ```
299#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
300pub struct UpdateMessageRequest {
301    /// Mark message as read/unread.
302    #[serde(skip_serializing_if = "Option::is_none")]
303    pub unread: Option<bool>,
304
305    /// Mark message as starred/unstarred.
306    #[serde(skip_serializing_if = "Option::is_none")]
307    pub starred: Option<bool>,
308
309    /// Update folders the message belongs to.
310    #[serde(skip_serializing_if = "Option::is_none")]
311    pub folders: Option<Vec<String>>,
312}
313
314impl UpdateMessageRequest {
315    /// Create a new builder for update message request.
316    pub fn builder() -> UpdateMessageRequestBuilder {
317        UpdateMessageRequestBuilder::default()
318    }
319}
320
321/// Builder for update message request.
322#[derive(Debug, Clone, Default)]
323pub struct UpdateMessageRequestBuilder {
324    request: UpdateMessageRequest,
325}
326
327impl UpdateMessageRequestBuilder {
328    /// Mark message as read/unread.
329    pub fn unread(mut self, unread: bool) -> Self {
330        self.request.unread = Some(unread);
331        self
332    }
333
334    /// Mark message as starred/unstarred.
335    pub fn starred(mut self, starred: bool) -> Self {
336        self.request.starred = Some(starred);
337        self
338    }
339
340    /// Update folders the message belongs to.
341    pub fn folders(mut self, folders: Vec<String>) -> Self {
342        self.request.folders = Some(folders);
343        self
344    }
345
346    /// Build the update request.
347    pub fn build(self) -> UpdateMessageRequest {
348        self.request
349    }
350}
351
352#[cfg(test)]
353mod tests {
354    use super::*;
355
356    #[test]
357    fn test_message_creation() {
358        let message = Message {
359            id: MessageId::new("msg_123"),
360            grant_id: GrantId::new("grant_123"),
361            thread_id: Some(ThreadId::new("thread_123")),
362            subject: Some("Test Subject".to_string()),
363            from: vec![],
364            to: vec![],
365            cc: vec![],
366            bcc: vec![],
367            reply_to: vec![],
368            date: 1234567890,
369            unread: Some(true),
370            starred: Some(false),
371            snippet: Some("Test snippet".to_string()),
372            body: Some("Test body".to_string()),
373            attachments: vec![],
374            folders: vec![],
375            created_at: Some(1234567890),
376        };
377
378        assert_eq!(message.id.as_str(), "msg_123");
379        assert_eq!(message.grant_id.as_str(), "grant_123");
380        assert_eq!(message.subject, Some("Test Subject".to_string()));
381        assert_eq!(message.unread, Some(true));
382    }
383
384    #[test]
385    fn test_message_serialization() {
386        let message = Message {
387            id: MessageId::new("msg_123"),
388            grant_id: GrantId::new("grant_123"),
389            thread_id: Some(ThreadId::new("thread_123")),
390            subject: Some("Test".to_string()),
391            from: vec![],
392            to: vec![],
393            cc: vec![],
394            bcc: vec![],
395            reply_to: vec![],
396            date: 1234567890,
397            unread: Some(true),
398            starred: Some(false),
399            snippet: Some("Snippet".to_string()),
400            body: Some("Body".to_string()),
401            attachments: vec![],
402            folders: vec![],
403            created_at: Some(1234567890),
404        };
405
406        let json = serde_json::to_string(&message).unwrap();
407        assert!(json.contains("msg_123"));
408        assert!(json.contains("grant_123"));
409        assert!(json.contains("Test"));
410
411        let deserialized: Message = serde_json::from_str(&json).unwrap();
412        assert_eq!(deserialized, message);
413    }
414
415    #[test]
416    fn test_query_params_builder() {
417        let params = MessageQueryParams::builder()
418            .subject("invoice")
419            .unread(true)
420            .limit(50)
421            .build();
422
423        assert_eq!(params.subject, Some("invoice".to_string()));
424        assert_eq!(params.unread, Some(true));
425        assert_eq!(params.limit, Some(50));
426    }
427
428    #[test]
429    fn test_query_params_all_fields() {
430        let params = MessageQueryParams::builder()
431            .subject("test")
432            .from("sender@example.com")
433            .to("recipient@example.com")
434            .cc("cc@example.com")
435            .bcc("bcc@example.com")
436            .any_email("any@example.com")
437            .thread_id("thread_123")
438            .unread(true)
439            .starred(false)
440            .has_attachment(true)
441            .received_before(2000000000)
442            .received_after(1000000000)
443            .in_folder("folder_123")
444            .limit(100)
445            .page_token("token_abc")
446            .build();
447
448        assert_eq!(params.subject, Some("test".to_string()));
449        assert_eq!(params.from, Some("sender@example.com".to_string()));
450        assert_eq!(params.to, Some("recipient@example.com".to_string()));
451        assert_eq!(params.limit, Some(100));
452        assert_eq!(params.page_token, Some("token_abc".to_string()));
453    }
454
455    #[test]
456    fn test_query_params_serialization() {
457        let params = MessageQueryParams::builder()
458            .subject("test")
459            .unread(true)
460            .limit(50)
461            .build();
462
463        let json = serde_json::to_string(&params).unwrap();
464        assert!(json.contains("test"));
465        assert!(json.contains("true"));
466        assert!(json.contains("50"));
467    }
468
469    #[test]
470    fn test_update_message_request_builder() {
471        let update = UpdateMessageRequest::builder()
472            .unread(false)
473            .starred(true)
474            .build();
475
476        assert_eq!(update.unread, Some(false));
477        assert_eq!(update.starred, Some(true));
478    }
479
480    #[test]
481    fn test_update_message_request_with_folders() {
482        let update = UpdateMessageRequest::builder()
483            .unread(false)
484            .folders(vec!["folder1".to_string(), "folder2".to_string()])
485            .build();
486
487        assert_eq!(update.unread, Some(false));
488        assert_eq!(
489            update.folders,
490            Some(vec!["folder1".to_string(), "folder2".to_string()])
491        );
492    }
493
494    #[test]
495    fn test_update_message_request_serialization() {
496        let update = UpdateMessageRequest::builder()
497            .unread(false)
498            .starred(true)
499            .build();
500
501        let json = serde_json::to_string(&update).unwrap();
502        let deserialized: UpdateMessageRequest = serde_json::from_str(&json).unwrap();
503        assert_eq!(deserialized, update);
504    }
505}