From c5179a04845087f2fa6a5a0385238530c4b42cde Mon Sep 17 00:00:00 2001 From: m-ewura Date: Thu, 18 Sep 2025 14:48:42 -0700 Subject: [PATCH 1/3] Return Postmark API response in send() and add tests --- postmark/core.py | 6 ++-- tests.py | 76 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 2 deletions(-) diff --git a/postmark/core.py b/postmark/core.py index fce4f5c..a620569 100644 --- a/postmark/core.py +++ b/postmark/core.py @@ -556,8 +556,9 @@ def send(self, test=None): jsontxt = result.read().decode('utf8') result.close() if result.code == 200: - self.message_id = json.loads(jsontxt).get('MessageID', None) - return True + parsed = json.loads(jsontxt) + self.message_id = parsed.get("MessageID", None) + return parsed else: raise PMMailSendException('Return code %d: %s' % (result.code, result.msg)) except HTTPError as err: @@ -729,6 +730,7 @@ def send(self, test=None): results = json.loads(jsontxt) for i, res in enumerate(results): self.__messages[i].message_id = res.get("MessageID", None) + return results else: raise PMMailSendException('Return code %d: %s' % (result.code, result.msg)) except HTTPError as err: diff --git a/tests.py b/tests.py index e97ddf3..9189403 100644 --- a/tests.py +++ b/tests.py @@ -20,6 +20,9 @@ import mock +import django +from unittest.mock import patch, MagicMock + from postmark import ( PMBatchMail, PMMail, PMMailInactiveRecipientException, PMMailUnprocessableEntityException, PMMailServerErrorException, @@ -29,6 +32,79 @@ from django.conf import settings +if not settings.configured: + settings.configure( + POSTMARK_TRACK_OPENS=False, + POSTMARK_API_KEY="dummy", + POSTMARK_SENDER="test@example.com", + ) + django.setup() + +def make_fake_response(payload, code=200): + """Helper to fake an HTTP response object.""" + mock = MagicMock() + mock.code = code + mock.read.return_value = json.dumps(payload).encode("utf-8") + mock.close.return_value = None + return mock + +@patch("postmark.core.urlopen") +def test_mail_returns_result(mock_urlopen): + # Fake Postmark single send response + fake_payload = { + "To": "receiver@example.com", + "SubmittedAt": "2025-09-18T10:00:00Z", + "MessageID": "abc-123", + "ErrorCode": 0, + "Message": "OK" + } + mock_urlopen.return_value = make_fake_response(fake_payload) + + mail = PMMail( + api_key="test-api-key", + sender="sender@example.com", + to="receiver@example.com", + subject="Hello", + text_body="Testing single mail return", + ) + result = mail.send() + + assert isinstance(result, dict) + assert result["ErrorCode"] == 0 + assert result["Message"] == "OK" + + +@patch("postmark.core.urlopen") +def test_batch_mail_returns_results(mock_urlopen): + # Fake batch response + fake_payload = [ + { + "To": "receiver@example.com", + "SubmittedAt": "2025-09-18T10:00:00Z", + "MessageID": "abc-123", + "ErrorCode": 0, + "Message": "OK", + } + ] + mock_urlopen.return_value = make_fake_response(fake_payload) + + # Build PMMail objects + message = PMMail( + api_key="test-api-key", + sender="sender@example.com", + to="receiver@example.com", + subject="Hello", + text_body="Testing batch return", + ) + + # Pass list of PMMail objects to PMBatchMail + batch = PMBatchMail(api_key="test-api-key", messages=[message]) + results = batch.send() + + assert isinstance(results, list) + assert results[0]["ErrorCode"] == 0 + + class PMMailTests(unittest.TestCase): def test_406_error_inactive_recipient(self): json_payload = BytesIO() From a47e0bfbc02c205f82714dacdcf191c59bcfd537 Mon Sep 17 00:00:00 2001 From: m-ewura Date: Tue, 23 Sep 2025 12:52:19 -0700 Subject: [PATCH 2/3] Add optional return_json flag with Django/global fallback --- postmark/core.py | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/postmark/core.py b/postmark/core.py index a620569..f7167a1 100644 --- a/postmark/core.py +++ b/postmark/core.py @@ -504,7 +504,7 @@ def to_json_message(self): return json_message - def send(self, test=None): + def send(self, test=None, return_json=None): ''' Send the email through the Postmark system. Pass test=True to just print out the resulting @@ -537,6 +537,21 @@ def send(self, test=None): else: endpoint_url = __POSTMARK_URL__ + 'email' + """ + Args: + return_json (bool | None): + True -> return parsed JSON + False -> return True/False + None -> fallback to settings.POSTMARK_RETURN_JSON (default False) + """ + + if return_json is None: + try: + from django.conf import settings as django_settings + return_json = getattr(django_settings, "POSTMARK_RETURN_JSON", False) + except ImportError: + return_json = False + # Set up the url Request req = Request( endpoint_url, @@ -558,7 +573,7 @@ def send(self, test=None): if result.code == 200: parsed = json.loads(jsontxt) self.message_id = parsed.get("MessageID", None) - return parsed + return parsed if return_json else True else: raise PMMailSendException('Return code %d: %s' % (result.code, result.msg)) except HTTPError as err: @@ -677,7 +692,7 @@ def _check_values(self): message._check_values() - def send(self, test=None): + def send(self, test=None, return_json=None): # Has one of the messages caused an inactive recipient error? inactive_recipient = False @@ -692,6 +707,21 @@ def send(self, test=None): except ImportError: pass + """ + Args: + return_json (bool | None): + True -> return parsed JSON + False -> return True/False + None -> fallback to settings.POSTMARK_RETURN_JSON (default False) + """ + + if return_json is None: + try: + from django.conf import settings as django_settings + return_json = getattr(django_settings, "POSTMARK_RETURN_JSON", False) + except ImportError: + return_json = False + # Split up into groups of 500 messages for sending for messages in _chunks(self.messages, PMBatchMail.MAX_MESSAGES): json_message = [] @@ -730,7 +760,7 @@ def send(self, test=None): results = json.loads(jsontxt) for i, res in enumerate(results): self.__messages[i].message_id = res.get("MessageID", None) - return results + return results if return_json else True else: raise PMMailSendException('Return code %d: %s' % (result.code, result.msg)) except HTTPError as err: From bda9fe2da9299945d0475639b1386c725ecbb2fe Mon Sep 17 00:00:00 2001 From: m-ewura Date: Thu, 25 Sep 2025 19:10:31 -0700 Subject: [PATCH 3/3] Update PMMail/PMBatchMail tests for return_json support and backward compatibility --- tests.py | 132 +++++++++++++++++++++++++++---------------------------- 1 file changed, 66 insertions(+), 66 deletions(-) diff --git a/tests.py b/tests.py index 9189403..6498a46 100644 --- a/tests.py +++ b/tests.py @@ -20,8 +20,7 @@ import mock -import django -from unittest.mock import patch, MagicMock +from unittest.mock import MagicMock from postmark import ( PMBatchMail, PMMail, PMMailInactiveRecipientException, @@ -32,14 +31,6 @@ from django.conf import settings -if not settings.configured: - settings.configure( - POSTMARK_TRACK_OPENS=False, - POSTMARK_API_KEY="dummy", - POSTMARK_SENDER="test@example.com", - ) - django.setup() - def make_fake_response(payload, code=200): """Helper to fake an HTTP response object.""" mock = MagicMock() @@ -48,62 +39,6 @@ def make_fake_response(payload, code=200): mock.close.return_value = None return mock -@patch("postmark.core.urlopen") -def test_mail_returns_result(mock_urlopen): - # Fake Postmark single send response - fake_payload = { - "To": "receiver@example.com", - "SubmittedAt": "2025-09-18T10:00:00Z", - "MessageID": "abc-123", - "ErrorCode": 0, - "Message": "OK" - } - mock_urlopen.return_value = make_fake_response(fake_payload) - - mail = PMMail( - api_key="test-api-key", - sender="sender@example.com", - to="receiver@example.com", - subject="Hello", - text_body="Testing single mail return", - ) - result = mail.send() - - assert isinstance(result, dict) - assert result["ErrorCode"] == 0 - assert result["Message"] == "OK" - - -@patch("postmark.core.urlopen") -def test_batch_mail_returns_results(mock_urlopen): - # Fake batch response - fake_payload = [ - { - "To": "receiver@example.com", - "SubmittedAt": "2025-09-18T10:00:00Z", - "MessageID": "abc-123", - "ErrorCode": 0, - "Message": "OK", - } - ] - mock_urlopen.return_value = make_fake_response(fake_payload) - - # Build PMMail objects - message = PMMail( - api_key="test-api-key", - sender="sender@example.com", - to="receiver@example.com", - subject="Hello", - text_body="Testing batch return", - ) - - # Pass list of PMMail objects to PMBatchMail - batch = PMBatchMail(api_key="test-api-key", messages=[message]) - results = batch.send() - - assert isinstance(results, list) - assert results[0]["ErrorCode"] == 0 - class PMMailTests(unittest.TestCase): def test_406_error_inactive_recipient(self): @@ -257,6 +192,37 @@ def test_send_metadata_invalid_format(self): self.assertRaises(TypeError, PMMail, api_key='test', sender='from@example.com', to='to@example.com', subject='test', text_body='test', metadata={'test': {}}) + def test_mail_returns_result(self): + fake_payload = { + "To": "receiver@example.com", + "SubmittedAt": "2025-09-18T10:00:00Z", + "MessageID": "abc-123", + "ErrorCode": 0, + "Message": "OK" + } + + mail = PMMail( + api_key="test-api-key", + sender="sender@example.com", + to="receiver@example.com", + subject="Hello", + text_body="Testing single mail return", + ) + + with mock.patch("postmark.core.urlopen") as mock_urlopen: + mock_urlopen.return_value = make_fake_response(fake_payload) + + # Test boolean return + result = mail.send() + self.assertTrue(result) + + # Test JSON return explicitly + result_json = mail.send(return_json=True) + self.assertIsInstance(result_json, dict) + self.assertEqual(result_json["ErrorCode"], 0) + self.assertEqual(result_json["Message"], "OK") + + class PMBatchMailTests(unittest.TestCase): def test_406_error_inactive_recipient(self): @@ -321,6 +287,40 @@ def test_500_error_server_error(self): 500, '', {}, None)): self.assertRaises(PMMailServerErrorException, batch.send) + def test_batch_mail_returns_results(self): + fake_payload = [ + { + "To": "receiver@example.com", + "SubmittedAt": "2025-09-18T10:00:00Z", + "MessageID": "abc-123", + "ErrorCode": 0, + "Message": "OK", + } + ] + + message = PMMail( + api_key="test-api-key", + sender="sender@example.com", + to="receiver@example.com", + subject="Hello", + text_body="Testing batch return", + ) + # pass list of PMMail objects to PMBatchMail + batch = PMBatchMail(api_key="test-api-key", messages=[message]) + + with mock.patch("postmark.core.urlopen") as mock_urlopen: + mock_urlopen.return_value = make_fake_response(fake_payload) + + # Test boolean return + results = batch.send() + self.assertTrue(results) + + # Test JSON return explicitly + results_json = batch.send(return_json=True) + self.assertIsInstance(results_json, list) + self.assertEqual(results_json[0]["ErrorCode"], 0) + + class PMBounceManagerTests(unittest.TestCase): def test_activate(self):