api!: remove is_chatmail and the XCHATMAIL capability

This removes the last usages of the deprecated `is_chatmail` and
no `XCHATMAIL` IMAP capability is read anymore.
UIs are not using it for a longer time anymore.

The self-reporting statistics now set `is_chatmail` field to
`true`: all transports have relay-typical metadata
`false`: at least one doesn't
`null`: we don't know

BREAKING CHANGE: `is_chatmail` is no longer a known config key.
This commit is contained in:
holger krekel
2026-09-17 16:29:37 +00:00
parent 57b2c000e6
commit 96e84d85a5
15 changed files with 41 additions and 83 deletions

View File

@@ -461,10 +461,6 @@ char* dc_get_blobdir (const dc_context_t* context);
* - `gossip_period` = How often to gossip Autocrypt keys in chats with multiple recipients, in
* seconds. 2 days by default.
* This is not supposed to be changed by UIs and only used for testing.
* - `is_chatmail` = (deprecated) 1 if the the server is a chatmail server, 0 otherwise.
* This is deprecated, UIs should not behave differently
* for chatmail relays and classical email servers.
* Most usages in UIs can be replaced by `force_encryption`.
* - `is_muted` = Whether a context is muted by the user.
* Muted contexts should not sound, vibrate or show notifications.
* In contrast to `dc_set_chat_mute_duration()`,

View File

@@ -421,7 +421,6 @@ def test_dont_move_sync_msgs(acf, direct_imap):
addr, password = acf.get_credentials()
ac1 = acf.get_unconfigured_account()
ac1.set_config("bcc_self", "1")
ac1.set_config("fix_is_chatmail", "1")
ac1.add_or_update_transport({"addr": addr, "password": password})
ac1.start_io()
ac1_direct_imap = direct_imap(ac1)

View File

@@ -305,18 +305,6 @@ pub enum Config {
/// True if account is configured.
Configured,
/// Deprecated, we are trying to get rid of this global setting.
/// It is possible to configure a profile with both chatmail relays
/// and classical email servers.
///
/// Most usages in UIs can be replaced by `force_encryption`.
///
/// True if account is a chatmail account.
IsChatmail,
/// True if `IsChatmail` mustn't be autoconfigured. For tests.
FixIsChatmail,
/// True if account is muted.
IsMuted,

View File

@@ -490,17 +490,6 @@ pub(crate) async fn configure(
progress!(ctx, 900);
let is_configured = ctx.is_configured().await?;
if !ctx.get_config_bool(Config::FixIsChatmail).await? {
if imap_session.is_chatmail() {
ctx.sql.set_raw_config("is_chatmail", Some("1")).await?;
} else if !is_configured {
// Reset the setting that may have been set
// during failed configuration.
ctx.sql.set_raw_config("is_chatmail", Some("0")).await?;
}
}
// Drop the imap connection explicitly
// to make sure that it's not forgotten in a future refactoring
drop(imap_session);

View File

@@ -568,15 +568,6 @@ impl Context {
self.scheduler.maybe_network().await;
}
/// Deprecated, we are trying to get rid of this global setting.
/// It is possible to configure a profile with both chatmail relays
/// and classical email servers.
///
/// Returns true if an account is on a chatmail server.
pub async fn is_chatmail(&self) -> Result<bool> {
self.get_config_bool(Config::IsChatmail).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 {
@@ -885,13 +876,6 @@ impl Context {
res.insert("imap_server_id", format!("{server_id:?}"));
}
res.insert("is_chatmail", self.is_chatmail().await?.to_string());
res.insert(
"fix_is_chatmail",
self.get_config_bool(Config::FixIsChatmail)
.await?
.to_string(),
);
res.insert(
"is_muted",
self.get_config_bool(Config::IsMuted).await?.to_string(),

View File

@@ -6,7 +6,6 @@ mod tests {
use crate::chat;
use crate::chat::send_text_msg;
use crate::config::Config;
use crate::message::Message;
use crate::mimeparser::SystemMessage;
use crate::receive_imf::receive_imf;
@@ -61,7 +60,6 @@ Sent with my Delta Chat Messenger: https://delta.chat";
async fn test_chatmail_can_send_unencrypted() -> Result<()> {
let mut tcm = TestContextManager::new();
let bob = &tcm.bob().await;
bob.set_config_bool(Config::IsChatmail, true).await?;
bob.allow_unencrypted().await?;
let bob_chat_id = receive_imf(
bob,

View File

@@ -25,14 +25,6 @@ pub(crate) struct Capabilities {
/// <https://tools.ietf.org/html/rfc4978>
pub can_compress: bool,
/// True if the server has an XCHATMAIL capability
/// indicating that it is a <https://github.com/deltachat/chatmail> server.
///
/// This can be used to hide some advanced settings in the UI
/// that are only interesting for normal email accounts,
/// e.g. the ability to move messages to Delta Chat folder.
pub is_chatmail: bool,
/// Server ID if the server supports ID capability.
pub server_id: Option<HashMap<String, String>>,
}

View File

@@ -82,7 +82,6 @@ pub(crate) async fn identify_server(
can_check_quota: caps.has_str("QUOTA"),
can_metadata: caps.has_str("METADATA"),
can_compress: caps.has_str("COMPRESS=DEFLATE"),
is_chatmail: caps.has_str("XCHATMAIL"),
server_id,
};
Ok(capabilities)

View File

@@ -111,11 +111,6 @@ impl Session {
self.capabilities.can_metadata
}
// Returns true if IMAP server has `XCHATMAIL` capability.
pub(crate) fn is_chatmail(&self) -> bool {
self.capabilities.is_chatmail
}
/// Prefetch `n_uids` messages starting from `uid_next`. Returns a list of fetch results in the
/// order of ascending UIDs.
#[expect(clippy::arithmetic_side_effects)]

View File

@@ -981,7 +981,6 @@ mod tests {
context1.get_config(Config::BccSelf).await?,
Some("0".to_string())
);
context1.set_config_bool(Config::IsChatmail, true).await?;
assert_eq!(context1.get_config_bool(Config::IsMuted).await?, false);
context1.set_config_bool(Config::IsMuted, true).await?;
@@ -1001,7 +1000,6 @@ mod tests {
.get_matching(|evt| matches!(evt, EventType::ImexProgress(1000)))
.await;
assert!(context2.is_configured().await?);
assert!(context2.is_chatmail().await?);
for ctx in [context1, context2] {
// BccSelf should be enabled automatically when exporting a backup
assert_eq!(ctx.get_config_bool(Config::BccSelf).await?, true);

View File

@@ -521,7 +521,6 @@ async fn inbox_fetch_idle(ctx: &Context, imap: &mut Imap, mut session: Session)
};
maybe_broadcast_reactions(ctx).await.log_err(ctx).ok();
maybe_send_stats(ctx).await.log_err(ctx).ok();
session
.update_metadata(ctx)
@@ -534,6 +533,8 @@ async fn inbox_fetch_idle(ctx: &Context, imap: &mut Imap, mut session: Session)
);
}
maybe_send_stats(ctx).await.log_err(ctx).ok();
let session = fetch_idle(ctx, imap, session).await?;
Ok(session)
}

View File

@@ -16,15 +16,15 @@ async fn test_clear_config_cache() -> anyhow::Result<()> {
// This test checks that the config cache is invalidated in `execute_migration()`.
let t = TestContext::new().await;
assert_eq!(t.get_config_bool(Config::IsChatmail).await?, false);
assert_eq!(t.sql.get_raw_config_bool("cached_key").await?, false);
t.sql
.execute_migration(
"INSERT INTO config (keyname, value) VALUES ('is_chatmail', '1')",
"INSERT INTO config (keyname, value) VALUES ('cached_key', '1')",
1000,
)
.await?;
assert_eq!(t.get_config_bool(Config::IsChatmail).await?, true);
assert_eq!(t.sql.get_raw_config_bool("cached_key").await?, true);
assert_eq!(t.sql.get_raw_config_int(VERSION_CFG).await?.unwrap(), 1000);
Ok(())

View File

@@ -42,7 +42,8 @@ struct Statistics {
/// Size of the public key in bytes (encoded in binary, not base64).
pubkey_size: usize,
stats_id: String,
is_chatmail: bool,
/// Whether all transports are chatmail relays, `None` if not known for all of them.
is_chatmail: Option<bool>,
contact_stats: Vec<ContactStat>,
message_stats: BTreeMap<Chattype, MessageStats>,
securejoin_sources: SecurejoinSources,
@@ -350,16 +351,22 @@ async fn get_stats(context: &Context) -> Result<String> {
let sending_disabled_timestamps =
get_timestamps(context, "stats_sending_disabled_events").await?;
let number_of_transports = context.count_transports().await?;
let metadata = context.metadata.read().await;
let is_chatmail = (!metadata.is_empty() && metadata.len() == number_of_transports)
.then(|| metadata.values().all(|m| m.supports_push));
drop(metadata);
let stats = Statistics {
core_version: DC_VERSION_STR.to_string(),
number_of_transports: context.count_transports().await?,
number_of_transports,
key_create_timestamps,
number_of_keys,
key_version: self_public_key.primary_key.version().into(),
key_algorithm: format!("{:?}", self_public_key.algorithm()),
pubkey_size: DcKey::to_bytes(&self_public_key).len(),
stats_id: stats_id(context).await?,
is_chatmail: context.is_chatmail().await?,
is_chatmail,
contact_stats: get_contact_stats(context, last_old_contact).await?,
message_stats: get_message_stats(context).await?,
securejoin_sources: get_securejoin_source_stats(context).await?,

View File

@@ -4,11 +4,13 @@ use super::*;
use crate::chat::{
Chat, create_broadcast, create_group, create_group_unencrypted, get_chat_contacts,
};
use crate::imap::ServerMetadata;
use crate::mimeparser::SystemMessage;
use crate::qr::check_qr;
use crate::securejoin::{get_securejoin_qr, join_securejoin, join_securejoin_with_ux_info};
use crate::test_utils::{TestContext, TestContextManager, get_chat_msg};
use crate::tools::SystemTime;
use crate::transport::add_pseudo_transport;
use pretty_assertions::assert_eq;
use serde_json::{Number, Value};
@@ -463,18 +465,33 @@ async fn test_stats_securejoin_invites() -> Result<()> {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_stats_is_chatmail() -> Result<()> {
async fn is_chatmail(context: &TestContext) -> Result<Value> {
let stats: Value = serde_json::from_str(&get_stats(context).await?)?;
Ok(stats.get("is_chatmail").unwrap().clone())
}
let alice = &TestContext::new_alice().await;
alice.set_config_bool(Config::StatsSending, true).await?;
assert!(is_chatmail(alice).await?.is_null());
let r = get_stats(alice).await?;
let r: serde_json::Value = serde_json::from_str(&r)?;
assert_eq!(r.get("is_chatmail").unwrap().as_bool().unwrap(), false);
alice.metadata.write().await.insert(
0,
ServerMetadata {
supports_push: true,
..Default::default()
},
);
assert_eq!(is_chatmail(alice).await?, Value::Bool(true));
alice.set_config_bool(Config::IsChatmail, true).await?;
add_pseudo_transport(alice, "alice@example.net").await?;
assert!(is_chatmail(alice).await?.is_null());
let r = get_stats(alice).await?;
let r: serde_json::Value = serde_json::from_str(&r)?;
assert_eq!(r.get("is_chatmail").unwrap().as_bool().unwrap(), true);
alice
.metadata
.write()
.await
.insert(1, ServerMetadata::default());
assert_eq!(is_chatmail(alice).await?, Value::Bool(false));
Ok(())
}

View File

@@ -703,9 +703,7 @@ mod tests {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_send_sync_msg_enables_bccself() -> Result<()> {
for (chatmail, sync_message_sent) in
[(false, false), (false, true), (true, false), (true, true)]
{
for sync_message_sent in [false, true] {
let alice1 = TestContext::new_alice().await;
let alice2 = TestContext::new_alice().await;
@@ -714,9 +712,6 @@ mod tests {
alice1.set_config_bool(Config::SyncMsgs, true).await?;
alice2.set_config_bool(Config::SyncMsgs, true).await?;
alice1.set_config_bool(Config::IsChatmail, chatmail).await?;
alice2.set_config_bool(Config::IsChatmail, chatmail).await?;
alice1.set_config_bool(Config::BccSelf, true).await?;
alice2.set_config_bool(Config::BccSelf, false).await?;
@@ -735,7 +730,7 @@ mod tests {
alice1.send_text(chat.id, "Hi").await
};
// On chatmail accounts, BccSelf defaults to false.
// BccSelf defaults to false.
// When receiving a sync message from another device,
// there obviously is a multi-device-setup, and BccSelf
// should be enabled.