From 9df56324dad280080daff97acff2ce2ad00bd058 Mon Sep 17 00:00:00 2001 From: link2xt Date: Wed, 26 Aug 2026 13:58:02 +0000 Subject: [PATCH 1/3] refactor: make create_send_msg_jobs() private It is not called from outside the "chat" module. --- src/chat.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chat.rs b/src/chat.rs index 037e56675e..a3a91a26a7 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -2856,7 +2856,7 @@ async fn render_mime_message_and_pre_message( /// Returns row ids if `smtp` table jobs were created or an empty `Vec` otherwise. /// /// The caller has to interrupt SMTP loop or otherwise process new rows. -pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) -> Result> { +async fn create_send_msg_jobs(context: &Context, msg: &mut Message) -> Result> { let cmd = msg.param.get_cmd(); if cmd == SystemMessage::GroupNameChanged || cmd == SystemMessage::GroupDescriptionChanged { msg.chat_id From 41a68dec0b0893c936fdccced842d09ed97f6e2c Mon Sep 17 00:00:00 2001 From: link2xt Date: Fri, 14 Aug 2026 11:17:12 +0000 Subject: [PATCH 2/3] fix: make create_send_msg_jobs actually return row IDs .execute() was returning the number of rows, so usually 1. .insert() is returning the row ID. In most cases it does not matter because the result is checked with .is_empty(), but send_msg_sync() actually uses the row IDs. --- src/chat.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/chat.rs b/src/chat.rs index a3a91a26a7..d570652095 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -3037,21 +3037,21 @@ WHERE id=? )?; let all_recipients = recipients.join(" "); if let Some(pre_msg) = &rendered_pre_msg { - let row_id = stmt.execute(( + let row_id = stmt.insert(( &pre_msg.rfc724_mid, &all_recipients, &pre_msg.message, msg.id, ))?; - row_ids.push(row_id.try_into()?); + row_ids.push(row_id); } - let row_id = stmt.execute(( + let row_id = stmt.insert(( &rendered_msg.rfc724_mid, &all_recipients, &rendered_msg.message, msg.id, ))?; - row_ids.push(row_id.try_into()?); + row_ids.push(row_id); } Ok(row_ids) }; From a67a5b02449c8726750f59edc7eb6b15206e2887 Mon Sep 17 00:00:00 2001 From: link2xt Date: Tue, 25 Aug 2026 13:52:33 +0000 Subject: [PATCH 3/3] test: test dc_send_msg_sync() --- python/src/deltachat/chat.py | 34 ++++++++++++++++++++++++++++------ python/tests/test_1_online.py | 26 ++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/python/src/deltachat/chat.py b/python/src/deltachat/chat.py index b83ee30818..950d7c1c21 100644 --- a/python/src/deltachat/chat.py +++ b/python/src/deltachat/chat.py @@ -259,6 +259,15 @@ def get_join_qr(self) -> Optional[str]: # ------ chat messaging API ------------------------------ + def _reload_sent_msg(self, msg: Message, sent_id: int) -> Message: + """Helper to reload just sent message""" + sent_msg = Message.from_db(self.account, sent_id) + if sent_msg is None: + raise ValueError("cannot load just sent message from the database") + # modify message in place to avoid bad state for the caller + msg._dc_msg = sent_msg._dc_msg + return msg + def send_msg(self, msg: Message) -> Message: """send a message by using a ready Message object. @@ -274,12 +283,25 @@ def send_msg(self, msg: Message) -> Message: sent_id = lib.dc_send_msg(self.account._dc_context, self.id, msg._dc_msg) if sent_id == 0: raise ValueError("message could not be sent") - # modify message in place to avoid bad state for the caller - sent_msg = Message.from_db(self.account, sent_id) - if sent_msg is None: - raise ValueError("cannot load just sent message from the database") - msg._dc_msg = sent_msg._dc_msg - return msg + return self._reload_sent_msg(msg, sent_id) + + def send_msg_sync(self, msg: Message) -> Message: + """Send a message synchronously. + This bypasses the IO scheduler and creates its own SMTP connection. + + :param msg: a :class:`deltachat.message.Message` instance + previously returned by + e.g. :meth:`deltachat.message.Message.new_empty`. + :raises ValueError: if message can not be sent. + + :returns: a :class:`deltachat.message.Message` instance as + sent out. This is the same object as was passed in, which + has been modified with the new state of the core. + """ + sent_id = lib.dc_send_msg_sync(self.account._dc_context, self.id, msg._dc_msg) + if sent_id == 0: + raise ValueError("message could not be sent") + return self._reload_sent_msg(msg, sent_id) def send_text(self, text): """send a text message and return the resulting Message instance. diff --git a/python/tests/test_1_online.py b/python/tests/test_1_online.py index 04957d98bc..7d8264f7fb 100644 --- a/python/tests/test_1_online.py +++ b/python/tests/test_1_online.py @@ -291,6 +291,32 @@ def test_forward_own_message(acfactory, lp): assert msg_in.is_forwarded() +def test_send_msg_sync(acfactory, lp): + ac1, ac2 = acfactory.get_online_accounts(2) + chat1 = acfactory.get_accepted_chat(ac1, ac2) + + # Send some message from ac1 + # so we are testing not the first message + # being sent synchronously. + lp.sec("ac1: send message to ac2") + chat1.send_text("message") + + lp.sec("ac2: receive message") + msg_in = ac2._evtracker.wait_next_incoming_message() + assert msg_in.text == "message" + + # Stop I/O and send message synchronously. + ac1.stop_io() + msg1 = Message.new_empty(ac1, "text") + msg1.set_text("message1") + chat1.send_msg_sync(msg1) + msg1.is_out_delivered() + + lp.sec("ac2: receive message") + msg_in = ac2._evtracker.wait_next_incoming_message() + assert msg_in.text == "message1" + + def test_resend_message(acfactory, lp): ac1, ac2 = acfactory.get_online_accounts(2) chat1 = acfactory.get_accepted_chat(ac1, ac2)