fix: use max_smtp_rcpt_to chunking for the actual transport we are sending from

removes another `ConfiguredAddr` usage
and a bug that the wrong max smtp recipients setting was used.
This commit is contained in:
holger krekel
2026-09-18 22:55:41 +02:00
parent 0a7e697f50
commit 7876115d86
5 changed files with 45 additions and 44 deletions

View File

@@ -568,11 +568,9 @@ impl Context {
self.scheduler.maybe_network().await;
}
/// Returns maximum number of recipients a single email can be sent to.
pub(crate) async fn get_max_smtp_rcpt_to(&self) -> Result<u32> {
let Some((transport_id, param)) = ConfiguredLoginParam::load(self).await? else {
bail!("Not configured");
};
/// Returns maximum number of recipients a single email can be sent to
/// over the transport `transport_id`, which sends from `addr`.
pub(crate) async fn get_max_smtp_rcpt_to(&self, transport_id: u32, addr: &str) -> Result<u32> {
let metadata_limit = self
.metadata
.read()
@@ -582,9 +580,7 @@ impl Context {
if let Some(limit) = metadata_limit {
return Ok(limit);
}
if let Some(limit) =
crate::provider::legacy_settings_for_addr(&param.addr)?.max_smtp_rcpt_to
{
if let Some(limit) = crate::provider::legacy_settings_for_addr(addr)?.max_smtp_rcpt_to {
return Ok(limit);
}
Ok(constants::DEFAULT_MAX_SMTP_RCPT_TO)

View File

@@ -5,12 +5,35 @@ use tempfile::tempdir;
use super::*;
use crate::chat::{Chat, MuteDuration, get_chat_contacts, get_chat_msgs, send_msg, set_muted};
use crate::chatlist::Chatlist;
use crate::constants::Chattype;
use crate::constants::{Chattype, DEFAULT_MAX_SMTP_RCPT_TO};
use crate::message::Message;
use crate::receive_imf::receive_imf;
use crate::test_utils::{E2EE_INFO_MSGS, TestContext, TestContextManager};
use crate::tools::{SystemTime, create_outgoing_rfc724_mid};
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_get_max_smtp_rcpt_to() -> Result<()> {
let t = TestContext::new().await;
assert_eq!(
t.get_max_smtp_rcpt_to(2, "alice@example.org").await?,
DEFAULT_MAX_SMTP_RCPT_TO
);
for (transport_id, limit) in [(1, 3), (2, 7)] {
t.metadata.write().await.insert(
transport_id,
ServerMetadata {
max_smtp_rcpt_to: Some(limit),
..Default::default()
},
);
}
assert_eq!(t.get_max_smtp_rcpt_to(2, "alice@example.org").await?, 7);
assert_eq!(t.get_max_smtp_rcpt_to(1, "alice@example.org").await?, 3);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_wrong_db() -> Result<()> {
let tmp = tempfile::tempdir()?;

View File

@@ -41,6 +41,9 @@ pub(crate) struct Smtp {
/// Email address we are sending from.
from: Option<EmailAddress>,
/// Transport we are connected to.
transport_id: Option<u32>,
/// Timestamp of last successful send/receive network interaction
/// (eg connect or send succeeded). On initialization and disconnect
/// it is set to None.
@@ -116,7 +119,10 @@ impl Smtp {
)
.await
{
Ok(()) => return Ok(()),
Ok(()) => {
self.transport_id = Some(transport_id);
return Ok(());
}
Err(err) => {
warn!(
context,
@@ -466,6 +472,12 @@ pub(crate) async fn send_msg_to_smtp(
.context("No From address available, likely not connected")?
.to_string();
let transport_id = smtp.transport_id.context("Not connected")?;
let chunk_size = context
.get_max_smtp_rcpt_to(transport_id, &from_addr)
.await?
.max(1);
let rendered_mail =
mimefactory::render_queued_mail(queued_mail, &public_key, &secret_key, from_addr)?;
let body = rendered_mail.message;
@@ -474,8 +486,6 @@ pub(crate) async fn send_msg_to_smtp(
context,
"Try number {retries} to send message {msg_id} (entry {rowid}) over SMTP."
);
let chunk_size = context.get_max_smtp_rcpt_to().await?.max(1);
let mut unsent = recipients_list.as_slice();
let status = loop {
let unsent_len = u32::try_from(unsent.len()).context("Too many SMTP recipients")?;

View File

@@ -11,7 +11,7 @@
use std::fmt;
use std::sync::atomic::Ordering;
use anyhow::{Context as _, Result, bail, format_err};
use anyhow::{Context as _, Result, format_err};
use deltachat_contact_tools::{EmailAddress, addr_normalize};
use rusqlite::OptionalExtension;
use serde::{Deserialize, Serialize};
@@ -260,34 +260,6 @@ impl fmt::Display for ConfiguredLoginParam {
}
impl ConfiguredLoginParam {
/// Load configured account settings from the database.
///
/// Returns transport ID and configured parameters
/// of the transport currently used for sending.
/// Returns `None` if account is not configured.
pub(crate) async fn load(context: &Context) -> Result<Option<(u32, Self)>> {
let Some(self_addr) = context.get_config(Config::ConfiguredAddr).await? else {
return Ok(None);
};
let Some((id, json)) = context
.sql
.query_row_optional(
"SELECT id, configured_param FROM transports WHERE addr=?",
(&self_addr,),
|row| {
let id: u32 = row.get(0)?;
let json: String = row.get(1)?;
Ok((id, json))
},
)
.await?
else {
bail!("Self address {self_addr} doesn't have a corresponding transport");
};
Ok(Some((id, Self::from_json(&json)?)))
}
/// Loads configured login parameters for all transports.
///
/// Returns a vector of all transport IDs

View File

@@ -62,7 +62,7 @@ async fn test_save_load_login_param() -> Result<()> {
expected_param
);
assert_eq!(t.is_configured().await?, true);
let (_transport_id, loaded) = ConfiguredLoginParam::load(&t).await?.unwrap();
let (_transport_id, loaded) = ConfiguredLoginParam::load_all(&t).await?.remove(0);
assert_eq!(param, loaded);
let formatted = format!(" {loaded}");
@@ -75,7 +75,7 @@ async fn test_save_load_login_param() -> Result<()> {
// Legacy ConfiguredImapCertificateChecks config is ignored
t.set_config(Config::ConfiguredImapCertificateChecks, Some("999"))
.await?;
assert!(ConfiguredLoginParam::load(&t).await.is_ok());
assert!(ConfiguredLoginParam::load_all(&t).await.is_ok());
// Test that we don't panic on unknown ConfiguredImapCertificateChecks values.
let wrong_param = expected_param.replace("Strict", "Stricct");
@@ -83,7 +83,7 @@ async fn test_save_load_login_param() -> Result<()> {
t.sql
.execute("UPDATE transports SET configured_param=?", (wrong_param,))
.await?;
assert!(ConfiguredLoginParam::load(&t).await.is_err());
assert!(ConfiguredLoginParam::load_all(&t).await.is_err());
Ok(())
}