io_smtp/rfc3461/parameter.rs
1//! DSN ESMTP parameter constructors for MAIL FROM and RCPT TO.
2//!
3//! # Example
4//!
5//! ```rust,ignore
6//! use io_smtp::{
7//! rfc3461::parameter::{SmtpDsnNotify, SmtpDsnRet},
8//! rfc5321::{mail::SmtpMail, rcpt::SmtpRcpt, types::parameter::SmtpParameter},
9//! };
10//!
11//! // NOTE: MAIL FROM with RET=HDRS and ENVID
12//! let params = vec![SmtpDsnRet::Hdrs.into_parameter(), SmtpParameter::envid("abc123")];
13//! let coroutine = SmtpMail::new(reverse_path, params);
14//!
15//! // NOTE: RCPT TO with NOTIFY=SUCCESS,FAILURE
16//! let params = vec![(SmtpDsnNotify::SUCCESS | SmtpDsnNotify::FAILURE).into_parameter()];
17//! let coroutine = SmtpRcpt::new(forward_path, params);
18//! ```
19
20use core::ops::BitOr;
21
22use alloc::{borrow::Cow, string::String, vec::Vec};
23
24use crate::rfc5321::{SmtpAtom, SmtpParameter};
25
26/// The value of the `RET` ESMTP parameter on `MAIL FROM`.
27///
28/// Controls how much of the original message is included in a DSN.
29///
30/// # Reference
31///
32/// RFC 3461 §4.3
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum SmtpDsnRet {
35 /// Include the full original message in any DSN.
36 Full,
37 /// Include only the headers of the original message in any DSN.
38 Hdrs,
39}
40
41impl SmtpDsnRet {
42 /// Build the `RET=FULL` or `RET=HDRS` [`SmtpParameter`] for `MAIL FROM`.
43 pub fn into_parameter(self) -> SmtpParameter<'static> {
44 let value = match self {
45 Self::Full => "FULL",
46 Self::Hdrs => "HDRS",
47 };
48 SmtpParameter {
49 keyword: SmtpAtom(Cow::Borrowed("RET")),
50 value: Some(Cow::Borrowed(value)),
51 }
52 }
53}
54
55/// DSN parameter constructors for `MAIL FROM` and `RCPT TO`
56/// (RFC 3461).
57impl SmtpParameter<'static> {
58 /// Build the `ENVID=<id>` parameter for `MAIL FROM`.
59 ///
60 /// The envelope identifier is an opaque string chosen by the
61 /// sender that uniquely identifies this mail transaction. It is
62 /// included in any DSN generated for the message.
63 ///
64 /// The value must contain only printable US-ASCII characters
65 /// excluding `=` and whitespace (xtext encoding, RFC 3461 §4).
66 ///
67 /// # Reference
68 ///
69 /// RFC 3461 §4.4
70 pub fn envid(id: impl Into<String>) -> SmtpParameter<'static> {
71 SmtpParameter {
72 keyword: SmtpAtom(Cow::Borrowed("ENVID")),
73 value: Some(Cow::Owned(id.into())),
74 }
75 }
76}
77
78/// The `NOTIFY` conditions for a single `RCPT TO`.
79///
80/// Flags may be combined (e.g. `SmtpDsnNotify::SUCCESS |
81/// SmtpDsnNotify::FAILURE`), except that `NEVER` must not be combined
82/// with any other value.
83///
84/// # Reference
85///
86/// RFC 3461 §4.1
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88pub struct SmtpDsnNotify(u8);
89
90impl SmtpDsnNotify {
91 /// Never send a DSN for this recipient.
92 pub const NEVER: Self = Self(0);
93 /// Send a DSN on successful delivery.
94 pub const SUCCESS: Self = Self(1);
95 /// Send a DSN on delivery failure.
96 pub const FAILURE: Self = Self(2);
97 /// Send a DSN if delivery is delayed.
98 pub const DELAY: Self = Self(4);
99
100 /// Combine two notify flags.
101 #[must_use]
102 pub const fn or(self, other: Self) -> Self {
103 Self(self.0 | other.0)
104 }
105
106 /// Build the `NOTIFY=...` [`SmtpParameter`] for `RCPT TO`.
107 pub fn into_parameter(self) -> SmtpParameter<'static> {
108 let value = if self.0 == 0 {
109 Cow::Borrowed("NEVER")
110 } else {
111 let mut parts: Vec<&str> = Vec::new();
112 if self.0 & 1 != 0 {
113 parts.push("SUCCESS");
114 }
115 if self.0 & 2 != 0 {
116 parts.push("FAILURE");
117 }
118 if self.0 & 4 != 0 {
119 parts.push("DELAY");
120 }
121 Cow::Owned(parts.join(","))
122 };
123 SmtpParameter {
124 keyword: SmtpAtom(Cow::Borrowed("NOTIFY")),
125 value: Some(value),
126 }
127 }
128}
129
130impl BitOr for SmtpDsnNotify {
131 type Output = Self;
132 fn bitor(self, rhs: Self) -> Self {
133 Self(self.0 | rhs.0)
134 }
135}
136
137/// Original recipient parameter constructor for `RCPT TO`
138/// (RFC 3461).
139impl SmtpParameter<'static> {
140 /// Build the `ORCPT=rfc822;<address>` parameter for `RCPT TO`.
141 ///
142 /// Specifies the original recipient address, before any aliasing
143 /// or forwarding, so that DSNs can reference it.
144 ///
145 /// # Reference
146 ///
147 /// RFC 3461 §4.2
148 pub fn orcpt_rfc822(address: impl Into<String>) -> SmtpParameter<'static> {
149 let value = {
150 let mut s = String::from("rfc822;");
151 s.push_str(&address.into());
152 s
153 };
154 SmtpParameter {
155 keyword: SmtpAtom(Cow::Borrowed("ORCPT")),
156 value: Some(Cow::Owned(value)),
157 }
158 }
159}