mmerickel: I'll try in the next half hour here to see if I can reproduce the old error and then upgrade to see if this patch fixes it. Thanks.
diff --git a/repoze/sendmail/delivery.py b/repoze/sendmail/delivery.py
index 59115d7..28b7cf4 100644
--- a/repoze/sendmail/delivery.py
+++ b/repoze/sendmail/delivery.py
@@ -102,6 +102,7 @@ class MailDataManager(object):
         if self.transaction is None:
             raise ValueError("Not in a transaction")
         self.state = final_state
+        self.tpc_phase = 0
 
     def commit(self, trans):
         if self.transaction is None:

MailDataManager breaks the transaction manager upon abort
I have some code sending emails and for legitimate reasons the email is rejected by the remote server. I want to log the exception but instead I see the `ValueError('TPC in progress')` a couple times in the output, squashing the error.

It's actually causing the transaction manager to be in such a broken state that the next transaction.manager.begin() after the failed abort, causes another exception. Below is the crap fix to prove the diagnosis, but I'm not positive it's the correct fix. I downgraded to 4.1 and things work as expected.

From what I can tell this behavior appeared in https://github.com/repoze/repoze.sendmail/commit/06ad882dc4f4a220fba2583e67076e9f3dcf3359 when the manager was rewritten.

On a related side note, aborting in the second phase of a two-phase commit is a Really Bad Thing(tm). Since sending email isn't transactional it almost makes more sense to either send the email in an earlier phase or even as an `afterCommitHook`.

``` diff
diff --git a/repoze/sendmail/delivery.py b/repoze/sendmail/delivery.py
index 59115d7..6b56add 100644
--- a/repoze/sendmail/delivery.py
+++ b/repoze/sendmail/delivery.py
@@ -115,8 +115,8 @@ class MailDataManager(object):
             raise ValueError("Not in a transaction")
         if self.transaction is not trans:
             raise ValueError("In a different transaction")
-        if self.tpc_phase != 0:
-            raise ValueError("TPC in progress")
+        #if self.tpc_phase != 0:
+        #    raise ValueError("TPC in progress")
         if self.onAbort:
             self.onAbort()
```

diff --git a/repoze/sendmail/tests/test_delivery.py b/repoze/sendmail/tests/test_delivery.py
index 9776943..fe5ef6f 100644
--- a/repoze/sendmail/tests/test_delivery.py
+++ b/repoze/sendmail/tests/test_delivery.py
@@ -79,6 +79,7 @@ class TestMailDataManager(unittest.TestCase):
         mdm.join_transaction(txn)
         mdm._finish(2)
         self.assertEqual(mdm.state, 2)
+        self.assertEqual(mdm.tpc_phase, 0)
 
     def test_commit_wo_transaction(self):
         mdm = self._makeOne(object)
@@ -251,6 +252,7 @@ class TestMailDataManager(unittest.TestCase):
         mdm.tpc_finish(txn)
         self.assertEqual(_called, [(1, 2)])
         self.assertEqual(mdm.state, MailDataManagerState.TPC_FINISHED)
+        self.assertEqual(mdm.tpc_phase, 0)
 
     def test_tpc_abort_wo_transaction(self):
         mdm = self._makeOne()
@@ -287,6 +289,7 @@ class TestMailDataManager(unittest.TestCase):
         mdm.tpc_phase = 1
         mdm.tpc_abort(txn)
         self.assertEqual(mdm.state, MailDataManagerState.TPC_ABORTED)
+        self.assertEqual(mdm.tpc_phase, 0)
 
     def test_tpc_abort_voted_ok(self):
         from ..delivery import MailDataManagerState
@@ -296,6 +299,7 @@ class TestMailDataManager(unittest.TestCase):
         mdm.tpc_phase = 2
         mdm.tpc_abort(txn)
         self.assertEqual(mdm.state, MailDataManagerState.TPC_ABORTED)
+        self.assertEqual(mdm.tpc_phase, 0)
 
 
 class TestAbstractMailDelivery(unittest.TestCase):

pip install -e .[testing]
parse_log_pytest
setuptools zope.interface>=3.6.0 transaction
pytest
3.5
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==22.2.0
certifi==2021.5.30
coverage==6.2
importlib-metadata==4.8.3
iniconfig==1.1.1
nose==1.3.7
packaging==21.3
pluggy==1.0.0
py==1.11.0
pyparsing==3.1.4
pytest==7.0.1
-e git+https://github.com/repoze/repoze.sendmail.git@af9b7db732bf7ee0edb956c05b5c7b7818e31d64#egg=repoze.sendmail
tomli==1.2.3
transaction==3.0.1
typing_extensions==4.1.1
zipp==3.6.0
zope.interface @ file:///tmp/build/80754af9/zope.interface_1625036152722/work

name: repoze.sendmail
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - transaction=3.0.1=py36h06a4308_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - zope=1.0=py36_1
  - zope.interface=5.4.0=py36h7f8727e_0
  - pip:
      - attrs==22.2.0
      - coverage==6.2
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - nose==1.3.7
      - packaging==21.3
      - pluggy==1.0.0
      - py==1.11.0
      - pyparsing==3.1.4
      - pytest==7.0.1
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - zipp==3.6.0
prefix: /opt/conda/envs/repoze.sendmail

repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_tpc_abort_begun_ok repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_tpc_abort_voted_ok repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_tpc_finish_ok
repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test__finish_w_transaction repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test__finish_wo_transaction repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_abort_w_TPC repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_abort_w_foreign_transaction repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_abort_w_onAbort repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_abort_w_same_transaction repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_abort_wo_transaction repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_class_conforms_to_IDataManager repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_commit_w_TPC repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_commit_w_foreign_transaction repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_commit_w_same_transaction repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_commit_wo_transaction repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_ctor repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_instance_conforms_to_IDataManager repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_join_transaction_conflict repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_join_transaction_duplicated repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_join_transaction_explicit repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_join_transaction_implicit repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_savepoint_w_transaction repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_savepoint_wo_transaction repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_sortKey repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_tpc_abort_already_finished repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_tpc_abort_not_already_tpc repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_tpc_abort_w_foreign_transaction repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_tpc_abort_wo_transaction repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_tpc_begin_already_tpc repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_tpc_begin_ok repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_tpc_begin_w_foreign_transaction repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_tpc_begin_w_subtransaction repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_tpc_begin_wo_transaction repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_tpc_finish_not_already_tpc repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_tpc_finish_not_voted repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_tpc_finish_w_foreign_transaction repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_tpc_finish_wo_transaction repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_tpc_vote_not_already_tpc repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_tpc_vote_ok repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_tpc_vote_w_foreign_transaction repoze/sendmail/tests/test_delivery.py::TestMailDataManager::test_tpc_vote_wo_transaction repoze/sendmail/tests/test_delivery.py::TestAbstractMailDelivery::test_send_w_bad_message repoze/sendmail/tests/test_delivery.py::TestAbstractMailDelivery::test_send_w_bare_message repoze/sendmail/tests/test_delivery.py::TestAbstractMailDelivery::test_send_w_populated_message repoze/sendmail/tests/test_delivery.py::TestDirectMailDelivery::test_alternate_transaction_manager repoze/sendmail/tests/test_delivery.py::TestDirectMailDelivery::test_class_conforms_to_IMailDelivery repoze/sendmail/tests/test_delivery.py::TestDirectMailDelivery::test_ctor repoze/sendmail/tests/test_delivery.py::TestDirectMailDelivery::test_instance_conforms_to_IMailDelivery repoze/sendmail/tests/test_delivery.py::TestDirectMailDelivery::test_send repoze/sendmail/tests/test_delivery.py::TestDirectMailDelivery::test_send_returns_messageId repoze/sendmail/tests/test_delivery.py::TestQueuedMailDelivery::test_class_conforms_to_IMailDelivery repoze/sendmail/tests/test_delivery.py::TestQueuedMailDelivery::test_ctor repoze/sendmail/tests/test_delivery.py::TestQueuedMailDelivery::test_instance_conforms_to_IMailDelivery repoze/sendmail/tests/test_delivery.py::TestQueuedMailDelivery::test_send repoze/sendmail/tests/test_delivery.py::TestQueuedMailDeliveryWithMaildir::test_send_w_non_ASCII_addrs
repoze/sendmail/delivery.py
repoze/sendmail/delivery.py