From b59eebf1e7d03bb65e98f2da348ee662debb7acb Mon Sep 17 00:00:00 2001 From: John Coffey Date: Mon, 28 Sep 2026 18:48:36 -0700 Subject: [PATCH] Submissions say when DLP held the message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An EmailSubmission create's response carries inbuxa:held (dlp-and-mail-flow-rules spec, §2.6, §4): true when the message is held for review, false otherwise, so the webmail can say so at once. A sender can't read the review queue, and a held message's sendAt is its real send time, not the century-off release, so this is how the sender learns. mail_rules_tests checks both values. --- crates/jmap-proto/src/object/email_submission.rs | 5 +++++ crates/jmap/src/submission/set.rs | 10 +++++++++- tests/src/system/mail_rules.rs | 2 ++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/crates/jmap-proto/src/object/email_submission.rs b/crates/jmap-proto/src/object/email_submission.rs index f16c4c7..792a246 100644 --- a/crates/jmap-proto/src/object/email_submission.rs +++ b/crates/jmap-proto/src/object/email_submission.rs @@ -45,6 +45,9 @@ pub enum EmailSubmissionProperty { // inbuxa: DLP (dlp-and-mail-flow-rules spec, §2.5): `{"reason": ...}` // to send despite a warning DlpOverride, + // inbuxa: in a create's response, true when DLP held the message for + // review (§2.6) + DlpHeld, Pointer(JsonPointer), } @@ -96,6 +99,7 @@ impl Property for EmailSubmissionProperty { EmailSubmissionProperty::IdentityId => "identityId", EmailSubmissionProperty::MdnBlobIds => "mdnBlobIds", EmailSubmissionProperty::DlpOverride => "inbuxa:dlpOverride", + EmailSubmissionProperty::DlpHeld => "inbuxa:held", EmailSubmissionProperty::SendAt => "sendAt", EmailSubmissionProperty::ThreadId => "threadId", EmailSubmissionProperty::UndoStatus => "undoStatus", @@ -188,6 +192,7 @@ impl EmailSubmissionProperty { "dsnBlobIds" => EmailSubmissionProperty::DsnBlobIds, "mdnBlobIds" => EmailSubmissionProperty::MdnBlobIds, "inbuxa:dlpOverride" => EmailSubmissionProperty::DlpOverride, + "inbuxa:held" => EmailSubmissionProperty::DlpHeld, ) .or_else(|| { if allow_patch && value.contains('/') { diff --git a/crates/jmap/src/submission/set.rs b/crates/jmap/src/submission/set.rs index 055ab30..2f83dfe 100644 --- a/crates/jmap/src/submission/set.rs +++ b/crates/jmap/src/submission/set.rs @@ -91,6 +91,13 @@ impl EmailSubmissionSet for Server { ); let send_at = submission.send_at; + // inbuxa: DLP (§2.6): the sender learns it's held + let held = match submission.queue_id { + Some(queue_id) => { + inbuxa_features::mailflow::held::is_held(self.store(), queue_id).await? + } + None => false, + }; let undo_status = match submission.undo_status { UndoStatus::Pending => email_submission::UndoStatus::Pending, UndoStatus::Final => email_submission::UndoStatus::Final, @@ -128,7 +135,8 @@ impl EmailSubmissionSet for Server { .with_key_value( EmailSubmissionProperty::UndoStatus, Value::Element(EmailSubmissionValue::UndoStatus(undo_status)), - ), + ) + .with_key_value(EmailSubmissionProperty::DlpHeld, Value::Bool(held)), ), ); } diff --git a/tests/src/system/mail_rules.rs b/tests/src/system/mail_rules.rs index 842d8f5..faff97e 100644 --- a/tests/src/system/mail_rules.rs +++ b/tests/src/system/mail_rules.rs @@ -312,6 +312,7 @@ pub async fn dlp(test: &mut TestServer) { response["created"].get("s").is_some(), "no rules: {response}" ); + assert_eq!(response["created"]["s"]["inbuxa:held"], false, "{response}"); // A warning: refused with the rule and its notice, then sent with a reason let (_, response) = call( @@ -833,6 +834,7 @@ pub async fn hold(test: &mut TestServer) { .as_str() .unwrap_or_else(|| panic!("held, not refused: {response}")) .to_string(); + assert_eq!(response["created"]["s"]["inbuxa:held"], true, "{response}"); let (_, response) = call(&admin, "inbuxa:HeldMessage/get", json!({"ids": null})).await; let list = response["list"] .as_array()