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:
@@ -116,6 +116,7 @@ impl ConnectionPool {
|
||||
&self.config.host,
|
||||
self.config.port,
|
||||
self.config.connection_timeout_secs,
|
||||
self.config.tls_opportunistic,
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
@@ -139,7 +140,7 @@ impl ConnectionPool {
|
||||
if !self.config.secure && capabilities.starttls {
|
||||
protocol::send_starttls(&mut stream, self.config.socket_timeout_secs).await?;
|
||||
stream =
|
||||
super::connection::upgrade_to_tls(stream, &self.config.host).await?;
|
||||
super::connection::upgrade_to_tls(stream, &self.config.host, self.config.tls_opportunistic).await?;
|
||||
|
||||
// Re-EHLO after STARTTLS — use updated capabilities for auth
|
||||
capabilities = protocol::send_ehlo(
|
||||
@@ -244,9 +245,10 @@ impl SmtpClientManager {
|
||||
protocol::send_rset(&mut conn.stream, config.socket_timeout_secs).await?;
|
||||
}
|
||||
|
||||
// Perform the SMTP transaction
|
||||
// Perform the SMTP transaction (use pipelining if server supports it)
|
||||
let pipelining = conn.capabilities.pipelining;
|
||||
let result =
|
||||
Self::perform_send(&mut conn.stream, sender, recipients, message, config).await;
|
||||
Self::perform_send(&mut conn.stream, sender, recipients, message, config, pipelining).await;
|
||||
|
||||
// Re-acquire the pool lock and release the connection
|
||||
let mut pool = pool_arc.lock().await;
|
||||
@@ -268,30 +270,39 @@ impl SmtpClientManager {
|
||||
recipients: &[String],
|
||||
message: &[u8],
|
||||
config: &SmtpClientConfig,
|
||||
pipelining: bool,
|
||||
) -> Result<SmtpSendResult, SmtpClientError> {
|
||||
let timeout_secs = config.socket_timeout_secs;
|
||||
|
||||
// MAIL FROM
|
||||
protocol::send_mail_from(stream, sender, timeout_secs).await?;
|
||||
let (accepted, rejected) = if pipelining {
|
||||
// Use pipelined envelope: MAIL FROM + all RCPT TO in one batch
|
||||
let (_mail_ok, acc, rej) = protocol::send_pipelined_envelope(
|
||||
stream, sender, recipients, timeout_secs,
|
||||
).await?;
|
||||
(acc, rej)
|
||||
} else {
|
||||
// Sequential: MAIL FROM, then each RCPT TO
|
||||
protocol::send_mail_from(stream, sender, timeout_secs).await?;
|
||||
|
||||
// RCPT TO for each recipient
|
||||
let mut accepted = Vec::new();
|
||||
let mut rejected = Vec::new();
|
||||
let mut accepted = Vec::new();
|
||||
let mut rejected = Vec::new();
|
||||
|
||||
for rcpt in recipients {
|
||||
match protocol::send_rcpt_to(stream, rcpt, timeout_secs).await {
|
||||
Ok(resp) => {
|
||||
if resp.is_success() {
|
||||
accepted.push(rcpt.clone());
|
||||
} else {
|
||||
for rcpt in recipients {
|
||||
match protocol::send_rcpt_to(stream, rcpt, timeout_secs).await {
|
||||
Ok(resp) => {
|
||||
if resp.is_success() {
|
||||
accepted.push(rcpt.clone());
|
||||
} else {
|
||||
rejected.push(rcpt.clone());
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
rejected.push(rcpt.clone());
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
rejected.push(rcpt.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
(accepted, rejected)
|
||||
};
|
||||
|
||||
// If no recipients were accepted, fail
|
||||
if accepted.is_empty() {
|
||||
@@ -339,6 +350,7 @@ impl SmtpClientManager {
|
||||
&config.host,
|
||||
config.port,
|
||||
config.connection_timeout_secs,
|
||||
config.tls_opportunistic,
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user