diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 4c28622..bd6261b 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -71,16 +71,22 @@ jobs: TOX_FORCE_IGNORE_OUTCOME: false ANYMAIL_RUN_LIVE_TESTS: true ANYMAIL_TEST_AMAZON_SES_ACCESS_KEY_ID: ${{ secrets.ANYMAIL_TEST_AMAZON_SES_ACCESS_KEY_ID }} + ANYMAIL_TEST_AMAZON_SES_DOMAIN: ${{ secrets.ANYMAIL_TEST_AMAZON_SES_DOMAIN }} ANYMAIL_TEST_AMAZON_SES_REGION_NAME: ${{ secrets.ANYMAIL_TEST_AMAZON_SES_REGION_NAME }} ANYMAIL_TEST_AMAZON_SES_SECRET_ACCESS_KEY: ${{ secrets.ANYMAIL_TEST_AMAZON_SES_SECRET_ACCESS_KEY }} ANYMAIL_TEST_MAILGUN_API_KEY: ${{ secrets.ANYMAIL_TEST_MAILGUN_API_KEY }} ANYMAIL_TEST_MAILGUN_DOMAIN: ${{ secrets.ANYMAIL_TEST_MAILGUN_DOMAIN }} ANYMAIL_TEST_MAILJET_API_KEY: ${{ secrets.ANYMAIL_TEST_MAILJET_API_KEY }} + ANYMAIL_TEST_MAILJET_DOMAIN: ${{ secrets.ANYMAIL_TEST_MAILJET_DOMAIN }} ANYMAIL_TEST_MAILJET_SECRET_KEY: ${{ secrets.ANYMAIL_TEST_MAILJET_SECRET_KEY }} ANYMAIL_TEST_MANDRILL_API_KEY: ${{ secrets.ANYMAIL_TEST_MANDRILL_API_KEY }} + ANYMAIL_TEST_POSTMARK_DOMAIN: ${{ secrets.ANYMAIL_TEST_POSTMARK_DOMAIN }} ANYMAIL_TEST_POSTMARK_SERVER_TOKEN: ${{ secrets.ANYMAIL_TEST_POSTMARK_SERVER_TOKEN }} ANYMAIL_TEST_POSTMARK_TEMPLATE_ID: ${{ secrets.ANYMAIL_TEST_POSTMARK_TEMPLATE_ID }} ANYMAIL_TEST_SENDGRID_API_KEY: ${{ secrets.ANYMAIL_TEST_SENDGRID_API_KEY }} + ANYMAIL_TEST_SENDGRID_DOMAIN: ${{ secrets.ANYMAIL_TEST_SENDGRID_DOMAIN }} ANYMAIL_TEST_SENDGRID_TEMPLATE_ID: ${{ secrets.ANYMAIL_TEST_SENDGRID_TEMPLATE_ID }} ANYMAIL_TEST_SENDINBLUE_API_KEY: ${{ secrets.ANYMAIL_TEST_SENDINBLUE_API_KEY }} + ANYMAIL_TEST_SENDINBLUE_DOMAIN: ${{ secrets.ANYMAIL_TEST_SENDINBLUE_DOMAIN }} ANYMAIL_TEST_SPARKPOST_API_KEY: ${{ secrets.ANYMAIL_TEST_SPARKPOST_API_KEY }} + ANYMAIL_TEST_SPARKPOST_DOMAIN: ${{ secrets.ANYMAIL_TEST_SPARKPOST_DOMAIN }} diff --git a/docs/contributing.rst b/docs/contributing.rst index e8eb08b..3cf0bae 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -38,7 +38,7 @@ Bugs You can report problems or request features in `Anymail's GitHub issue tracker`_. (For a security-related issue that should not be disclosed publicly, instead email -Anymail's maintainers at securityanymailinfo.) +Anymail's maintainers at *security\anymail\dev*.) We also have some :ref:`troubleshooting` information that may be helpful. diff --git a/docs/docs_privacy.rst b/docs/docs_privacy.rst index f9041f0..bb9fc86 100644 --- a/docs/docs_privacy.rst +++ b/docs/docs_privacy.rst @@ -37,7 +37,7 @@ analytics tracking, see the "Information for Visitors of Sites and Apps Using Google Analytics" section of Google's `Safeguarding your data`_ document. Questions about privacy and information practices related to this Anymail -documentation site can be emailed to *privacy\anymail\info*. +documentation site can be emailed to *privacy\anymail\dev*. (This is not an appropriate contact for questions about *using* Anymail; see :ref:`help` if you need assistance with your code.) diff --git a/docs/help.rst b/docs/help.rst index 4b27795..9c1249e 100644 --- a/docs/help.rst +++ b/docs/help.rst @@ -55,7 +55,7 @@ Here's how to contact the Anymail community: **"I found a security issue!"** - Contact the Anymail maintainers by emailing *securityanymailinfo.* + Contact the Anymail maintainers by emailing *security\anymail\dev.* (Please don't open a GitHub issue or post publicly about potential security problems.) **"Could Anymail support this ESP or feature...?"** diff --git a/tests/test_amazon_ses_integration.py b/tests/test_amazon_ses_integration.py index c895d0d..6dac2e1 100644 --- a/tests/test_amazon_ses_integration.py +++ b/tests/test_amazon_ses_integration.py @@ -1,6 +1,7 @@ import os import unittest import warnings +from email.utils import formataddr from django.test import SimpleTestCase, override_settings, tag @@ -13,11 +14,15 @@ from .utils import AnymailTestMixin, sample_image_path ANYMAIL_TEST_AMAZON_SES_ACCESS_KEY_ID = os.getenv("ANYMAIL_TEST_AMAZON_SES_ACCESS_KEY_ID") ANYMAIL_TEST_AMAZON_SES_SECRET_ACCESS_KEY = os.getenv("ANYMAIL_TEST_AMAZON_SES_SECRET_ACCESS_KEY") ANYMAIL_TEST_AMAZON_SES_REGION_NAME = os.getenv("ANYMAIL_TEST_AMAZON_SES_REGION_NAME", "us-east-1") +ANYMAIL_TEST_AMAZON_SES_DOMAIN = os.getenv("ANYMAIL_TEST_AMAZON_SES_DOMAIN") -@unittest.skipUnless(ANYMAIL_TEST_AMAZON_SES_ACCESS_KEY_ID and ANYMAIL_TEST_AMAZON_SES_SECRET_ACCESS_KEY, - "Set ANYMAIL_TEST_AMAZON_SES_ACCESS_KEY_ID and ANYMAIL_TEST_AMAZON_SES_SECRET_ACCESS_KEY " - "environment variables to run Amazon SES integration tests") +@unittest.skipUnless( + ANYMAIL_TEST_AMAZON_SES_ACCESS_KEY_ID + and ANYMAIL_TEST_AMAZON_SES_SECRET_ACCESS_KEY + and ANYMAIL_TEST_AMAZON_SES_DOMAIN, + "Set ANYMAIL_TEST_AMAZON_SES_ACCESS_KEY_ID and ANYMAIL_TEST_AMAZON_SES_SECRET_ACCESS_KEY " + "and ANYMAIL_TEST_AMAZON_SES_DOMAIN environment variables to run Amazon SES integration tests") @override_settings( EMAIL_BACKEND="anymail.backends.amazon_ses.EmailBackend", ANYMAIL={ @@ -47,17 +52,13 @@ class AmazonSESBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): To avoid stacking up a pile of undeliverable @example.com emails, the tests use Amazon's @simulator.amazonses.com addresses. https://docs.aws.amazon.com/ses/latest/DeveloperGuide/mailbox-simulator.html - - Amazon SES also doesn't support arbitrary senders (so no from@example.com). - We've set up @test-ses.anymail.info as a validated sending domain for these tests. - You may need to change the from_email to your own address when testing. - """ def setUp(self): super().setUp() + self.from_email = 'test@%s' % ANYMAIL_TEST_AMAZON_SES_DOMAIN self.message = AnymailMessage('Anymail Amazon SES integration test', 'Text content', - 'test@test-ses.anymail.info', ['success@simulator.amazonses.com']) + self.from_email, ['success@simulator.amazonses.com']) self.message.attach_alternative('

HTML content

', "text/html") # boto3 relies on GC to close connections. Python 3 warns about unclosed ssl.SSLSocket during cleanup. @@ -85,7 +86,7 @@ class AmazonSESBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): message = AnymailMessage( subject="Anymail Amazon SES all-options integration test", body="This is the text body", - from_email='"Test From" ', + from_email=formataddr(("Test From, with comma", self.from_email)), to=["success+to1@simulator.amazonses.com", "Recipient 2 "], cc=["success+cc1@simulator.amazonses.com", "Copy 2 "], bcc=["success+bcc1@simulator.amazonses.com", "Blind Copy 2 "], @@ -119,7 +120,7 @@ class AmazonSESBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): # }) message = AnymailMessage( template_id='TestTemplate', - from_email='"Test From" ', + from_email=formataddr(("Test From", self.from_email)), to=["First Recipient ", "success+to2@simulator.amazonses.com"], merge_data={ diff --git a/tests/test_mailgun_integration.py b/tests/test_mailgun_integration.py index 6e0aa20..811a43e 100644 --- a/tests/test_mailgun_integration.py +++ b/tests/test_mailgun_integration.py @@ -2,6 +2,7 @@ import logging import os import unittest from datetime import datetime, timedelta +from email.utils import formataddr from time import sleep import requests @@ -36,8 +37,9 @@ class MailgunBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): def setUp(self): super().setUp() + self.from_email = 'from@%s' % ANYMAIL_TEST_MAILGUN_DOMAIN self.message = AnymailMessage('Anymail Mailgun integration test', 'Text content', - 'from@example.com', ['test+to1@anymail.info']) + self.from_email, ['test+to1@anymail.dev']) self.message.attach_alternative('

HTML content

', "text/html") def fetch_mailgun_events(self, message_id, event=None, @@ -86,8 +88,8 @@ class MailgunBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): self.assertEqual(sent_count, 1) anymail_status = self.message.anymail_status - sent_status = anymail_status.recipients['test+to1@anymail.info'].status - message_id = anymail_status.recipients['test+to1@anymail.info'].message_id + sent_status = anymail_status.recipients['test+to1@anymail.dev'].status + message_id = anymail_status.recipients['test+to1@anymail.dev'].message_id self.assertEqual(sent_status, 'queued') # Mailgun always queues self.assertGreater(len(message_id), 0) # don't know what it'll be, but it should exist @@ -98,13 +100,14 @@ class MailgunBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): def test_all_options(self): send_at = datetime.now().replace(microsecond=0) + timedelta(minutes=2) send_at_timestamp = send_at.timestamp() + from_email = formataddr(("Test From, with comma", self.from_email)) message = AnymailMessage( subject="Anymail Mailgun all-options integration test", body="This is the text body", - from_email="Test From , also-from@example.com", - to=["test+to1@anymail.info", "Recipient 2 "], - cc=["test+cc1@anymail.info", "Copy 2 "], - bcc=["test+bcc1@anymail.info", "Blind Copy 2 "], + from_email=from_email, + to=["test+to1@anymail.dev", "Recipient 2 "], + cc=["test+cc1@anymail.dev", "Copy 2 "], + bcc=["test+bcc1@anymail.dev", "Blind Copy 2 "], reply_to=["reply1@example.com", "Reply 2 "], headers={"X-Anymail-Test": "value"}, @@ -136,14 +139,14 @@ class MailgunBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): {"meta1": "simple string", "meta2": "2"}) # all metadata values become strings self.assertEqual(event["message"]["scheduled-for"], send_at_timestamp) - self.assertIn(event["recipient"], ['test+to1@anymail.info', 'test+to2@anymail.info', - 'test+cc1@anymail.info', 'test+cc2@anymail.info', - 'test+bcc1@anymail.info', 'test+bcc2@anymail.info']) + self.assertIn(event["recipient"], ['test+to1@anymail.dev', 'test+to2@anymail.dev', + 'test+cc1@anymail.dev', 'test+cc2@anymail.dev', + 'test+bcc1@anymail.dev', 'test+bcc2@anymail.dev']) headers = event["message"]["headers"] - self.assertEqual(headers["from"], "Test From , also-from@example.com") + self.assertEqual(headers["from"], from_email) self.assertEqual(headers["to"], - "test+to1@anymail.info, Recipient 2 ") + "test+to1@anymail.dev, Recipient 2 ") self.assertEqual(headers["subject"], "Anymail Mailgun all-options integration test") attachments = event["message"]["attachments"] @@ -167,12 +170,12 @@ class MailgunBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): message = AnymailMessage( template_id='test-template', # name of a real template named in Anymail's Mailgun test account subject='Your order %recipient.order%', # Mailgun templates don't define subject - from_email='Test From ', # Mailgun templates don't define sender - to=["test+to1@anymail.info"], + from_email=formataddr(('Test From>', self.from_email)), # Mailgun templates don't define sender + to=["test+to1@anymail.dev"], # metadata and merge_data must not have any conflicting keys when using template_id metadata={"meta1": "simple string", "meta2": 2}, merge_data={ - 'test+to1@anymail.info': { + 'test+to1@anymail.dev': { 'name': "Test Recipient", } }, @@ -182,7 +185,7 @@ class MailgunBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): ) message.send() recipient_status = message.anymail_status.recipients - self.assertEqual(recipient_status['test+to1@anymail.info'].status, 'queued') + self.assertEqual(recipient_status['test+to1@anymail.dev'].status, 'queued') # As of Anymail 0.10, this test is no longer possible, because # Anymail now raises AnymailInvalidAddress without even calling Mailgun diff --git a/tests/test_mailjet_integration.py b/tests/test_mailjet_integration.py index f9e2707..8eaf58e 100644 --- a/tests/test_mailjet_integration.py +++ b/tests/test_mailjet_integration.py @@ -1,5 +1,6 @@ import os import unittest +from email.utils import formataddr from django.test import SimpleTestCase, override_settings, tag @@ -10,12 +11,14 @@ from .utils import AnymailTestMixin, sample_image_path ANYMAIL_TEST_MAILJET_API_KEY = os.getenv('ANYMAIL_TEST_MAILJET_API_KEY') ANYMAIL_TEST_MAILJET_SECRET_KEY = os.getenv('ANYMAIL_TEST_MAILJET_SECRET_KEY') +ANYMAIL_TEST_MAILJET_DOMAIN = os.getenv('ANYMAIL_TEST_MAILJET_DOMAIN') @tag('mailjet', 'live') -@unittest.skipUnless(ANYMAIL_TEST_MAILJET_API_KEY and ANYMAIL_TEST_MAILJET_SECRET_KEY, - "Set ANYMAIL_TEST_MAILJET_API_KEY and ANYMAIL_TEST_MAILJET_SECRET_KEY " - "environment variables to run Mailjet integration tests") +@unittest.skipUnless( + ANYMAIL_TEST_MAILJET_API_KEY and ANYMAIL_TEST_MAILJET_SECRET_KEY and ANYMAIL_TEST_MAILJET_DOMAIN, + "Set ANYMAIL_TEST_MAILJET_API_KEY and ANYMAIL_TEST_MAILJET_SECRET_KEY and ANYMAIL_TEST_MAILJET_DOMAIN " + "environment variables to run Mailjet integration tests") @override_settings(ANYMAIL={"MAILJET_API_KEY": ANYMAIL_TEST_MAILJET_API_KEY, "MAILJET_SECRET_KEY": ANYMAIL_TEST_MAILJET_SECRET_KEY, "MAILJET_SEND_DEFAULTS": {"esp_extra": {"SandboxMode": True}}, # don't actually send mail @@ -26,20 +29,18 @@ class MailjetBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): These tests run against the **live** Mailjet API, using the environment variables `ANYMAIL_TEST_MAILJET_API_KEY` and `ANYMAIL_TEST_MAILJET_SECRET_KEY` - as the API key and API secret key, respectively. - If those variables are not set, these tests won't run. + as the API key and API secret key, respectively, and `ANYMAIL_TEST_MAILJET_DOMAIN` as + a validated Mailjet sending domain. If those variables are not set, these tests won't run. These tests enable Mailjet's SandboxMode to avoid sending any email; remove the esp_extra setting above if you are trying to actually send test messages. - - Mailjet also doesn't support unverified senders (so no from@example.com). - We've set up @test-mj.anymail.info as a validated sending domain for these tests. """ def setUp(self): super().setUp() + self.from_email = 'test@%s' % ANYMAIL_TEST_MAILJET_DOMAIN self.message = AnymailMessage('Anymail Mailjet integration test', 'Text content', - 'test@test-mj.anymail.info', ['test+to1@anymail.info']) + self.from_email, ['test+to1@anymail.dev']) self.message.attach_alternative('

HTML content

', "text/html") def test_simple_send(self): @@ -48,8 +49,8 @@ class MailjetBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): self.assertEqual(sent_count, 1) anymail_status = self.message.anymail_status - sent_status = anymail_status.recipients['test+to1@anymail.info'].status - message_id = anymail_status.recipients['test+to1@anymail.info'].message_id + sent_status = anymail_status.recipients['test+to1@anymail.dev'].status + message_id = anymail_status.recipients['test+to1@anymail.dev'].message_id self.assertEqual(sent_status, 'sent') self.assertRegex(message_id, r'.+') @@ -60,10 +61,10 @@ class MailjetBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): message = AnymailMessage( subject="Anymail Mailjet all-options integration test", body="This is the text body", - from_email='"Test Sender, Inc." ', - to=['test+to1@anymail.info', '"Recipient, 2nd" '], - cc=['test+cc1@anymail.info', 'Copy 2 '], - bcc=['test+bcc1@anymail.info', 'Blind Copy 2 '], + from_email=formataddr(("Test Sender, Inc.", self.from_email)), + to=['test+to1@anymail.dev', '"Recipient, 2nd" '], + cc=['test+cc1@anymail.dev', 'Copy 2 '], + bcc=['test+bcc1@anymail.dev', 'Blind Copy 2 '], reply_to=['"Reply, To" '], # Mailjet only supports single reply_to headers={"X-Anymail-Test": "value"}, @@ -88,11 +89,11 @@ class MailjetBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): subject="Anymail Mailjet merge_data test", # Mailjet doesn't support merge fields in the subject body="This body includes merge data: [[var:value]]\n" "And global merge data: [[var:global]]", - from_email="Test From ", - to=["test+to1@anymail.info", "Recipient 2 "], + from_email=formataddr(("Test From", self.from_email)), + to=["test+to1@anymail.dev", "Recipient 2 "], merge_data={ - 'test+to1@anymail.info': {'value': 'one'}, - 'test+to2@anymail.info': {'value': 'two'}, + 'test+to1@anymail.dev': {'value': 'one'}, + 'test+to2@anymail.dev': {'value': 'two'}, }, merge_global_data={ 'global': 'global_value' @@ -100,15 +101,15 @@ class MailjetBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): ) message.send() recipient_status = message.anymail_status.recipients - self.assertEqual(recipient_status['test+to1@anymail.info'].status, 'sent') - self.assertEqual(recipient_status['test+to2@anymail.info'].status, 'sent') + self.assertEqual(recipient_status['test+to1@anymail.dev'].status, 'sent') + self.assertEqual(recipient_status['test+to2@anymail.dev'].status, 'sent') def test_stored_template(self): message = AnymailMessage( template_id='176375', # ID of the real template named 'test-template' in our Mailjet test account - to=["test+to1@anymail.info"], + to=["test+to1@anymail.dev"], merge_data={ - 'test+to1@anymail.info': { + 'test+to1@anymail.dev': { 'name': "Test Recipient", } }, @@ -119,7 +120,7 @@ class MailjetBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): message.from_email = None # use the template's sender email/name message.send() recipient_status = message.anymail_status.recipients - self.assertEqual(recipient_status['test+to1@anymail.info'].status, 'sent') + self.assertEqual(recipient_status['test+to1@anymail.dev'].status, 'sent') @override_settings(ANYMAIL={"MAILJET_API_KEY": "Hey, that's not an API key!", "MAILJET_SECRET_KEY": "and this isn't the secret for it"}) diff --git a/tests/test_mandrill_integration.py b/tests/test_mandrill_integration.py index e92779a..0a8e967 100644 --- a/tests/test_mandrill_integration.py +++ b/tests/test_mandrill_integration.py @@ -1,5 +1,6 @@ import os import unittest +from email.utils import formataddr from django.core import mail from django.test import SimpleTestCase, override_settings, tag @@ -10,11 +11,13 @@ from anymail.message import AnymailMessage from .utils import AnymailTestMixin, sample_image_path ANYMAIL_TEST_MANDRILL_API_KEY = os.getenv('ANYMAIL_TEST_MANDRILL_API_KEY') +ANYMAIL_TEST_MANDRILL_DOMAIN = os.getenv('ANYMAIL_TEST_MANDRILL_DOMAIN') @tag('mandrill', 'live') -@unittest.skipUnless(ANYMAIL_TEST_MANDRILL_API_KEY, - "Set ANYMAIL_TEST_MANDRILL_API_KEY environment variable to run integration tests") +@unittest.skipUnless(ANYMAIL_TEST_MANDRILL_API_KEY and ANYMAIL_TEST_MANDRILL_DOMAIN, + "Set ANYMAIL_TEST_MANDRILL_API_KEY and ANYMAIL_TEST_MANDRILL_DOMAIN " + "environment variables to run integration tests") @override_settings(MANDRILL_API_KEY=ANYMAIL_TEST_MANDRILL_API_KEY, EMAIL_BACKEND="anymail.backends.mandrill.EmailBackend") class MandrillBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): @@ -26,13 +29,13 @@ class MandrillBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): See https://mandrill.zendesk.com/hc/en-us/articles/205582447 for info on Mandrill test keys. - """ def setUp(self): super().setUp() + self.from_email = 'from@%s' % ANYMAIL_TEST_MANDRILL_DOMAIN self.message = mail.EmailMultiAlternatives('Anymail Mandrill integration test', 'Text content', - 'from@example.com', ['test+to1@anymail.info']) + self.from_email, ['test+to1@anymail.dev']) self.message.attach_alternative('

HTML content

', "text/html") def test_simple_send(self): @@ -42,8 +45,8 @@ class MandrillBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): # noinspection PyUnresolvedReferences anymail_status = self.message.anymail_status - sent_status = anymail_status.recipients['test+to1@anymail.info'].status - message_id = anymail_status.recipients['test+to1@anymail.info'].message_id + sent_status = anymail_status.recipients['test+to1@anymail.dev'].status + message_id = anymail_status.recipients['test+to1@anymail.dev'].message_id self.assertIn(sent_status, ['sent', 'queued']) # successful send (could still bounce later) self.assertGreater(len(message_id), 0) # don't know what it'll be, but it should exist @@ -55,10 +58,10 @@ class MandrillBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): message = AnymailMessage( subject="Anymail Mandrill all-options integration test", body="This is the text body", - from_email="Test From ", - to=["test+to1@anymail.info", "Recipient 2 "], - cc=["test+cc1@anymail.info", "Copy 2 "], - bcc=["test+bcc1@anymail.info", "Blind Copy 2 "], + from_email=formataddr(("Test From, with comma", self.from_email)), + to=["test+to1@anymail.dev", "Recipient 2 "], + cc=["test+cc1@anymail.dev", "Copy 2 "], + bcc=["test+bcc1@anymail.dev", "Blind Copy 2 "], reply_to=["reply1@example.com", "Reply 2 "], headers={"X-Anymail-Test": "value"}, diff --git a/tests/test_postal_integration.py b/tests/test_postal_integration.py index 944b757..37c81eb 100644 --- a/tests/test_postal_integration.py +++ b/tests/test_postal_integration.py @@ -1,5 +1,6 @@ import os import unittest +from email.utils import formataddr from django.test import SimpleTestCase, override_settings, tag @@ -11,12 +12,14 @@ from .utils import AnymailTestMixin ANYMAIL_TEST_POSTAL_API_KEY = os.getenv('ANYMAIL_TEST_POSTAL_API_KEY') ANYMAIL_TEST_POSTAL_API_URL = os.getenv('ANYMAIL_TEST_POSTAL_API_URL') +ANYMAIL_TEST_POSTAL_DOMAIN = os.getenv('ANYMAIL_TEST_POSTAL_DOMAIN') @tag('postal', 'live') -@unittest.skipUnless(ANYMAIL_TEST_POSTAL_API_KEY and ANYMAIL_TEST_POSTAL_API_URL, - "Set ANYMAIL_TEST_POSTAL_API_KEY and ANYMAIL_TEST_POSTAL_API_URL " - "environment variables to run Postal integration tests") +@unittest.skipUnless( + ANYMAIL_TEST_POSTAL_API_KEY and ANYMAIL_TEST_POSTAL_API_URL and ANYMAIL_TEST_POSTAL_DOMAIN, + "Set ANYMAIL_TEST_POSTAL_API_KEY and ANYMAIL_TEST_POSTAL_API_URL and ANYMAIL_TEST_POSTAL_DOMAIN " + "environment variables to run Postal integration tests") @override_settings(ANYMAIL_POSTAL_API_KEY=ANYMAIL_TEST_POSTAL_API_KEY, ANYMAIL_POSTAL_API_URL=ANYMAIL_TEST_POSTAL_API_URL, EMAIL_BACKEND="anymail.backends.postal.EmailBackend") @@ -31,8 +34,9 @@ class PostalBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): def setUp(self): super().setUp() + self.from_email = 'from@%s' % ANYMAIL_TEST_POSTAL_DOMAIN self.message = AnymailMessage('Anymail Postal integration test', 'Text content', - 'from@example.com', ['test+to1@anymail.info']) + self.from_email, ['test+to1@anymail.dev']) self.message.attach_alternative('

HTML content

', "text/html") def test_simple_send(self): @@ -41,8 +45,8 @@ class PostalBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): self.assertEqual(sent_count, 1) anymail_status = self.message.anymail_status - sent_status = anymail_status.recipients['test+to1@anymail.info'].status - message_id = anymail_status.recipients['test+to1@anymail.info'].message_id + sent_status = anymail_status.recipients['test+to1@anymail.dev'].status + message_id = anymail_status.recipients['test+to1@anymail.dev'].message_id self.assertEqual(sent_status, 'queued') self.assertGreater(len(message_id), 0) # non-empty string @@ -53,11 +57,11 @@ class PostalBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): message = AnymailMessage( subject="Anymail Postal all-options integration test", body="This is the text body", - from_email="Test From ", - envelope_sender="bounces@example.com", - to=["test+to1@anymail.info", "Recipient 2 "], - cc=["test+cc1@anymail.info", "Copy 2 "], - bcc=["test+bcc1@anymail.info", "Blind Copy 2 "], + from_email=formataddr(("Test From, with comma", self.from_email)), + envelope_sender="bounces@%s" % ANYMAIL_TEST_POSTAL_DOMAIN, + to=["test+to1@anymail.dev", "Recipient 2 "], + cc=["test+cc1@anymail.dev", "Copy 2 "], + bcc=["test+bcc1@anymail.dev", "Blind Copy 2 "], reply_to=["reply1@example.com"], headers={"X-Anymail-Test": "value"}, tags=["tag 1"], # max one tag @@ -67,11 +71,11 @@ class PostalBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): message.send() self.assertEqual(message.anymail_status.status, {'queued'}) - self.assertEqual(message.anymail_status.recipients['test+to1@anymail.info'].status, 'queued') - self.assertEqual(message.anymail_status.recipients['test+to2@anymail.info'].status, 'queued') + self.assertEqual(message.anymail_status.recipients['test+to1@anymail.dev'].status, 'queued') + self.assertEqual(message.anymail_status.recipients['test+to2@anymail.dev'].status, 'queued') # distinct messages should have different message_ids: - self.assertNotEqual(message.anymail_status.recipients['test+to1@anymail.info'].message_id, - message.anymail_status.recipients['teset+to2@anymail.info'].message_id) + self.assertNotEqual(message.anymail_status.recipients['test+to1@anymail.dev'].message_id, + message.anymail_status.recipients['teset+to2@anymail.dev'].message_id) def test_invalid_from(self): self.message.from_email = 'webmaster@localhost' # Django's default From diff --git a/tests/test_postmark_integration.py b/tests/test_postmark_integration.py index c4822a4..2364840 100644 --- a/tests/test_postmark_integration.py +++ b/tests/test_postmark_integration.py @@ -1,5 +1,6 @@ import os import unittest +from email.utils import formataddr from django.test import SimpleTestCase, override_settings, tag @@ -13,9 +14,13 @@ from .utils import AnymailTestMixin, sample_image_path # But to test template sends, a real Postmark server token and template id are needed: ANYMAIL_TEST_POSTMARK_SERVER_TOKEN = os.getenv('ANYMAIL_TEST_POSTMARK_SERVER_TOKEN') ANYMAIL_TEST_POSTMARK_TEMPLATE_ID = os.getenv('ANYMAIL_TEST_POSTMARK_TEMPLATE_ID') +ANYMAIL_TEST_POSTMARK_DOMAIN = os.getenv('ANYMAIL_TEST_POSTMARK_DOMAIN') @tag('postmark', 'live') +@unittest.skipUnless(ANYMAIL_TEST_POSTMARK_DOMAIN, + "Set ANYMAIL_TEST_POSTMARK_DOMAIN environment variable " + "to run Postmark template integration tests") @override_settings(ANYMAIL_POSTMARK_SERVER_TOKEN="POSTMARK_API_TEST", EMAIL_BACKEND="anymail.backends.postmark.EmailBackend") class PostmarkBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): @@ -27,8 +32,9 @@ class PostmarkBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): def setUp(self): super().setUp() + self.from_email = 'from@%s' % ANYMAIL_TEST_POSTMARK_DOMAIN self.message = AnymailMessage('Anymail Postmark integration test', 'Text content', - 'from@example.com', ['test+to1@anymail.info']) + self.from_email, ['test+to1@anymail.dev']) self.message.attach_alternative('

HTML content

', "text/html") def test_simple_send(self): @@ -37,8 +43,8 @@ class PostmarkBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): self.assertEqual(sent_count, 1) anymail_status = self.message.anymail_status - sent_status = anymail_status.recipients['test+to1@anymail.info'].status - message_id = anymail_status.recipients['test+to1@anymail.info'].message_id + sent_status = anymail_status.recipients['test+to1@anymail.dev'].status + message_id = anymail_status.recipients['test+to1@anymail.dev'].message_id self.assertEqual(sent_status, 'sent') self.assertGreater(len(message_id), 0) # non-empty string @@ -49,11 +55,10 @@ class PostmarkBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): message = AnymailMessage( subject="Anymail Postmark all-options integration test", body="This is the text body", - # Postmark accepts multiple from_email addresses, but truncates to the first on their end - from_email="Test From , also-from@example.com", - to=["test+to1@anymail.info", "Recipient 2 "], - cc=["test+cc1@anymail.info", "Copy 2 "], - bcc=["test+bcc1@anymail.info", "Blind Copy 2 "], + from_email=formataddr(("Test From, with comma", self.from_email)), + to=["test+to1@anymail.dev", "Recipient 2 "], + cc=["test+cc1@anymail.dev", "Copy 2 "], + bcc=["test+bcc1@anymail.dev", "Blind Copy 2 "], reply_to=["reply1@example.com", "Reply 2 "], headers={"X-Anymail-Test": "value"}, @@ -74,11 +79,11 @@ class PostmarkBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): message.send() self.assertEqual(message.anymail_status.status, {'sent'}) - self.assertEqual(message.anymail_status.recipients['test+to1@anymail.info'].status, 'sent') - self.assertEqual(message.anymail_status.recipients['test+to2@anymail.info'].status, 'sent') + self.assertEqual(message.anymail_status.recipients['test+to1@anymail.dev'].status, 'sent') + self.assertEqual(message.anymail_status.recipients['test+to2@anymail.dev'].status, 'sent') # distinct messages should have different message_ids: - self.assertNotEqual(message.anymail_status.recipients['test+to1@anymail.info'].message_id, - message.anymail_status.recipients['test+to2@anymail.info'].message_id) + self.assertNotEqual(message.anymail_status.recipients['test+to1@anymail.dev'].message_id, + message.anymail_status.recipients['test+to2@anymail.dev'].message_id) def test_invalid_from(self): self.message.from_email = 'webmaster@localhost' # Django's default From @@ -88,18 +93,21 @@ class PostmarkBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): self.assertEqual(err.status_code, 422) self.assertIn("Invalid 'From' address", str(err)) - @unittest.skipUnless(ANYMAIL_TEST_POSTMARK_SERVER_TOKEN and ANYMAIL_TEST_POSTMARK_TEMPLATE_ID, - "Set ANYMAIL_TEST_POSTMARK_SERVER_TOKEN and ANYMAIL_TEST_POSTMARK_TEMPLATE_ID " - "environment variables to run Postmark template integration tests") + @unittest.skipUnless( + ANYMAIL_TEST_POSTMARK_SERVER_TOKEN + and ANYMAIL_TEST_POSTMARK_TEMPLATE_ID + and ANYMAIL_TEST_POSTMARK_DOMAIN, + "Set ANYMAIL_TEST_POSTMARK_SERVER_TOKEN and ANYMAIL_TEST_POSTMARK_TEMPLATE_ID " + "and ANYMAIL_TEST_POSTMARK_DOMAIN environment variables to run Postmark template integration tests") @override_settings(ANYMAIL_POSTMARK_SERVER_TOKEN=ANYMAIL_TEST_POSTMARK_SERVER_TOKEN) def test_template(self): message = AnymailMessage( - from_email="from@test-pm.anymail.info", - to=["test+to1@anymail.info", "Second Recipient "], + from_email=self.from_email, + to=["test+to1@anymail.dev", "Second Recipient "], template_id=ANYMAIL_TEST_POSTMARK_TEMPLATE_ID, merge_data={ - "test+to1@anymail.info": {"name": "Recipient 1", "order_no": "12345"}, - "test+to2@anymail.info": {"order_no": "6789"}, + "test+to1@anymail.dev": {"name": "Recipient 1", "order_no": "12345"}, + "test+to2@anymail.dev": {"order_no": "6789"}, }, merge_global_data={"name": "Valued Customer"}, ) diff --git a/tests/test_sendgrid_integration.py b/tests/test_sendgrid_integration.py index 5c5b6f7..bc499d3 100644 --- a/tests/test_sendgrid_integration.py +++ b/tests/test_sendgrid_integration.py @@ -1,6 +1,7 @@ import os import unittest from datetime import datetime, timedelta +from email.utils import formataddr from django.test import SimpleTestCase, override_settings, tag @@ -11,12 +12,13 @@ from .utils import AnymailTestMixin, sample_image_path ANYMAIL_TEST_SENDGRID_API_KEY = os.getenv('ANYMAIL_TEST_SENDGRID_API_KEY') ANYMAIL_TEST_SENDGRID_TEMPLATE_ID = os.getenv('ANYMAIL_TEST_SENDGRID_TEMPLATE_ID') +ANYMAIL_TEST_SENDGRID_DOMAIN = os.getenv('ANYMAIL_TEST_SENDGRID_DOMAIN') @tag('sendgrid', 'live') -@unittest.skipUnless(ANYMAIL_TEST_SENDGRID_API_KEY, - "Set ANYMAIL_TEST_SENDGRID_API_KEY environment variable " - "to run SendGrid integration tests") +@unittest.skipUnless(ANYMAIL_TEST_SENDGRID_API_KEY and ANYMAIL_TEST_SENDGRID_DOMAIN, + "Set ANYMAIL_TEST_SENDGRID_API_KEY and ANYMAIL_TEST_SENDGRID_DOMAIN " + "environment variables to run SendGrid integration tests") @override_settings(ANYMAIL_SENDGRID_API_KEY=ANYMAIL_TEST_SENDGRID_API_KEY, ANYMAIL_SENDGRID_SEND_DEFAULTS={"esp_extra": { "mail_settings": {"sandbox_mode": {"enable": True}}, @@ -34,13 +36,13 @@ class SendGridBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): The tests also use SendGrid's "sink domain" @sink.sendgrid.net for recipient addresses. https://support.sendgrid.com/hc/en-us/articles/201995663-Safely-Test-Your-Sending-Speed - """ def setUp(self): super().setUp() + self.from_email = 'from@%s' % ANYMAIL_TEST_SENDGRID_DOMAIN self.message = AnymailMessage('Anymail SendGrid integration test', 'Text content', - 'from@example.com', ['to@sink.sendgrid.net']) + self.from_email, ['to@sink.sendgrid.net']) self.message.attach_alternative('

HTML content

', "text/html") def test_simple_send(self): @@ -62,7 +64,7 @@ class SendGridBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): message = AnymailMessage( subject="Anymail all-options integration test", body="This is the text body", - from_email='"Test From, with comma" ', + from_email=formataddr(("Test From, with comma", self.from_email)), to=["to1@sink.sendgrid.net", '"Recipient 2, OK?" '], cc=["cc1@sink.sendgrid.net", "Copy 2 "], bcc=["bcc1@sink.sendgrid.net", "Blind Copy 2 "], @@ -91,7 +93,7 @@ class SendGridBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): message = AnymailMessage( subject="Anymail merge_data test: %field%", body="This body includes merge data: %field%", - from_email="Test From ", + from_email=formataddr(("Test From", self.from_email)), to=["to1@sink.sendgrid.net", "Recipient 2 "], reply_to=['"Merge data in reply name: %field%" '], merge_data={ @@ -112,7 +114,7 @@ class SendGridBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): "to a template in your SendGrid account to test stored templates") def test_stored_template(self): message = AnymailMessage( - from_email="Test From ", + from_email=formataddr(("Test From", self.from_email)), to=["to@sink.sendgrid.net"], # Anymail's live test template has merge fields "name", "order_no", and "dept"... template_id=ANYMAIL_TEST_SENDGRID_TEMPLATE_ID, diff --git a/tests/test_sendinblue_integration.py b/tests/test_sendinblue_integration.py index 0c0ca0a..52b4164 100644 --- a/tests/test_sendinblue_integration.py +++ b/tests/test_sendinblue_integration.py @@ -1,5 +1,6 @@ import os import unittest +from email.utils import formataddr from django.test import SimpleTestCase, override_settings, tag @@ -9,12 +10,13 @@ from anymail.message import AnymailMessage from .utils import AnymailTestMixin ANYMAIL_TEST_SENDINBLUE_API_KEY = os.getenv('ANYMAIL_TEST_SENDINBLUE_API_KEY') +ANYMAIL_TEST_SENDINBLUE_DOMAIN = os.getenv('ANYMAIL_TEST_SENDINBLUE_DOMAIN') @tag('sendinblue', 'live') -@unittest.skipUnless(ANYMAIL_TEST_SENDINBLUE_API_KEY, - "Set ANYMAIL_TEST_SENDINBLUE_API_KEY environment variable " - "to run SendinBlue integration tests") +@unittest.skipUnless(ANYMAIL_TEST_SENDINBLUE_API_KEY and ANYMAIL_TEST_SENDINBLUE_DOMAIN, + "Set ANYMAIL_TEST_SENDINBLUE_API_KEY and ANYMAIL_TEST_SENDINBLUE_DOMAIN " + "environment variables to run SendinBlue integration tests") @override_settings(ANYMAIL_SENDINBLUE_API_KEY=ANYMAIL_TEST_SENDINBLUE_API_KEY, ANYMAIL_SENDINBLUE_SEND_DEFAULTS=dict(), EMAIL_BACKEND="anymail.backends.sendinblue.EmailBackend") @@ -23,7 +25,8 @@ class SendinBlueBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): SendinBlue doesn't have sandbox so these tests run against the **live** SendinBlue API, using the - environment variable `ANYMAIL_TEST_SENDINBLUE_API_KEY` as the API key + environment variable `ANYMAIL_TEST_SENDINBLUE_API_KEY` as the API key, + and `ANYMAIL_TEST_SENDINBLUE_DOMAIN` to construct sender addresses. If those variables are not set, these tests won't run. https://developers.sendinblue.com/docs/faq#section-how-can-i-test-the-api- @@ -32,9 +35,9 @@ class SendinBlueBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): def setUp(self): super().setUp() - + self.from_email = 'from@%s' % ANYMAIL_TEST_SENDINBLUE_DOMAIN self.message = AnymailMessage('Anymail SendinBlue integration test', 'Text content', - 'from@test-sb.anymail.info', ['test+to1@anymail.info']) + self.from_email, ['test+to1@anymail.dev']) self.message.attach_alternative('

HTML content

', "text/html") def test_simple_send(self): @@ -43,8 +46,8 @@ class SendinBlueBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): self.assertEqual(sent_count, 1) anymail_status = self.message.anymail_status - sent_status = anymail_status.recipients['test+to1@anymail.info'].status - message_id = anymail_status.recipients['test+to1@anymail.info'].message_id + sent_status = anymail_status.recipients['test+to1@anymail.dev'].status + message_id = anymail_status.recipients['test+to1@anymail.dev'].message_id self.assertEqual(sent_status, 'queued') # SendinBlue always queues self.assertRegex(message_id, r'\<.+@.+\>') # Message-ID can be ...@smtp-relay.mail.fr or .sendinblue.com @@ -55,10 +58,10 @@ class SendinBlueBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): message = AnymailMessage( subject="Anymail SendinBlue all-options integration test", body="This is the text body", - from_email='"Test From, with comma" ', - to=["test+to1@anymail.info", '"Recipient 2, OK?" '], - cc=["test+cc1@anymail.info", "Copy 2 "], - bcc=["test+bcc1@anymail.info", "Blind Copy 2 "], + from_email=formataddr(("Test From, with comma", self.from_email)), + to=["test+to1@anymail.dev", '"Recipient 2, OK?" '], + cc=["test+cc1@anymail.dev", "Copy 2 "], + bcc=["test+bcc1@anymail.dev", "Blind Copy 2 "], reply_to=['"Reply, with comma" '], # SendinBlue API v3 only supports single reply-to headers={"X-Anymail-Test": "value", "X-Anymail-Count": 3}, @@ -77,9 +80,9 @@ class SendinBlueBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): def test_template(self): message = AnymailMessage( template_id=5, # There is a *new-style* template with this id in the Anymail test account - from_email='Sender ', # Override template sender - to=["Recipient "], # No batch send (so max one recipient suggested) - reply_to=["Do not reply "], + from_email=formataddr(('Sender', self.from_email)), # Override template sender + to=["Recipient "], # No batch send (so max one recipient suggested) + reply_to=["Do not reply "], tags=["using-template"], headers={"X-Anymail-Test": "group: A, variation: C"}, merge_global_data={ diff --git a/tests/test_sparkpost_integration.py b/tests/test_sparkpost_integration.py index e87ef1d..bcba54c 100644 --- a/tests/test_sparkpost_integration.py +++ b/tests/test_sparkpost_integration.py @@ -1,6 +1,7 @@ import os import unittest from datetime import datetime, timedelta +from email.utils import formataddr from django.test import SimpleTestCase, override_settings, tag @@ -10,12 +11,13 @@ from anymail.message import AnymailMessage from .utils import AnymailTestMixin, sample_image_path ANYMAIL_TEST_SPARKPOST_API_KEY = os.getenv('ANYMAIL_TEST_SPARKPOST_API_KEY') +ANYMAIL_TEST_SPARKPOST_DOMAIN = os.getenv('ANYMAIL_TEST_SPARKPOST_DOMAIN') @tag('sparkpost', 'live') -@unittest.skipUnless(ANYMAIL_TEST_SPARKPOST_API_KEY, - "Set ANYMAIL_TEST_SPARKPOST_API_KEY environment variable " - "to run SparkPost integration tests") +@unittest.skipUnless(ANYMAIL_TEST_SPARKPOST_API_KEY and ANYMAIL_TEST_SPARKPOST_DOMAIN, + "Set ANYMAIL_TEST_SPARKPOST_API_KEY and ANYMAIL_TEST_SPARKPOST_DOMAIN " + "environment variables to run SparkPost integration tests") @override_settings(ANYMAIL_SPARKPOST_API_KEY=ANYMAIL_TEST_SPARKPOST_API_KEY, EMAIL_BACKEND="anymail.backends.sparkpost.EmailBackend") class SparkPostBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): @@ -29,15 +31,13 @@ class SparkPostBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): you ask. To avoid stacking up a pile of undeliverable @example.com emails, the tests use SparkPost's "sink domain" @*.sink.sparkpostmail.com. https://www.sparkpost.com/docs/faq/using-sink-server/ - - SparkPost also doesn't support arbitrary senders (so no from@example.com). - We've set up @test-sp.anymail.info as a validated sending domain for these tests. """ def setUp(self): super().setUp() + self.from_email = 'test@%s' % ANYMAIL_TEST_SPARKPOST_DOMAIN self.message = AnymailMessage('Anymail SparkPost integration test', 'Text content', - 'test@test-sp.anymail.info', ['to@test.sink.sparkpostmail.com']) + self.from_email, ['to@test.sink.sparkpostmail.com']) self.message.attach_alternative('

HTML content

', "text/html") def test_simple_send(self): @@ -59,7 +59,7 @@ class SparkPostBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): message = AnymailMessage( subject="Anymail all-options integration test", body="This is the text body", - from_email="Test From ", + from_email=formataddr(("Test From, with comma", self.from_email)), to=["to1@test.sink.sparkpostmail.com", "Recipient 2 "], # Limit the live b/cc's to avoid running through our small monthly allowance: # cc=["cc1@test.sink.sparkpostmail.com", "Copy 2 "], @@ -90,7 +90,7 @@ class SparkPostBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): subject="Anymail merge_data test: {{ value }}", body="This body includes merge data: {{ value }}\n" "And global merge data: {{ global }}", - from_email="Test From ", + from_email=formataddr(("Test From", self.from_email)), to=["to1@test.sink.sparkpostmail.com", "Recipient 2 "], merge_data={ 'to1@test.sink.sparkpostmail.com': {'value': 'one'},