feat(mailer-smtp): add SCRAM-SHA-256 auth, Ed25519 DKIM, opportunistic TLS, SNI cert selection, pipelining and delivery/bridge improvements
This commit is contained in:
@@ -318,6 +318,54 @@ pub async fn send_rcpt_to(
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
/// Send MAIL FROM + RCPT TO commands in a single pipelined batch.
|
||||
///
|
||||
/// Writes all envelope commands at once, then reads responses in order.
|
||||
/// Returns `(mail_from_ok, accepted_recipients, rejected_recipients)`.
|
||||
pub async fn send_pipelined_envelope(
|
||||
stream: &mut ClientSmtpStream,
|
||||
sender: &str,
|
||||
recipients: &[String],
|
||||
timeout_secs: u64,
|
||||
) -> Result<(bool, Vec<String>, Vec<String>), SmtpClientError> {
|
||||
// Build the full pipelined command batch
|
||||
let mut batch = format!("MAIL FROM:<{sender}>\r\n");
|
||||
for rcpt in recipients {
|
||||
batch.push_str(&format!("RCPT TO:<{rcpt}>\r\n"));
|
||||
}
|
||||
|
||||
// Send all commands at once
|
||||
debug!("SMTP C (pipelined): MAIL FROM + {} RCPT TO", recipients.len());
|
||||
stream.write_all(batch.as_bytes()).await?;
|
||||
stream.flush().await?;
|
||||
|
||||
// Read MAIL FROM response
|
||||
let mail_resp = read_response(stream, timeout_secs).await?;
|
||||
if !mail_resp.is_success() {
|
||||
return Err(mail_resp.to_error());
|
||||
}
|
||||
|
||||
// Read RCPT TO responses
|
||||
let mut accepted = Vec::new();
|
||||
let mut rejected = Vec::new();
|
||||
for rcpt in recipients {
|
||||
match read_response(stream, timeout_secs).await {
|
||||
Ok(resp) => {
|
||||
if resp.is_success() {
|
||||
accepted.push(rcpt.clone());
|
||||
} else {
|
||||
rejected.push(rcpt.clone());
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
rejected.push(rcpt.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok((true, accepted, rejected))
|
||||
}
|
||||
|
||||
/// Send DATA command, followed by the message body with dot-stuffing.
|
||||
pub async fn send_data(
|
||||
stream: &mut ClientSmtpStream,
|
||||
|
||||
Reference in New Issue
Block a user