From 71692b487b3c968a46898d8d946f8170c9cac3ed Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 17 Dec 2025 15:45:12 +0530 Subject: [PATCH 1/3] fix!: sendmail `now=True` after current transaction is commited Sendmail implicitly commits transaction to update status. Do it after the parent transaction is commited. closes https://github.com/frappe/frappe/issues/33343 --- frappe/email/__init__.py | 6 +++++- frappe/tests/test_email.py | 8 +------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/frappe/email/__init__.py b/frappe/email/__init__.py index b7c8a92673..e584bbac2d 100644 --- a/frappe/email/__init__.py +++ b/frappe/email/__init__.py @@ -239,4 +239,8 @@ def sendmail( ) # build email queue and send the email if send_now is True. - return builder.process(send_now=now) + + q = builder.process(send_now=False) + if now: + frappe.db.after_commit.add(q.send) + return q diff --git a/frappe/tests/test_email.py b/frappe/tests/test_email.py index 392316055c..4b22fbc49b 100644 --- a/frappe/tests/test_email.py +++ b/frappe/tests/test_email.py @@ -355,13 +355,7 @@ class TestEmailIntegrationTest(IntegrationTestCase): subject = "checking if email works" content = "is email working?" - email = frappe.sendmail( - sender=sender, recipients=recipients, subject=subject, content=content, now=True - ) - email.reload() - self.assertEqual(email.sender, sender) - self.assertEqual(len(email.recipients), 2) - self.assertEqual(email.status, "Sent") + frappe.sendmail(sender=sender, recipients=recipients, subject=subject, content=content, now=True) sent_mails = self.get_last_sent_emails() self.assertEqual(len(sent_mails), 2) From 5922f2980655aec5f73a7b3fa625e4fa394b764f Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 17 Dec 2025 16:23:54 +0530 Subject: [PATCH 2/3] chore: remove unncessary print --- frappe/search/sqlite_search.py | 1 - 1 file changed, 1 deletion(-) diff --git a/frappe/search/sqlite_search.py b/frappe/search/sqlite_search.py index 868848edbf..308c8b5839 100644 --- a/frappe/search/sqlite_search.py +++ b/frappe/search/sqlite_search.py @@ -522,7 +522,6 @@ class SQLiteSearch(ABC): ORDER BY bm25_score LIMIT ? """ - print(sql) return self.sql(sql, params, read_only=True) def _process_search_results(self, raw_results, query): From 9979314b327c3223af48b3676c684c19cc8fc594 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 17 Dec 2025 16:12:34 +0530 Subject: [PATCH 3/3] test: commit to trigger sendmail --- .../doctype/user_invitation/test_user_invitation.py | 9 +++++++++ .../doctype/email_account/test_email_account.py | 3 ++- frappe/integrations/doctype/webhook/test_webhook.py | 1 + frappe/tests/test_email.py | 13 ++++++++++++- 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/user_invitation/test_user_invitation.py b/frappe/core/doctype/user_invitation/test_user_invitation.py index 634c739d8e..9ffe106d0e 100644 --- a/frappe/core/doctype/user_invitation/test_user_invitation.py +++ b/frappe/core/doctype/user_invitation/test_user_invitation.py @@ -78,6 +78,7 @@ class IntegrationTestUserInvitation(IntegrationTestCase): invitation = self.get_dummy_invitation() self.assertEqual(len(self.get_email_names()), 0) invitation.insert() + frappe.db.commit() self.assertEqual(invitation.invited_by, frappe.session.user) self.assertEqual(invitation.status, "Pending") self.assertIsInstance(invitation.email_sent_at, str) @@ -90,8 +91,10 @@ class IntegrationTestUserInvitation(IntegrationTestCase): def test_update_invitation_status_to_expired(self): invitation = self.get_dummy_invitation() invitation.insert() + frappe.db.commit() self.assertEqual(len(self.get_email_names()), 1) invitation.expire() + frappe.db.commit() emails = self.get_email_messages(False) self.assertEqual(len(emails), 2) self.assertIn("expired", emails[0].message.lower()) @@ -252,15 +255,21 @@ class IntegrationTestUserInvitation(IntegrationTestCase): def test_cancel_invitation_api(self): invitation = self.get_dummy_invitation() invitation.insert() + frappe.db.commit() + invitation.reload() self.assertEqual(invitation.status, "Pending") self.assertEqual(len(self.get_email_names()), 1) res = cancel_invitation(invitation.name, "frappe") + frappe.db.commit() + self.assertTrue(res["cancelled_now"]) invitation.reload() self.assertEqual(invitation.status, "Cancelled") self.assertEqual(len(self.get_email_names()), 2) res = cancel_invitation(invitation.name, "frappe") + frappe.db.commit() + self.assertFalse(res["cancelled_now"]) self.assertEqual(len(self.get_email_names()), 2) diff --git a/frappe/email/doctype/email_account/test_email_account.py b/frappe/email/doctype/email_account/test_email_account.py index 96a02c86dd..d0d77a180f 100644 --- a/frappe/email/doctype/email_account/test_email_account.py +++ b/frappe/email/doctype/email_account/test_email_account.py @@ -183,8 +183,9 @@ class TestEmailAccount(IntegrationTestCase): recipients="test_recipient@example.com", content="test mail 001", subject="test-mail-001", - delayed=False, + now=True, ) + frappe.db.commit() # now=True requires commit sent_mail = email.message_from_string(frappe.safe_decode(frappe.flags.sent_mail)) self.assertTrue("test-mail-001" in sent_mail.get("Subject")) diff --git a/frappe/integrations/doctype/webhook/test_webhook.py b/frappe/integrations/doctype/webhook/test_webhook.py index 55e70c4c5a..c5afc4de2e 100644 --- a/frappe/integrations/doctype/webhook/test_webhook.py +++ b/frappe/integrations/doctype/webhook/test_webhook.py @@ -112,6 +112,7 @@ class TestWebhook(IntegrationTestCase): self.test_user.email = "user1@integration.webhooks.test.com" self.test_user.first_name = "user1" self.test_user.send_welcome_email = False + frappe.db.commit() def tearDown(self) -> None: self.user.delete() diff --git a/frappe/tests/test_email.py b/frappe/tests/test_email.py index 4b22fbc49b..6d7a7b2c16 100644 --- a/frappe/tests/test_email.py +++ b/frappe/tests/test_email.py @@ -132,6 +132,7 @@ class TestEmail(IntegrationTestCase): expose_recipients="footer", now=True, ) + frappe.db.commit() email_queue = frappe.db.sql("""select name from `tabEmail Queue` where status='Sent'""", as_dict=1) self.assertEqual(len(email_queue), 1) queue_recipients = [ @@ -165,6 +166,7 @@ class TestEmail(IntegrationTestCase): unsubscribe_message="Unsubscribe", now=True, ) + frappe.db.commit() email_queue = frappe.db.sql("""select name from `tabEmail Queue` where status='Sent'""", as_dict=1) self.assertEqual(len(email_queue), 1) queue_recipients = [ @@ -210,6 +212,7 @@ class TestEmail(IntegrationTestCase): message="This mail is queued!", now=True, ) + frappe.db.commit() email_queue_sender = frappe.db.get_value("Email Queue", {"status": "Sent"}, "sender") self.assertEqual(email_queue_sender, assertion) @@ -355,7 +358,14 @@ class TestEmailIntegrationTest(IntegrationTestCase): subject = "checking if email works" content = "is email working?" - frappe.sendmail(sender=sender, recipients=recipients, subject=subject, content=content, now=True) + email = frappe.sendmail( + sender=sender, recipients=recipients, subject=subject, content=content, now=True + ) + frappe.db.commit() + email.reload() + self.assertEqual(email.sender, sender) + self.assertEqual(len(email.recipients), 2) + self.assertEqual(email.status, "Sent") sent_mails = self.get_last_sent_emails() self.assertEqual(len(sent_mails), 2) @@ -381,6 +391,7 @@ class TestEmailIntegrationTest(IntegrationTestCase): send_email=True, now=True, ).get("name") + frappe.db.commit() communication = frappe.get_doc("Communication", name)