Skip to main content

jmap_mail_types/
submission.rs

1//! [`EmailSubmission`] and related types for RFC 8621 §7.
2//!
3//! Covers the SMTP envelope ([`Envelope`], [`Address`]), per-recipient delivery
4//! status ([`DeliveryStatus`], [`Delivered`], [`Displayed`]), undo tracking
5//! ([`UndoStatus`]), and the [`EmailSubmission`] object itself.
6//!
7//! Also defines [`EmailSubmissionFilterCondition`] for EmailSubmission/query
8//! (RFC 8621 §7.3); the `EmailSubmissionFilter` type alias lives in
9//! [`crate::query`].
10
11use std::collections::HashMap;
12
13use jmap_types::{Id, UTCDate};
14use serde::{Deserialize, Serialize};
15
16/// SMTP envelope address with optional MAIL FROM / RCPT TO parameters (RFC 8621 §7).
17///
18/// Used in both `mailFrom` and the elements of `rcptTo` within an [`Envelope`].
19#[non_exhaustive]
20#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
21#[serde(rename_all = "camelCase")]
22pub struct Address {
23    /// The email address (Mailbox as per RFC 5321 Reverse-path / Forward-path).
24    pub email: String,
25    /// Optional SMTP parameters (mail-parameter or rcpt-parameter per RFC 5321).
26    ///
27    /// Each key is a parameter name; the value is the parameter value string, or
28    /// `None` if the parameter takes no value.  xtext / unitext encodings are
29    /// stripped; JSON string encoding applies.
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub parameters: Option<HashMap<String, Option<String>>>,
32}
33
34impl Address {
35    /// Construct an [`Address`] with no SMTP parameters.
36    pub fn new(email: impl Into<String>) -> Self {
37        Self {
38            email: email.into(),
39            parameters: None,
40        }
41    }
42}
43
44/// SMTP envelope for an [`EmailSubmission`] (RFC 8621 §7).
45///
46/// Carries the return address and recipient list used in the SMTP dialogue.
47/// If omitted on creation the server derives it from the Email headers.
48#[non_exhaustive]
49#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
50#[serde(rename_all = "camelCase")]
51pub struct Envelope {
52    /// Return address for the SMTP MAIL FROM command.
53    pub mail_from: Address,
54    /// Recipient addresses for SMTP RCPT TO commands.
55    pub rcpt_to: Vec<Address>,
56}
57
58impl Envelope {
59    /// Construct an [`Envelope`] from a return address and recipient list.
60    pub fn new(mail_from: Address, rcpt_to: Vec<Address>) -> Self {
61        Self { mail_from, rcpt_to }
62    }
63}
64
65/// Delivery status of a message to a recipient (RFC 8621 §7, `delivered` field).
66#[derive(Debug, Clone, PartialEq, Eq, Hash)]
67#[non_exhaustive]
68pub enum Delivered {
69    /// The message is in a local mail queue and the status is not yet known.
70    Queued,
71    /// The message was successfully delivered to the mail store of the recipient.
72    Yes,
73    /// Delivery failed; the `smtp_reply` field contains the failure reason.
74    No,
75    /// The final delivery status is unknown.
76    Unknown,
77    /// An unrecognised value was received from the server.
78    ///
79    /// The inner string retains the original value so this variant round-trips correctly.
80    Other(String),
81}
82
83impl_string_enum!(Delivered, "a delivery status string",
84    "queued"  => Queued,
85    "yes"     => Yes,
86    "no"      => No,
87    "unknown" => Unknown,
88);
89
90/// Display status of a message to a recipient (RFC 8621 §7, `displayed` field).
91#[derive(Debug, Clone, PartialEq, Eq, Hash)]
92#[non_exhaustive]
93pub enum Displayed {
94    /// The display status is unknown.
95    Unknown,
96    /// The message has been displayed to the recipient at least once.
97    Yes,
98    /// An unrecognised value was received from the server.
99    ///
100    /// The inner string retains the original value so this variant round-trips correctly.
101    Other(String),
102}
103
104impl_string_enum!(Displayed, "a display status string",
105    "unknown" => Unknown,
106    "yes"     => Yes,
107);
108
109/// Whether an [`EmailSubmission`] may still be canceled (RFC 8621 §7).
110#[derive(Debug, Clone, PartialEq, Eq, Hash)]
111#[non_exhaustive]
112pub enum UndoStatus {
113    /// The message has not yet been relayed; cancellation may be possible.
114    Pending,
115    /// The message has been relayed to at least one recipient and cannot be recalled.
116    Final,
117    /// The submission was canceled and will not be delivered to any recipient.
118    Canceled,
119    /// An unrecognised value was received from the server.
120    ///
121    /// The inner string retains the original value so this variant round-trips correctly.
122    Other(String),
123}
124
125impl_string_enum!(UndoStatus, "an undo status string",
126    "pending"  => Pending,
127    "final"    => Final,
128    "canceled" => Canceled,
129);
130
131/// Per-recipient delivery status for an [`EmailSubmission`] (RFC 8621 §7).
132#[non_exhaustive]
133#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
134#[serde(rename_all = "camelCase")]
135pub struct DeliveryStatus {
136    /// The SMTP reply string returned when the server last attempted relay,
137    /// or from a later DSN (RFC 3464).  Multi-line responses are concatenated
138    /// into a single string.
139    pub smtp_reply: String,
140    /// Whether the message reached the recipient's mail store.
141    pub delivered: Delivered,
142    /// Whether the message has been displayed to the recipient.
143    pub displayed: Displayed,
144}
145
146impl DeliveryStatus {
147    /// Construct a [`DeliveryStatus`] from its three required fields.
148    pub fn new(smtp_reply: impl Into<String>, delivered: Delivered, displayed: Displayed) -> Self {
149        Self {
150            smtp_reply: smtp_reply.into(),
151            delivered,
152            displayed,
153        }
154    }
155}
156
157/// Represents the submission of an Email for delivery (RFC 8621 §7).
158#[non_exhaustive]
159#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
160#[serde(rename_all = "camelCase")]
161pub struct EmailSubmission {
162    /// Server-assigned immutable identifier for this submission.
163    pub id: Id,
164    /// Id of the Identity used to send this submission.
165    pub identity_id: Id,
166    /// Id of the Email being submitted.
167    pub email_id: Id,
168    /// Thread id of the submitted Email (server-set).
169    pub thread_id: Id,
170    /// SMTP envelope; server-derived from Email headers when absent on creation.
171    #[serde(skip_serializing_if = "Option::is_none")]
172    pub envelope: Option<Envelope>,
173    /// UTC timestamp when the submission was / will be released for delivery.
174    pub send_at: UTCDate,
175    /// Whether the submission may still be canceled.
176    pub undo_status: UndoStatus,
177    /// Per-recipient delivery status, keyed by recipient email address.
178    ///
179    /// `None` when the server does not support delivery-status tracking.
180    #[serde(skip_serializing_if = "Option::is_none")]
181    pub delivery_status: Option<HashMap<String, DeliveryStatus>>,
182    /// Blob ids of DSN messages (RFC 3464) received for this submission.
183    ///
184    /// Always present in serialized output (empty array when no DSN has been received);
185    /// RFC 8621 §7 requires these fields in responses.  Do not add `skip_serializing_if`.
186    pub dsn_blob_ids: Vec<Id>,
187    /// Blob ids of MDN messages (RFC 8098) received for this submission.
188    ///
189    /// Always present in serialized output; same rationale as `dsn_blob_ids`.
190    pub mdn_blob_ids: Vec<Id>,
191}
192
193impl EmailSubmission {
194    /// Construct an [`EmailSubmission`] from its required fields.
195    ///
196    /// `envelope` and `delivery_status` default to `None`.
197    /// `dsn_blob_ids` and `mdn_blob_ids` default to empty.
198    pub fn new(
199        id: Id,
200        identity_id: Id,
201        email_id: Id,
202        thread_id: Id,
203        send_at: UTCDate,
204        undo_status: UndoStatus,
205    ) -> Self {
206        Self {
207            id,
208            identity_id,
209            email_id,
210            thread_id,
211            envelope: None,
212            send_at,
213            undo_status,
214            delivery_status: None,
215            dsn_blob_ids: Vec::new(),
216            mdn_blob_ids: Vec::new(),
217        }
218    }
219}
220
221// ---------------------------------------------------------------------------
222// EmailSubmission/query filter (RFC 8621 §7.3)
223// ---------------------------------------------------------------------------
224
225/// Filter condition for EmailSubmission/query (RFC 8621 §7.3).
226///
227/// All fields are optional.  If zero properties are specified, the condition
228/// evaluates to `true` for every submission.
229///
230/// RFC 8621 §7.3 uses the standard `/query` mechanism (RFC 8620 §5.5), so
231/// `EmailSubmissionFilterCondition` can be used inside a
232/// `Filter<EmailSubmissionFilterCondition>` to combine conditions with
233/// logical operators.
234#[non_exhaustive]
235#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
236#[serde(rename_all = "camelCase")]
237pub struct EmailSubmissionFilterCondition {
238    /// The submission's `identityId` must be in this list.
239    #[serde(skip_serializing_if = "Option::is_none")]
240    pub identity_ids: Option<Vec<Id>>,
241
242    /// The submission's `emailId` must be in this list.
243    #[serde(skip_serializing_if = "Option::is_none")]
244    pub email_ids: Option<Vec<Id>>,
245
246    /// The submission's `threadId` must be in this list.
247    #[serde(skip_serializing_if = "Option::is_none")]
248    pub thread_ids: Option<Vec<Id>>,
249
250    /// The submission's `undoStatus` must equal this value.
251    #[serde(skip_serializing_if = "Option::is_none")]
252    pub undo_status: Option<UndoStatus>,
253
254    /// The `sendAt` of the submission must be before this date-time.
255    #[serde(skip_serializing_if = "Option::is_none")]
256    pub before: Option<UTCDate>,
257
258    /// The `sendAt` of the submission must be on or after this date-time.
259    #[serde(skip_serializing_if = "Option::is_none")]
260    pub after: Option<UTCDate>,
261}