Skip to main content

rusmes_jmap/methods/
submission.rs

1//! EmailSubmission method implementations for JMAP
2//!
3//! Implements RFC 8621 Section 7 - Email Submission
4//! - EmailSubmission/set - send outbound email
5//! - EmailSubmission/get - query submission status
6//! - EmailSubmission/query - list submissions
7//! - EmailSubmission/changes - track submission changes
8
9use crate::types::JmapSetError;
10use chrono::{DateTime, Utc};
11use rusmes_storage::MessageStore;
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14
15/// Email submission object
16#[derive(Debug, Clone, Serialize, Deserialize)]
17#[serde(rename_all = "camelCase")]
18pub struct EmailSubmission {
19    /// Unique identifier
20    pub id: String,
21    /// Identity ID for sender
22    pub identity_id: String,
23    /// Email ID being submitted
24    pub email_id: String,
25    /// Thread ID
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub thread_id: Option<String>,
28    /// Envelope information
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub envelope: Option<Envelope>,
31    /// Send at time (for delayed send)
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub send_at: Option<DateTime<Utc>>,
34    /// Undo status
35    pub undo_status: UndoStatus,
36    /// Delivery status
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub delivery_status: Option<HashMap<String, DeliveryStatus>>,
39    /// DSN blob IDs
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub dsn_blob_ids: Option<Vec<String>>,
42    /// MDN blob IDs
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub mdn_blob_ids: Option<Vec<String>>,
45}
46
47/// Envelope for submission
48#[derive(Debug, Clone, Serialize, Deserialize)]
49#[serde(rename_all = "camelCase")]
50pub struct Envelope {
51    pub mail_from: Address,
52    pub rcpt_to: Vec<Address>,
53}
54
55/// Email address for envelope
56#[derive(Debug, Clone, Serialize, Deserialize)]
57#[serde(rename_all = "camelCase")]
58pub struct Address {
59    pub email: String,
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub parameters: Option<HashMap<String, Option<String>>>,
62}
63
64/// Undo status enum
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
66#[serde(rename_all = "camelCase")]
67pub enum UndoStatus {
68    Pending,
69    Final,
70    Canceled,
71}
72
73/// Delivery status for a recipient
74#[derive(Debug, Clone, Serialize, Deserialize)]
75#[serde(rename_all = "camelCase")]
76pub struct DeliveryStatus {
77    pub smtp_reply: String,
78    pub delivered: DeliveryState,
79    pub displayed: DisplayedState,
80}
81
82/// Delivery state enum
83#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
84#[serde(rename_all = "camelCase")]
85pub enum DeliveryState {
86    Queued,
87    Yes,
88    No,
89    Unknown,
90}
91
92/// Displayed state enum
93#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
94#[serde(rename_all = "camelCase")]
95pub enum DisplayedState {
96    Unknown,
97    Yes,
98    No,
99}
100
101/// EmailSubmission/get request
102#[derive(Debug, Clone, Deserialize)]
103#[serde(rename_all = "camelCase")]
104pub struct EmailSubmissionGetRequest {
105    pub account_id: String,
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub ids: Option<Vec<String>>,
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub properties: Option<Vec<String>>,
110}
111
112/// EmailSubmission/get response
113#[derive(Debug, Clone, Serialize)]
114#[serde(rename_all = "camelCase")]
115pub struct EmailSubmissionGetResponse {
116    pub account_id: String,
117    pub state: String,
118    pub list: Vec<EmailSubmission>,
119    pub not_found: Vec<String>,
120}
121
122/// EmailSubmission/set request
123#[derive(Debug, Clone, Deserialize)]
124#[serde(rename_all = "camelCase")]
125pub struct EmailSubmissionSetRequest {
126    pub account_id: String,
127    #[serde(skip_serializing_if = "Option::is_none")]
128    pub if_in_state: Option<String>,
129    #[serde(skip_serializing_if = "Option::is_none")]
130    pub create: Option<HashMap<String, EmailSubmissionObject>>,
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub update: Option<HashMap<String, serde_json::Value>>,
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub destroy: Option<Vec<String>>,
135    #[serde(skip_serializing_if = "Option::is_none")]
136    pub on_success_update_email: Option<HashMap<String, serde_json::Value>>,
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub on_success_destroy_email: Option<Vec<String>>,
139}
140
141/// EmailSubmission object for creation
142#[derive(Debug, Clone, Deserialize)]
143#[serde(rename_all = "camelCase")]
144pub struct EmailSubmissionObject {
145    pub identity_id: String,
146    pub email_id: String,
147    #[serde(skip_serializing_if = "Option::is_none")]
148    pub envelope: Option<Envelope>,
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub send_at: Option<DateTime<Utc>>,
151}
152
153/// EmailSubmission/set response
154#[derive(Debug, Clone, Serialize)]
155#[serde(rename_all = "camelCase")]
156pub struct EmailSubmissionSetResponse {
157    pub account_id: String,
158    pub old_state: String,
159    pub new_state: String,
160    #[serde(skip_serializing_if = "Option::is_none")]
161    pub created: Option<HashMap<String, EmailSubmission>>,
162    #[serde(skip_serializing_if = "Option::is_none")]
163    pub updated: Option<HashMap<String, Option<EmailSubmission>>>,
164    #[serde(skip_serializing_if = "Option::is_none")]
165    pub destroyed: Option<Vec<String>>,
166    #[serde(skip_serializing_if = "Option::is_none")]
167    pub not_created: Option<HashMap<String, JmapSetError>>,
168    #[serde(skip_serializing_if = "Option::is_none")]
169    pub not_updated: Option<HashMap<String, JmapSetError>>,
170    #[serde(skip_serializing_if = "Option::is_none")]
171    pub not_destroyed: Option<HashMap<String, JmapSetError>>,
172}
173
174/// EmailSubmission/query request
175#[derive(Debug, Clone, Deserialize)]
176#[serde(rename_all = "camelCase")]
177pub struct EmailSubmissionQueryRequest {
178    pub account_id: String,
179    #[serde(skip_serializing_if = "Option::is_none")]
180    pub filter: Option<EmailSubmissionFilterCondition>,
181    #[serde(skip_serializing_if = "Option::is_none")]
182    pub sort: Option<Vec<EmailSubmissionSort>>,
183    #[serde(skip_serializing_if = "Option::is_none")]
184    pub position: Option<i64>,
185    #[serde(skip_serializing_if = "Option::is_none")]
186    pub limit: Option<u64>,
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub calculate_total: Option<bool>,
189}
190
191/// EmailSubmission filter condition
192#[derive(Debug, Clone, Deserialize)]
193#[serde(rename_all = "camelCase")]
194pub struct EmailSubmissionFilterCondition {
195    #[serde(skip_serializing_if = "Option::is_none")]
196    pub identity_ids: Option<Vec<String>>,
197    #[serde(skip_serializing_if = "Option::is_none")]
198    pub email_ids: Option<Vec<String>>,
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub thread_ids: Option<Vec<String>>,
201    #[serde(skip_serializing_if = "Option::is_none")]
202    pub undo_status: Option<UndoStatus>,
203    #[serde(skip_serializing_if = "Option::is_none")]
204    pub before: Option<DateTime<Utc>>,
205    #[serde(skip_serializing_if = "Option::is_none")]
206    pub after: Option<DateTime<Utc>>,
207}
208
209/// EmailSubmission sort comparator
210#[derive(Debug, Clone, Deserialize)]
211#[serde(rename_all = "camelCase")]
212pub struct EmailSubmissionSort {
213    pub property: String,
214    #[serde(skip_serializing_if = "Option::is_none")]
215    pub is_ascending: Option<bool>,
216}
217
218/// EmailSubmission/query response
219#[derive(Debug, Clone, Serialize)]
220#[serde(rename_all = "camelCase")]
221pub struct EmailSubmissionQueryResponse {
222    pub account_id: String,
223    pub query_state: String,
224    pub can_calculate_changes: bool,
225    pub position: i64,
226    pub ids: Vec<String>,
227    #[serde(skip_serializing_if = "Option::is_none")]
228    pub total: Option<u64>,
229    #[serde(skip_serializing_if = "Option::is_none")]
230    pub limit: Option<u64>,
231}
232
233/// EmailSubmission/changes request
234#[derive(Debug, Clone, Deserialize)]
235#[serde(rename_all = "camelCase")]
236pub struct EmailSubmissionChangesRequest {
237    pub account_id: String,
238    pub since_state: String,
239    #[serde(skip_serializing_if = "Option::is_none")]
240    pub max_changes: Option<u64>,
241}
242
243/// EmailSubmission/changes response
244#[derive(Debug, Clone, Serialize)]
245#[serde(rename_all = "camelCase")]
246pub struct EmailSubmissionChangesResponse {
247    pub account_id: String,
248    pub old_state: String,
249    pub new_state: String,
250    pub has_more_changes: bool,
251    pub created: Vec<String>,
252    pub updated: Vec<String>,
253    pub destroyed: Vec<String>,
254}
255
256/// Handle EmailSubmission/get method
257pub async fn email_submission_get(
258    request: EmailSubmissionGetRequest,
259    _message_store: &dyn MessageStore,
260) -> anyhow::Result<EmailSubmissionGetResponse> {
261    let list = Vec::new();
262    let mut not_found = Vec::new();
263
264    // If no IDs specified, return empty list
265    let ids = request.ids.unwrap_or_default();
266
267    for id in ids {
268        // In production, would query submission database
269        not_found.push(id);
270    }
271
272    Ok(EmailSubmissionGetResponse {
273        account_id: request.account_id,
274        state: "1".to_string(),
275        list,
276        not_found,
277    })
278}
279
280/// Handle EmailSubmission/set method
281#[allow(clippy::too_many_arguments)]
282pub async fn email_submission_set(
283    request: EmailSubmissionSetRequest,
284    _message_store: &dyn MessageStore,
285) -> anyhow::Result<EmailSubmissionSetResponse> {
286    let created = HashMap::new();
287    let updated = HashMap::new();
288    let destroyed = Vec::new();
289    let mut not_created = HashMap::new();
290    let mut not_updated = HashMap::new();
291    let mut not_destroyed = HashMap::new();
292
293    // Handle creates (send new emails)
294    if let Some(create_map) = request.create {
295        for (creation_id, _submission_obj) in create_map {
296            // In production, would:
297            // 1. Validate identity_id and email_id
298            // 2. Check if sendAt is in the future (delayed send)
299            // 3. Queue message for SMTP delivery
300            // 4. Create EmailSubmission object with pending status
301            // 5. Process onSuccessUpdateEmail if provided
302
303            not_created.insert(
304                creation_id,
305                JmapSetError {
306                    error_type: "notImplemented".to_string(),
307                    description: Some("Email submission not yet implemented".to_string()),
308                },
309            );
310        }
311    }
312
313    // Handle updates (e.g., cancel submission)
314    if let Some(update_map) = request.update {
315        for (id, _patch) in update_map {
316            // In production, would allow updating undoStatus to cancel
317            not_updated.insert(
318                id,
319                JmapSetError {
320                    error_type: "notImplemented".to_string(),
321                    description: Some("Submission update not yet implemented".to_string()),
322                },
323            );
324        }
325    }
326
327    // Handle destroys (delete submission records)
328    if let Some(destroy_ids) = request.destroy {
329        for id in destroy_ids {
330            not_destroyed.insert(
331                id,
332                JmapSetError {
333                    error_type: "notImplemented".to_string(),
334                    description: Some("Submission deletion not yet implemented".to_string()),
335                },
336            );
337        }
338    }
339
340    Ok(EmailSubmissionSetResponse {
341        account_id: request.account_id,
342        old_state: "1".to_string(),
343        new_state: "2".to_string(),
344        created: if created.is_empty() {
345            None
346        } else {
347            Some(created)
348        },
349        updated: if updated.is_empty() {
350            None
351        } else {
352            Some(updated)
353        },
354        destroyed: if destroyed.is_empty() {
355            None
356        } else {
357            Some(destroyed)
358        },
359        not_created: if not_created.is_empty() {
360            None
361        } else {
362            Some(not_created)
363        },
364        not_updated: if not_updated.is_empty() {
365            None
366        } else {
367            Some(not_updated)
368        },
369        not_destroyed: if not_destroyed.is_empty() {
370            None
371        } else {
372            Some(not_destroyed)
373        },
374    })
375}
376
377/// Handle EmailSubmission/query method
378pub async fn email_submission_query(
379    request: EmailSubmissionQueryRequest,
380    _message_store: &dyn MessageStore,
381) -> anyhow::Result<EmailSubmissionQueryResponse> {
382    // In production, would query submission database based on filter
383    let ids = Vec::new();
384
385    let position = request.position.unwrap_or(0);
386    let limit = request.limit.unwrap_or(100);
387
388    Ok(EmailSubmissionQueryResponse {
389        account_id: request.account_id,
390        query_state: "1".to_string(),
391        can_calculate_changes: false,
392        position,
393        ids,
394        total: if request.calculate_total.unwrap_or(false) {
395            Some(0)
396        } else {
397            None
398        },
399        limit: Some(limit),
400    })
401}
402
403/// Handle EmailSubmission/changes method
404pub async fn email_submission_changes(
405    request: EmailSubmissionChangesRequest,
406    _message_store: &dyn MessageStore,
407) -> anyhow::Result<EmailSubmissionChangesResponse> {
408    let since_state: u64 = request.since_state.parse().unwrap_or(0);
409    let new_state = (since_state + 1).to_string();
410
411    // In production, would query change log
412    let created = Vec::new();
413    let updated = Vec::new();
414    let destroyed = Vec::new();
415
416    Ok(EmailSubmissionChangesResponse {
417        account_id: request.account_id,
418        old_state: request.since_state,
419        new_state,
420        has_more_changes: false,
421        created,
422        updated,
423        destroyed,
424    })
425}
426
427#[cfg(test)]
428mod tests {
429    use super::*;
430    use rusmes_storage::backends::filesystem::FilesystemBackend;
431    use rusmes_storage::StorageBackend;
432    use std::path::PathBuf;
433
434    fn create_test_store() -> std::sync::Arc<dyn MessageStore> {
435        let backend = FilesystemBackend::new(PathBuf::from("/tmp/rusmes-test-storage"));
436        backend.message_store()
437    }
438
439    #[tokio::test]
440    async fn test_email_submission_get() {
441        let store = create_test_store();
442        let request = EmailSubmissionGetRequest {
443            account_id: "acc1".to_string(),
444            ids: Some(vec!["sub1".to_string()]),
445            properties: None,
446        };
447
448        let response = email_submission_get(request, store.as_ref()).await.unwrap();
449        assert_eq!(response.account_id, "acc1");
450        assert_eq!(response.not_found.len(), 1);
451    }
452
453    #[tokio::test]
454    async fn test_email_submission_set_create() {
455        let store = create_test_store();
456        let mut create_map = HashMap::new();
457        create_map.insert(
458            "sub1".to_string(),
459            EmailSubmissionObject {
460                identity_id: "id1".to_string(),
461                email_id: "email1".to_string(),
462                envelope: None,
463                send_at: None,
464            },
465        );
466
467        let request = EmailSubmissionSetRequest {
468            account_id: "acc1".to_string(),
469            if_in_state: None,
470            create: Some(create_map),
471            update: None,
472            destroy: None,
473            on_success_update_email: None,
474            on_success_destroy_email: None,
475        };
476
477        let response = email_submission_set(request, store.as_ref()).await.unwrap();
478        assert_eq!(response.account_id, "acc1");
479        assert!(response.not_created.is_some());
480    }
481
482    #[tokio::test]
483    async fn test_email_submission_query() {
484        let store = create_test_store();
485        let request = EmailSubmissionQueryRequest {
486            account_id: "acc1".to_string(),
487            filter: None,
488            sort: None,
489            position: None,
490            limit: Some(50),
491            calculate_total: Some(true),
492        };
493
494        let response = email_submission_query(request, store.as_ref())
495            .await
496            .unwrap();
497        assert_eq!(response.account_id, "acc1");
498        assert_eq!(response.total, Some(0));
499    }
500
501    #[tokio::test]
502    async fn test_email_submission_changes() {
503        let store = create_test_store();
504        let request = EmailSubmissionChangesRequest {
505            account_id: "acc1".to_string(),
506            since_state: "1".to_string(),
507            max_changes: Some(50),
508        };
509
510        let response = email_submission_changes(request, store.as_ref())
511            .await
512            .unwrap();
513        assert_eq!(response.account_id, "acc1");
514        assert_eq!(response.old_state, "1");
515        assert!(!response.has_more_changes);
516    }
517
518    #[tokio::test]
519    async fn test_email_submission_delayed_send() {
520        let store = create_test_store();
521        let mut create_map = HashMap::new();
522        let send_at = Utc::now() + chrono::Duration::hours(2);
523
524        create_map.insert(
525            "delayed1".to_string(),
526            EmailSubmissionObject {
527                identity_id: "id1".to_string(),
528                email_id: "email1".to_string(),
529                envelope: None,
530                send_at: Some(send_at),
531            },
532        );
533
534        let request = EmailSubmissionSetRequest {
535            account_id: "acc1".to_string(),
536            if_in_state: None,
537            create: Some(create_map),
538            update: None,
539            destroy: None,
540            on_success_update_email: None,
541            on_success_destroy_email: None,
542        };
543
544        let response = email_submission_set(request, store.as_ref()).await.unwrap();
545        assert!(response.not_created.is_some());
546    }
547
548    #[tokio::test]
549    async fn test_email_submission_with_envelope() {
550        let store = create_test_store();
551        let mut create_map = HashMap::new();
552        let envelope = Envelope {
553            mail_from: Address {
554                email: "sender@example.com".to_string(),
555                parameters: None,
556            },
557            rcpt_to: vec![Address {
558                email: "recipient@example.com".to_string(),
559                parameters: None,
560            }],
561        };
562
563        create_map.insert(
564            "env1".to_string(),
565            EmailSubmissionObject {
566                identity_id: "id1".to_string(),
567                email_id: "email1".to_string(),
568                envelope: Some(envelope),
569                send_at: None,
570            },
571        );
572
573        let request = EmailSubmissionSetRequest {
574            account_id: "acc1".to_string(),
575            if_in_state: None,
576            create: Some(create_map),
577            update: None,
578            destroy: None,
579            on_success_update_email: None,
580            on_success_destroy_email: None,
581        };
582
583        let response = email_submission_set(request, store.as_ref()).await.unwrap();
584        assert!(response.not_created.is_some());
585    }
586
587    #[tokio::test]
588    async fn test_email_submission_query_with_filter() {
589        let store = create_test_store();
590        let filter = EmailSubmissionFilterCondition {
591            identity_ids: Some(vec!["id1".to_string()]),
592            email_ids: None,
593            thread_ids: None,
594            undo_status: Some(UndoStatus::Pending),
595            before: None,
596            after: None,
597        };
598
599        let request = EmailSubmissionQueryRequest {
600            account_id: "acc1".to_string(),
601            filter: Some(filter),
602            sort: None,
603            position: None,
604            limit: None,
605            calculate_total: Some(false),
606        };
607
608        let response = email_submission_query(request, store.as_ref())
609            .await
610            .unwrap();
611        assert!(response.total.is_none());
612    }
613
614    #[tokio::test]
615    async fn test_email_submission_update_undo_status() {
616        let store = create_test_store();
617        let mut update_map = HashMap::new();
618        update_map.insert(
619            "sub1".to_string(),
620            serde_json::json!({"undoStatus": "canceled"}),
621        );
622
623        let request = EmailSubmissionSetRequest {
624            account_id: "acc1".to_string(),
625            if_in_state: None,
626            create: None,
627            update: Some(update_map),
628            destroy: None,
629            on_success_update_email: None,
630            on_success_destroy_email: None,
631        };
632
633        let response = email_submission_set(request, store.as_ref()).await.unwrap();
634        assert!(response.not_updated.is_some());
635    }
636
637    #[tokio::test]
638    async fn test_email_submission_destroy() {
639        let store = create_test_store();
640        let request = EmailSubmissionSetRequest {
641            account_id: "acc1".to_string(),
642            if_in_state: None,
643            create: None,
644            update: None,
645            destroy: Some(vec!["sub1".to_string(), "sub2".to_string()]),
646            on_success_update_email: None,
647            on_success_destroy_email: None,
648        };
649
650        let response = email_submission_set(request, store.as_ref()).await.unwrap();
651        assert!(response.not_destroyed.is_some());
652    }
653
654    #[tokio::test]
655    async fn test_email_submission_on_success_actions() {
656        let store = create_test_store();
657        let mut create_map = HashMap::new();
658        create_map.insert(
659            "sub1".to_string(),
660            EmailSubmissionObject {
661                identity_id: "id1".to_string(),
662                email_id: "email1".to_string(),
663                envelope: None,
664                send_at: None,
665            },
666        );
667
668        let mut on_success_update = HashMap::new();
669        on_success_update.insert(
670            "email1".to_string(),
671            serde_json::json!({"keywords/$sent": true}),
672        );
673
674        let request = EmailSubmissionSetRequest {
675            account_id: "acc1".to_string(),
676            if_in_state: None,
677            create: Some(create_map),
678            update: None,
679            destroy: None,
680            on_success_update_email: Some(on_success_update),
681            on_success_destroy_email: None,
682        };
683
684        let response = email_submission_set(request, store.as_ref()).await.unwrap();
685        assert!(response.not_created.is_some());
686    }
687
688    #[tokio::test]
689    async fn test_email_submission_query_sort() {
690        let store = create_test_store();
691        let sort = vec![EmailSubmissionSort {
692            property: "sendAt".to_string(),
693            is_ascending: Some(false),
694        }];
695
696        let request = EmailSubmissionQueryRequest {
697            account_id: "acc1".to_string(),
698            filter: None,
699            sort: Some(sort),
700            position: Some(10),
701            limit: Some(25),
702            calculate_total: None,
703        };
704
705        let response = email_submission_query(request, store.as_ref())
706            .await
707            .unwrap();
708        assert_eq!(response.position, 10);
709    }
710
711    #[tokio::test]
712    async fn test_email_submission_multiple_recipients() {
713        let store = create_test_store();
714        let mut create_map = HashMap::new();
715        let envelope = Envelope {
716            mail_from: Address {
717                email: "sender@example.com".to_string(),
718                parameters: None,
719            },
720            rcpt_to: vec![
721                Address {
722                    email: "recipient1@example.com".to_string(),
723                    parameters: None,
724                },
725                Address {
726                    email: "recipient2@example.com".to_string(),
727                    parameters: None,
728                },
729                Address {
730                    email: "recipient3@example.com".to_string(),
731                    parameters: None,
732                },
733            ],
734        };
735
736        create_map.insert(
737            "multi1".to_string(),
738            EmailSubmissionObject {
739                identity_id: "id1".to_string(),
740                email_id: "email1".to_string(),
741                envelope: Some(envelope),
742                send_at: None,
743            },
744        );
745
746        let request = EmailSubmissionSetRequest {
747            account_id: "acc1".to_string(),
748            if_in_state: None,
749            create: Some(create_map),
750            update: None,
751            destroy: None,
752            on_success_update_email: None,
753            on_success_destroy_email: None,
754        };
755
756        let response = email_submission_set(request, store.as_ref()).await.unwrap();
757        assert!(response.not_created.is_some());
758    }
759
760    #[tokio::test]
761    async fn test_email_submission_changes_pagination() {
762        let store = create_test_store();
763        let request = EmailSubmissionChangesRequest {
764            account_id: "acc1".to_string(),
765            since_state: "100".to_string(),
766            max_changes: Some(10),
767        };
768
769        let response = email_submission_changes(request, store.as_ref())
770            .await
771            .unwrap();
772        assert_eq!(response.old_state, "100");
773        assert_eq!(response.new_state, "101");
774    }
775
776    #[tokio::test]
777    async fn test_email_submission_get_with_properties() {
778        let store = create_test_store();
779        let properties = vec![
780            "id".to_string(),
781            "identityId".to_string(),
782            "emailId".to_string(),
783            "undoStatus".to_string(),
784            "deliveryStatus".to_string(),
785        ];
786
787        let request = EmailSubmissionGetRequest {
788            account_id: "acc1".to_string(),
789            ids: Some(vec!["sub1".to_string()]),
790            properties: Some(properties),
791        };
792
793        let response = email_submission_get(request, store.as_ref()).await.unwrap();
794        assert_eq!(response.list.len(), 0);
795    }
796
797    #[tokio::test]
798    async fn test_email_submission_query_date_range() {
799        let store = create_test_store();
800        let now = Utc::now();
801        let filter = EmailSubmissionFilterCondition {
802            identity_ids: None,
803            email_ids: None,
804            thread_ids: None,
805            undo_status: None,
806            before: Some(now),
807            after: Some(now - chrono::Duration::days(7)),
808        };
809
810        let request = EmailSubmissionQueryRequest {
811            account_id: "acc1".to_string(),
812            filter: Some(filter),
813            sort: None,
814            position: None,
815            limit: None,
816            calculate_total: Some(true),
817        };
818
819        let response = email_submission_query(request, store.as_ref())
820            .await
821            .unwrap();
822        assert_eq!(response.total, Some(0));
823    }
824
825    #[tokio::test]
826    async fn test_email_submission_envelope_parameters() {
827        let store = create_test_store();
828        let mut create_map = HashMap::new();
829        let mut params = HashMap::new();
830        params.insert("SIZE".to_string(), Some("1024".to_string()));
831        params.insert("BODY".to_string(), Some("8BITMIME".to_string()));
832
833        let envelope = Envelope {
834            mail_from: Address {
835                email: "sender@example.com".to_string(),
836                parameters: Some(params),
837            },
838            rcpt_to: vec![Address {
839                email: "recipient@example.com".to_string(),
840                parameters: None,
841            }],
842        };
843
844        create_map.insert(
845            "params1".to_string(),
846            EmailSubmissionObject {
847                identity_id: "id1".to_string(),
848                email_id: "email1".to_string(),
849                envelope: Some(envelope),
850                send_at: None,
851            },
852        );
853
854        let request = EmailSubmissionSetRequest {
855            account_id: "acc1".to_string(),
856            if_in_state: None,
857            create: Some(create_map),
858            update: None,
859            destroy: None,
860            on_success_update_email: None,
861            on_success_destroy_email: None,
862        };
863
864        let response = email_submission_set(request, store.as_ref()).await.unwrap();
865        assert!(response.not_created.is_some());
866    }
867
868    #[tokio::test]
869    async fn test_email_submission_if_in_state() {
870        let store = create_test_store();
871        let request = EmailSubmissionSetRequest {
872            account_id: "acc1".to_string(),
873            if_in_state: Some("state123".to_string()),
874            create: None,
875            update: None,
876            destroy: None,
877            on_success_update_email: None,
878            on_success_destroy_email: None,
879        };
880
881        let response = email_submission_set(request, store.as_ref()).await.unwrap();
882        assert_eq!(response.old_state, "1");
883    }
884
885    #[tokio::test]
886    async fn test_email_submission_query_thread_filter() {
887        let store = create_test_store();
888        let filter = EmailSubmissionFilterCondition {
889            identity_ids: None,
890            email_ids: None,
891            thread_ids: Some(vec!["thread1".to_string(), "thread2".to_string()]),
892            undo_status: None,
893            before: None,
894            after: None,
895        };
896
897        let request = EmailSubmissionQueryRequest {
898            account_id: "acc1".to_string(),
899            filter: Some(filter),
900            sort: None,
901            position: None,
902            limit: None,
903            calculate_total: None,
904        };
905
906        let response = email_submission_query(request, store.as_ref())
907            .await
908            .unwrap();
909        assert_eq!(response.ids.len(), 0);
910    }
911
912    #[tokio::test]
913    async fn test_email_submission_undo_status_values() {
914        assert_eq!(
915            serde_json::to_string(&UndoStatus::Pending).unwrap(),
916            "\"pending\""
917        );
918        assert_eq!(
919            serde_json::to_string(&UndoStatus::Final).unwrap(),
920            "\"final\""
921        );
922        assert_eq!(
923            serde_json::to_string(&UndoStatus::Canceled).unwrap(),
924            "\"canceled\""
925        );
926    }
927
928    #[tokio::test]
929    async fn test_email_submission_delivery_states() {
930        assert_eq!(
931            serde_json::to_string(&DeliveryState::Queued).unwrap(),
932            "\"queued\""
933        );
934        assert_eq!(
935            serde_json::to_string(&DeliveryState::Yes).unwrap(),
936            "\"yes\""
937        );
938        assert_eq!(serde_json::to_string(&DeliveryState::No).unwrap(), "\"no\"");
939        assert_eq!(
940            serde_json::to_string(&DeliveryState::Unknown).unwrap(),
941            "\"unknown\""
942        );
943    }
944
945    #[tokio::test]
946    async fn test_email_submission_get_all() {
947        let store = create_test_store();
948        let request = EmailSubmissionGetRequest {
949            account_id: "acc1".to_string(),
950            ids: None,
951            properties: None,
952        };
953
954        let response = email_submission_get(request, store.as_ref()).await.unwrap();
955        assert_eq!(response.list.len(), 0);
956    }
957
958    #[tokio::test]
959    async fn test_email_submission_batch_create() {
960        let store = create_test_store();
961        let mut create_map = HashMap::new();
962
963        for i in 1..=10 {
964            create_map.insert(
965                format!("sub{}", i),
966                EmailSubmissionObject {
967                    identity_id: format!("id{}", i),
968                    email_id: format!("email{}", i),
969                    envelope: None,
970                    send_at: None,
971                },
972            );
973        }
974
975        let request = EmailSubmissionSetRequest {
976            account_id: "acc1".to_string(),
977            if_in_state: None,
978            create: Some(create_map),
979            update: None,
980            destroy: None,
981            on_success_update_email: None,
982            on_success_destroy_email: None,
983        };
984
985        let response = email_submission_set(request, store.as_ref()).await.unwrap();
986        assert_eq!(response.not_created.unwrap().len(), 10);
987    }
988
989    #[tokio::test]
990    async fn test_email_submission_on_success_destroy() {
991        let store = create_test_store();
992        let mut create_map = HashMap::new();
993        create_map.insert(
994            "sub1".to_string(),
995            EmailSubmissionObject {
996                identity_id: "id1".to_string(),
997                email_id: "draft1".to_string(),
998                envelope: None,
999                send_at: None,
1000            },
1001        );
1002
1003        let request = EmailSubmissionSetRequest {
1004            account_id: "acc1".to_string(),
1005            if_in_state: None,
1006            create: Some(create_map),
1007            update: None,
1008            destroy: None,
1009            on_success_update_email: None,
1010            on_success_destroy_email: Some(vec!["draft1".to_string()]),
1011        };
1012
1013        let response = email_submission_set(request, store.as_ref()).await.unwrap();
1014        assert!(response.not_created.is_some());
1015    }
1016
1017    #[tokio::test]
1018    async fn test_email_submission_delayed_send_past() {
1019        let store = create_test_store();
1020        let mut create_map = HashMap::new();
1021        let send_at = Utc::now() - chrono::Duration::hours(1);
1022
1023        create_map.insert(
1024            "past1".to_string(),
1025            EmailSubmissionObject {
1026                identity_id: "id1".to_string(),
1027                email_id: "email1".to_string(),
1028                envelope: None,
1029                send_at: Some(send_at),
1030            },
1031        );
1032
1033        let request = EmailSubmissionSetRequest {
1034            account_id: "acc1".to_string(),
1035            if_in_state: None,
1036            create: Some(create_map),
1037            update: None,
1038            destroy: None,
1039            on_success_update_email: None,
1040            on_success_destroy_email: None,
1041        };
1042
1043        let response = email_submission_set(request, store.as_ref()).await.unwrap();
1044        assert!(response.not_created.is_some());
1045    }
1046
1047    #[tokio::test]
1048    async fn test_email_submission_empty_envelope_recipients() {
1049        let store = create_test_store();
1050        let mut create_map = HashMap::new();
1051        let envelope = Envelope {
1052            mail_from: Address {
1053                email: "sender@example.com".to_string(),
1054                parameters: None,
1055            },
1056            rcpt_to: vec![],
1057        };
1058
1059        create_map.insert(
1060            "empty1".to_string(),
1061            EmailSubmissionObject {
1062                identity_id: "id1".to_string(),
1063                email_id: "email1".to_string(),
1064                envelope: Some(envelope),
1065                send_at: None,
1066            },
1067        );
1068
1069        let request = EmailSubmissionSetRequest {
1070            account_id: "acc1".to_string(),
1071            if_in_state: None,
1072            create: Some(create_map),
1073            update: None,
1074            destroy: None,
1075            on_success_update_email: None,
1076            on_success_destroy_email: None,
1077        };
1078
1079        let response = email_submission_set(request, store.as_ref()).await.unwrap();
1080        assert!(response.not_created.is_some());
1081    }
1082
1083    #[tokio::test]
1084    async fn test_email_submission_query_email_ids_filter() {
1085        let store = create_test_store();
1086        let filter = EmailSubmissionFilterCondition {
1087            identity_ids: None,
1088            email_ids: Some(vec!["email1".to_string(), "email2".to_string()]),
1089            thread_ids: None,
1090            undo_status: None,
1091            before: None,
1092            after: None,
1093        };
1094
1095        let request = EmailSubmissionQueryRequest {
1096            account_id: "acc1".to_string(),
1097            filter: Some(filter),
1098            sort: None,
1099            position: None,
1100            limit: None,
1101            calculate_total: Some(true),
1102        };
1103
1104        let response = email_submission_query(request, store.as_ref())
1105            .await
1106            .unwrap();
1107        assert_eq!(response.total, Some(0));
1108    }
1109
1110    #[tokio::test]
1111    async fn test_email_submission_query_final_status() {
1112        let store = create_test_store();
1113        let filter = EmailSubmissionFilterCondition {
1114            identity_ids: None,
1115            email_ids: None,
1116            thread_ids: None,
1117            undo_status: Some(UndoStatus::Final),
1118            before: None,
1119            after: None,
1120        };
1121
1122        let request = EmailSubmissionQueryRequest {
1123            account_id: "acc1".to_string(),
1124            filter: Some(filter),
1125            sort: None,
1126            position: None,
1127            limit: None,
1128            calculate_total: None,
1129        };
1130
1131        let response = email_submission_query(request, store.as_ref())
1132            .await
1133            .unwrap();
1134        assert_eq!(response.ids.len(), 0);
1135    }
1136
1137    #[tokio::test]
1138    async fn test_email_submission_query_canceled_status() {
1139        let store = create_test_store();
1140        let filter = EmailSubmissionFilterCondition {
1141            identity_ids: None,
1142            email_ids: None,
1143            thread_ids: None,
1144            undo_status: Some(UndoStatus::Canceled),
1145            before: None,
1146            after: None,
1147        };
1148
1149        let request = EmailSubmissionQueryRequest {
1150            account_id: "acc1".to_string(),
1151            filter: Some(filter),
1152            sort: None,
1153            position: None,
1154            limit: None,
1155            calculate_total: None,
1156        };
1157
1158        let response = email_submission_query(request, store.as_ref())
1159            .await
1160            .unwrap();
1161        assert_eq!(response.ids.len(), 0);
1162    }
1163
1164    #[tokio::test]
1165    async fn test_email_submission_mixed_operations() {
1166        let store = create_test_store();
1167        let mut create_map = HashMap::new();
1168        create_map.insert(
1169            "new1".to_string(),
1170            EmailSubmissionObject {
1171                identity_id: "id1".to_string(),
1172                email_id: "email1".to_string(),
1173                envelope: None,
1174                send_at: None,
1175            },
1176        );
1177
1178        let mut update_map = HashMap::new();
1179        update_map.insert(
1180            "sub1".to_string(),
1181            serde_json::json!({"undoStatus": "canceled"}),
1182        );
1183
1184        let request = EmailSubmissionSetRequest {
1185            account_id: "acc1".to_string(),
1186            if_in_state: None,
1187            create: Some(create_map),
1188            update: Some(update_map),
1189            destroy: Some(vec!["sub2".to_string()]),
1190            on_success_update_email: None,
1191            on_success_destroy_email: None,
1192        };
1193
1194        let response = email_submission_set(request, store.as_ref()).await.unwrap();
1195        assert!(response.not_created.is_some());
1196        assert!(response.not_updated.is_some());
1197        assert!(response.not_destroyed.is_some());
1198    }
1199
1200    #[tokio::test]
1201    async fn test_email_submission_changes_zero_state() {
1202        let store = create_test_store();
1203        let request = EmailSubmissionChangesRequest {
1204            account_id: "acc1".to_string(),
1205            since_state: "0".to_string(),
1206            max_changes: None,
1207        };
1208
1209        let response = email_submission_changes(request, store.as_ref())
1210            .await
1211            .unwrap();
1212        assert_eq!(response.old_state, "0");
1213        assert_eq!(response.new_state, "1");
1214    }
1215
1216    #[tokio::test]
1217    async fn test_displayed_state_serialization() {
1218        assert_eq!(
1219            serde_json::to_string(&DisplayedState::Unknown).unwrap(),
1220            "\"unknown\""
1221        );
1222        assert_eq!(
1223            serde_json::to_string(&DisplayedState::Yes).unwrap(),
1224            "\"yes\""
1225        );
1226        assert_eq!(
1227            serde_json::to_string(&DisplayedState::No).unwrap(),
1228            "\"no\""
1229        );
1230    }
1231
1232    #[tokio::test]
1233    async fn test_email_submission_get_multiple_ids() {
1234        let store = create_test_store();
1235        let request = EmailSubmissionGetRequest {
1236            account_id: "acc1".to_string(),
1237            ids: Some(vec![
1238                "sub1".to_string(),
1239                "sub2".to_string(),
1240                "sub3".to_string(),
1241            ]),
1242            properties: None,
1243        };
1244
1245        let response = email_submission_get(request, store.as_ref()).await.unwrap();
1246        assert_eq!(response.not_found.len(), 3);
1247    }
1248
1249    #[tokio::test]
1250    async fn test_email_submission_query_multiple_sorts() {
1251        let store = create_test_store();
1252        let sort = vec![
1253            EmailSubmissionSort {
1254                property: "sendAt".to_string(),
1255                is_ascending: Some(false),
1256            },
1257            EmailSubmissionSort {
1258                property: "emailId".to_string(),
1259                is_ascending: Some(true),
1260            },
1261        ];
1262
1263        let request = EmailSubmissionQueryRequest {
1264            account_id: "acc1".to_string(),
1265            filter: None,
1266            sort: Some(sort),
1267            position: None,
1268            limit: None,
1269            calculate_total: None,
1270        };
1271
1272        let response = email_submission_query(request, store.as_ref())
1273            .await
1274            .unwrap();
1275        assert_eq!(response.ids.len(), 0);
1276    }
1277
1278    #[tokio::test]
1279    async fn test_email_submission_query_complex_filter() {
1280        let store = create_test_store();
1281        let now = Utc::now();
1282        let filter = EmailSubmissionFilterCondition {
1283            identity_ids: Some(vec!["id1".to_string(), "id2".to_string()]),
1284            email_ids: Some(vec!["email1".to_string()]),
1285            thread_ids: Some(vec!["thread1".to_string()]),
1286            undo_status: Some(UndoStatus::Pending),
1287            before: Some(now),
1288            after: Some(now - chrono::Duration::days(30)),
1289        };
1290
1291        let request = EmailSubmissionQueryRequest {
1292            account_id: "acc1".to_string(),
1293            filter: Some(filter),
1294            sort: None,
1295            position: Some(5),
1296            limit: Some(10),
1297            calculate_total: Some(true),
1298        };
1299
1300        let response = email_submission_query(request, store.as_ref())
1301            .await
1302            .unwrap();
1303        assert_eq!(response.position, 5);
1304        assert_eq!(response.limit, Some(10));
1305    }
1306
1307    #[tokio::test]
1308    async fn test_email_submission_envelope_with_dsn_params() {
1309        let store = create_test_store();
1310        let mut create_map = HashMap::new();
1311        let mut params = HashMap::new();
1312        params.insert("RET".to_string(), Some("FULL".to_string()));
1313        params.insert("ENVID".to_string(), Some("QQ314159".to_string()));
1314
1315        let envelope = Envelope {
1316            mail_from: Address {
1317                email: "sender@example.com".to_string(),
1318                parameters: Some(params),
1319            },
1320            rcpt_to: vec![Address {
1321                email: "recipient@example.com".to_string(),
1322                parameters: None,
1323            }],
1324        };
1325
1326        create_map.insert(
1327            "dsn1".to_string(),
1328            EmailSubmissionObject {
1329                identity_id: "id1".to_string(),
1330                email_id: "email1".to_string(),
1331                envelope: Some(envelope),
1332                send_at: None,
1333            },
1334        );
1335
1336        let request = EmailSubmissionSetRequest {
1337            account_id: "acc1".to_string(),
1338            if_in_state: None,
1339            create: Some(create_map),
1340            update: None,
1341            destroy: None,
1342            on_success_update_email: None,
1343            on_success_destroy_email: None,
1344        };
1345
1346        let response = email_submission_set(request, store.as_ref()).await.unwrap();
1347        assert!(response.not_created.is_some());
1348    }
1349
1350    #[tokio::test]
1351    async fn test_email_submission_with_recipient_params() {
1352        let store = create_test_store();
1353        let mut create_map = HashMap::new();
1354        let mut rcpt_params = HashMap::new();
1355        rcpt_params.insert("NOTIFY".to_string(), Some("SUCCESS,FAILURE".to_string()));
1356
1357        let envelope = Envelope {
1358            mail_from: Address {
1359                email: "sender@example.com".to_string(),
1360                parameters: None,
1361            },
1362            rcpt_to: vec![Address {
1363                email: "recipient@example.com".to_string(),
1364                parameters: Some(rcpt_params),
1365            }],
1366        };
1367
1368        create_map.insert(
1369            "notify1".to_string(),
1370            EmailSubmissionObject {
1371                identity_id: "id1".to_string(),
1372                email_id: "email1".to_string(),
1373                envelope: Some(envelope),
1374                send_at: None,
1375            },
1376        );
1377
1378        let request = EmailSubmissionSetRequest {
1379            account_id: "acc1".to_string(),
1380            if_in_state: None,
1381            create: Some(create_map),
1382            update: None,
1383            destroy: None,
1384            on_success_update_email: None,
1385            on_success_destroy_email: None,
1386        };
1387
1388        let response = email_submission_set(request, store.as_ref()).await.unwrap();
1389        assert!(response.not_created.is_some());
1390    }
1391
1392    #[tokio::test]
1393    async fn test_email_submission_destroy_multiple() {
1394        let store = create_test_store();
1395        let request = EmailSubmissionSetRequest {
1396            account_id: "acc1".to_string(),
1397            if_in_state: None,
1398            create: None,
1399            update: None,
1400            destroy: Some(vec![
1401                "sub1".to_string(),
1402                "sub2".to_string(),
1403                "sub3".to_string(),
1404                "sub4".to_string(),
1405                "sub5".to_string(),
1406            ]),
1407            on_success_update_email: None,
1408            on_success_destroy_email: None,
1409        };
1410
1411        let response = email_submission_set(request, store.as_ref()).await.unwrap();
1412        assert_eq!(response.not_destroyed.unwrap().len(), 5);
1413    }
1414
1415    #[tokio::test]
1416    async fn test_email_submission_query_position_and_limit() {
1417        let store = create_test_store();
1418        let request = EmailSubmissionQueryRequest {
1419            account_id: "acc1".to_string(),
1420            filter: None,
1421            sort: None,
1422            position: Some(100),
1423            limit: Some(5),
1424            calculate_total: Some(true),
1425        };
1426
1427        let response = email_submission_query(request, store.as_ref())
1428            .await
1429            .unwrap();
1430        assert_eq!(response.position, 100);
1431        assert_eq!(response.limit, Some(5));
1432        assert_eq!(response.total, Some(0));
1433    }
1434
1435    #[tokio::test]
1436    async fn test_email_submission_empty_request() {
1437        let store = create_test_store();
1438        let request = EmailSubmissionSetRequest {
1439            account_id: "acc1".to_string(),
1440            if_in_state: None,
1441            create: None,
1442            update: None,
1443            destroy: None,
1444            on_success_update_email: None,
1445            on_success_destroy_email: None,
1446        };
1447
1448        let response = email_submission_set(request, store.as_ref()).await.unwrap();
1449        assert!(response.created.is_none());
1450        assert!(response.updated.is_none());
1451        assert!(response.destroyed.is_none());
1452    }
1453
1454    #[tokio::test]
1455    async fn test_email_submission_query_default_limit() {
1456        let store = create_test_store();
1457        let request = EmailSubmissionQueryRequest {
1458            account_id: "acc1".to_string(),
1459            filter: None,
1460            sort: None,
1461            position: None,
1462            limit: None,
1463            calculate_total: None,
1464        };
1465
1466        let response = email_submission_query(request, store.as_ref())
1467            .await
1468            .unwrap();
1469        assert_eq!(response.limit, Some(100));
1470    }
1471
1472    #[tokio::test]
1473    async fn test_email_submission_changes_with_max_changes() {
1474        let store = create_test_store();
1475        let request = EmailSubmissionChangesRequest {
1476            account_id: "acc1".to_string(),
1477            since_state: "5".to_string(),
1478            max_changes: Some(100),
1479        };
1480
1481        let response = email_submission_changes(request, store.as_ref())
1482            .await
1483            .unwrap();
1484        assert_eq!(response.old_state, "5");
1485        assert_eq!(response.new_state, "6");
1486        assert!(!response.has_more_changes);
1487    }
1488
1489    #[tokio::test]
1490    async fn test_email_submission_on_success_combined() {
1491        let store = create_test_store();
1492        let mut create_map = HashMap::new();
1493        create_map.insert(
1494            "sub1".to_string(),
1495            EmailSubmissionObject {
1496                identity_id: "id1".to_string(),
1497                email_id: "draft1".to_string(),
1498                envelope: None,
1499                send_at: None,
1500            },
1501        );
1502
1503        let mut on_success_update = HashMap::new();
1504        on_success_update.insert(
1505            "draft1".to_string(),
1506            serde_json::json!({"keywords/$sent": true}),
1507        );
1508
1509        let request = EmailSubmissionSetRequest {
1510            account_id: "acc1".to_string(),
1511            if_in_state: None,
1512            create: Some(create_map),
1513            update: None,
1514            destroy: None,
1515            on_success_update_email: Some(on_success_update),
1516            on_success_destroy_email: Some(vec!["draft2".to_string()]),
1517        };
1518
1519        let response = email_submission_set(request, store.as_ref()).await.unwrap();
1520        assert!(response.not_created.is_some());
1521    }
1522
1523    #[tokio::test]
1524    async fn test_email_submission_serialization() {
1525        let submission = EmailSubmission {
1526            id: "sub1".to_string(),
1527            identity_id: "id1".to_string(),
1528            email_id: "email1".to_string(),
1529            thread_id: Some("thread1".to_string()),
1530            envelope: None,
1531            send_at: None,
1532            undo_status: UndoStatus::Pending,
1533            delivery_status: None,
1534            dsn_blob_ids: None,
1535            mdn_blob_ids: None,
1536        };
1537
1538        let json = serde_json::to_string(&submission).unwrap();
1539        assert!(json.contains("\"id\":\"sub1\""));
1540        assert!(json.contains("\"undoStatus\":\"pending\""));
1541    }
1542
1543    #[tokio::test]
1544    async fn test_email_submission_with_all_fields() {
1545        let mut delivery = HashMap::new();
1546        delivery.insert(
1547            "recipient@example.com".to_string(),
1548            DeliveryStatus {
1549                smtp_reply: "250 OK".to_string(),
1550                delivered: DeliveryState::Yes,
1551                displayed: DisplayedState::Unknown,
1552            },
1553        );
1554
1555        let submission = EmailSubmission {
1556            id: "sub1".to_string(),
1557            identity_id: "id1".to_string(),
1558            email_id: "email1".to_string(),
1559            thread_id: Some("thread1".to_string()),
1560            envelope: Some(Envelope {
1561                mail_from: Address {
1562                    email: "sender@example.com".to_string(),
1563                    parameters: None,
1564                },
1565                rcpt_to: vec![Address {
1566                    email: "recipient@example.com".to_string(),
1567                    parameters: None,
1568                }],
1569            }),
1570            send_at: Some(Utc::now()),
1571            undo_status: UndoStatus::Final,
1572            delivery_status: Some(delivery),
1573            dsn_blob_ids: Some(vec!["blob1".to_string()]),
1574            mdn_blob_ids: Some(vec!["blob2".to_string()]),
1575        };
1576
1577        let json = serde_json::to_string(&submission).unwrap();
1578        assert!(json.contains("\"id\":\"sub1\""));
1579        assert!(json.contains("\"deliveryStatus\""));
1580    }
1581
1582    #[tokio::test]
1583    async fn test_email_submission_query_zero_limit() {
1584        let store = create_test_store();
1585        let request = EmailSubmissionQueryRequest {
1586            account_id: "acc1".to_string(),
1587            filter: None,
1588            sort: None,
1589            position: None,
1590            limit: Some(0),
1591            calculate_total: Some(true),
1592        };
1593
1594        let response = email_submission_query(request, store.as_ref())
1595            .await
1596            .unwrap();
1597        assert_eq!(response.limit, Some(0));
1598    }
1599
1600    #[tokio::test]
1601    async fn test_email_submission_delayed_send_far_future() {
1602        let store = create_test_store();
1603        let mut create_map = HashMap::new();
1604        let send_at = Utc::now() + chrono::Duration::days(365);
1605
1606        create_map.insert(
1607            "future1".to_string(),
1608            EmailSubmissionObject {
1609                identity_id: "id1".to_string(),
1610                email_id: "email1".to_string(),
1611                envelope: None,
1612                send_at: Some(send_at),
1613            },
1614        );
1615
1616        let request = EmailSubmissionSetRequest {
1617            account_id: "acc1".to_string(),
1618            if_in_state: None,
1619            create: Some(create_map),
1620            update: None,
1621            destroy: None,
1622            on_success_update_email: None,
1623            on_success_destroy_email: None,
1624        };
1625
1626        let response = email_submission_set(request, store.as_ref()).await.unwrap();
1627        assert!(response.not_created.is_some());
1628    }
1629}