def test_all_options(self): message = AnymailMessage( subject="Anymail Postal all-options integration test", body="This is the text body", from_email="Test From <*****@*****.**>", envelope_sender="*****@*****.**", to=[ "*****@*****.**", "Recipient 2 <*****@*****.**>" ], cc=["*****@*****.**", "Copy 2 <*****@*****.**>"], bcc=[ "*****@*****.**", "Blind Copy 2 <*****@*****.**>" ], reply_to=["*****@*****.**"], headers={"X-Anymail-Test": "value"}, tags=["tag 1"], # max one tag ) message.attach("attachment1.txt", "Here is some\ntext for you", "text/plain") message.attach("attachment2.csv", "ID,Name\n1,Amy Lina", "text/csv") message.send() self.assertEqual(message.anymail_status.status, {'queued'}) self.assertEqual( message.anymail_status.recipients['*****@*****.**'].status, 'queued') self.assertEqual( message.anymail_status.recipients['*****@*****.**'].status, 'queued') # distinct messages should have different message_ids: self.assertNotEqual( message.anymail_status.recipients['*****@*****.**']. message_id, message.anymail_status. recipients['*****@*****.**'].message_id)
def test_all_options(self): message = AnymailMessage( subject="Anymail all-options integration test", body="This is the text body", from_email="Test From <*****@*****.**>", to=["*****@*****.**", "Recipient 2 <*****@*****.**>"], cc=["*****@*****.**", "Copy 2 <*****@*****.**>"], bcc=["*****@*****.**", "Blind Copy 2 <*****@*****.**>"], reply_to=["*****@*****.**", "Reply 2 <*****@*****.**>"], headers={"X-Anymail-Test": "value"}, # no metadata, send_at, track_clicks support tags=["tag 1"], # max one tag track_opens=True, ) message.attach("attachment1.txt", "Here is some\ntext for you", "text/plain") message.attach("attachment2.csv", "ID,Name\n1,Amy Lina", "text/csv") cid = message.attach_inline_image_file(sample_image_path()) message.attach_alternative( "<p><b>HTML:</b> with <a href='http://example.com'>link</a>" "and image: <img src='cid:%s'></div>" % cid, "text/html") message.send() self.assertTrue(message.anymail_status.status.issubset({'queued', 'sent'}))
def test_all_options(self): send_at = datetime.now().replace(microsecond=0) + timedelta(minutes=2) message = AnymailMessage( subject="Anymail all-options integration test FILES", body="This is the text body", from_email="Test From <*****@*****.**>", to=["*****@*****.**", "Recipient 2 <*****@*****.**>"], cc=["*****@*****.**", "Copy 2 <*****@*****.**>"], bcc=["*****@*****.**", "Blind Copy 2 <*****@*****.**>"], reply_to=["*****@*****.**", "Reply 2 <*****@*****.**>"], headers={"X-Anymail-Test": "value"}, metadata={"meta1": "simple string", "meta2": 2}, send_at=send_at, tags=["tag 1", "tag 2"], track_clicks=True, track_opens=True, ) message.attach("attachment1.txt", "Here is some\ntext for you", "text/plain") message.attach("attachment2.csv", "ID,Name\n1,Amy Lina", "text/csv") cid = message.attach_inline_image_file(sample_image_path()) message.attach_alternative( "<p><b>HTML:</b> with <a href='http://example.com'>link</a>" "and image: <img src='cid:%s'></div>" % cid, "text/html") message.send() self.assertEqual(message.anymail_status.status, {'queued'}) # SendGrid always queues
def email_user(self, subject, context, text_template, html_template=None, tag='Default'): from django.template.loader import render_to_string context['user'] = self text_message = render_to_string(text_template, context) if html_template: html_message = render_to_string(html_template, context) else: html_message = None # send_mail(subject, text_message, settings.DEFAULT_FROM_EMAIL, [self.email], fail_silently=False, # html_message=html_message) message = AnymailMessage( subject=subject, body=text_message, to=["%s <%s>" % (self.full_name or self.username, self.email)], tags=[tag], ) if html_message: message.attach_alternative(html_message, 'text/html') message.track_clicks = True message.send()
def handle_prison(self, credit_notice_email, path, date, **options): call_command( 'create_prisoner_credit_notices', path, credit_notice_email.prison.nomis_id, date=date, **options ) if not path.exists(): if self.verbosity: self.stdout.write('Nothing to send to %s' % credit_notice_email) return template_context = prepare_context() text_body = template_loader.get_template('credit/prisoner-notice-email.txt').render(template_context) html_body = template_loader.get_template('credit/prisoner-notice-email.html').render(template_context) email = AnymailMessage( subject=str(self.subject), body=text_body.strip('\n'), from_email=self.from_address, to=[credit_notice_email.email], tags=['prisoner-notice'], ) email.attach_alternative(html_body, 'text/html') email.attach_file(str(path), mimetype='application/pdf') if self.verbosity: self.stdout.write('Sending prisoner notice email to %s' % credit_notice_email) email.send()
def test_stored_template(self): 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=["*****@*****.**"], # metadata and merge_data must not have any conflicting keys when using template_id metadata={ "meta1": "simple string", "meta2": 2 }, merge_data={'*****@*****.**': { 'name': "Test Recipient", }}, merge_global_data={ 'order': '12345', }, ) message.send() recipient_status = message.anymail_status.recipients self.assertEqual(recipient_status['*****@*****.**'].status, 'queued')
def send_report(period_description, report_path, emails): email = AnymailMessage( subject=f'Prisoner money notifications for {period_description}', body=f""" OFFICIAL SENSITIVE Please find attached, the prisoner money notifications report for {period_description}. There is a separate sheet for each notification rule for credits and disbursements. The ‘Monitored by’ column that appears in some sheets is the number of users who are monitoring that prisoner or payment source. The ‘How many?’ column that appears in some sheets is the number that triggered the rule in column A. For example, if the ‘How many?’ column says 4 for the rule ‘More than 2 credits from the same debit card or bank account to any prisoner in a week’, then this means that a specific debit card or bank account sent 4 credits in a week up to when that credit was sent. If you have any queries, contact the team at {settings.TEAM_EMAIL}. """.strip(), from_email=default_from_address(), to=emails, tags=['notifications-report'], ) email.attach_file(str(report_path), mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') email.send()
def test_all_options(self): send_at = datetime.now() + timedelta(minutes=2) message = AnymailMessage( subject="Anymail all-options integration test", body="This is the text body", from_email="Test From <*****@*****.**>", to=["*****@*****.**", "Recipient 2 <*****@*****.**>"], cc=["*****@*****.**", "Copy 2 <*****@*****.**>"], bcc=["*****@*****.**", "Blind Copy 2 <*****@*****.**>"], reply_to=["*****@*****.**", "Reply 2 <*****@*****.**>"], headers={"X-Anymail-Test": "value"}, metadata={"meta1": "simple string", "meta2": 2}, send_at=send_at, tags=["tag 1"], # SparkPost only supports single tags track_clicks=True, track_opens=True, ) message.attach("attachment1.txt", "Here is some\ntext for you", "text/plain") message.attach("attachment2.csv", "ID,Name\n1,Amy Lina", "text/csv") cid = message.attach_inline_image_file(sample_image_path()) message.attach_alternative( "<p><b>HTML:</b> with <a href='http://example.com'>link</a>" "and image: <img src='cid:%s'></div>" % cid, "text/html") message.send() self.assertEqual(message.anymail_status.status, {'queued'}) # SparkPost always queues
def test_all_options(self): message = AnymailMessage( subject="Anymail all-options integration test", body="This is the text body", from_email='"Test Sender, Inc." <*****@*****.**>', to=['*****@*****.**', '"Recipient, 2nd" <*****@*****.**>'], cc=['*****@*****.**', 'Copy 2 <*****@*****.**>'], bcc=['*****@*****.**', 'Blind Copy 2 <*****@*****.**>'], reply_to=['*****@*****.**', '"Reply, 2nd" <*****@*****.**>'], headers={"X-Anymail-Test": "value"}, metadata={"meta1": "simple string", "meta2": 2}, tags=["tag 1"], # Mailjet only allows a single tag track_clicks=True, track_opens=True, ) message.attach("attachment1.txt", "Here is some\ntext for you", "text/plain") message.attach("attachment2.csv", "ID,Name\n1,Amy Lina", "text/csv") cid = message.attach_inline_image_file(sample_image_path()) message.attach_alternative( "<p><b>HTML:</b> with <a href='http://example.com'>link</a>" "and image: <img src='cid:%s'></div>" % cid, "text/html") message.send() self.assertEqual(message.anymail_status.status, {'sent'})
def test_all_options(self): message = AnymailMessage( subject="Anymail all-options integration test", body="This is the text body", from_email="Test From <*****@*****.**>", to=["*****@*****.**", "Recipient 2 <*****@*****.**>"], cc=["*****@*****.**", "Copy 2 <*****@*****.**>"], bcc=["*****@*****.**", "Blind Copy 2 <*****@*****.**>"], reply_to=["*****@*****.**", "Reply 2 <*****@*****.**>"], headers={"X-Anymail-Test": "value"}, # no metadata, send_at, track_clicks support tags=["tag 1"], # max one tag track_opens=True, ) message.attach("attachment1.txt", "Here is some\ntext for you", "text/plain") message.attach("attachment2.csv", "ID,Name\n1,Amy Lina", "text/csv") cid = message.attach_inline_image_file(sample_image_path()) message.attach_alternative( "<p><b>HTML:</b> with <a href='http://example.com'>link</a>" "and image: <img src='cid:%s'></div>" % cid, "text/html") message.send() self.assertTrue( message.anymail_status.status.issubset({'queued', 'sent'}))
def test_template(self): message = AnymailMessage( template_id= 1, # There is a template with this id in the Anymail test account to=[ "*****@*****.**" ], # SendinBlue doesn't allow recipient display names with templates reply_to=["*****@*****.**"], tags=["using-template"], headers={"X-Anymail-Test": "group: A, variation: C"}, merge_global_data={ # The Anymail test template includes `%SHIP_DATE%` and `%ORDER_ID%` variables "SHIP_DATE": "yesterday", "ORDER_ID": "12345", }, metadata={ "customer-id": "ZXK9123", "meta2": 2 }, ) message.from_email = None # Required for SendinBlue templates message.attach("attachment1.txt", "Here is some\ntext for you", "text/plain") message.send() self.assertEqual(message.anymail_status.status, {'queued'}) # SendinBlue always queues self.assertRegex(message.anymail_status.message_id, r'\<.+@.+\>')
def handle_prison(self, credit_notice_email, path, date, **options): call_command('create_prisoner_credit_notices', path, credit_notice_email.prison.nomis_id, date=date, **options) if not path.exists(): if self.verbosity: self.stdout.write('Nothing to send to %s' % credit_notice_email) return template_context = { 'static_url': urljoin(settings.SITE_URL, settings.STATIC_URL), } text_body = template_loader.get_template( 'credit/prisoner-notice-email.txt').render(template_context) html_body = template_loader.get_template( 'credit/prisoner-notice-email.html').render(template_context) email = AnymailMessage( subject=self.subject, body=text_body.strip('\n'), from_email=self.from_address, to=[credit_notice_email.email], tags=['prisoner-notice'], ) email.attach_alternative(html_body, 'text/html') email.attach_file(str(path), mimetype='application/pdf') if self.verbosity: self.stdout.write('Sending prisoner notice email to %s' % credit_notice_email) email.send()
def test_all_options(self): message = AnymailMessage( subject="Anymail Amazon SES all-options integration test", body="This is the text body", from_email='"Test From" <*****@*****.**>', to=["*****@*****.**", "Recipient 2 <*****@*****.**>"], cc=["*****@*****.**", "Copy 2 <*****@*****.**>"], bcc=["*****@*****.**", "Blind Copy 2 <*****@*****.**>"], reply_to=["*****@*****.**", "Reply 2 <*****@*****.**>"], headers={"X-Anymail-Test": "value"}, metadata={"meta1": "simple_string", "meta2": 2}, tags=["Re-engagement", "Cohort 12/2017"], ) message.attach("attachment1.txt", "Here is some\ntext for you", "text/plain") message.attach("attachment2.csv", "ID,Name\n1,Amy Lina", "text/csv") cid = message.attach_inline_image_file(sample_image_path()) message.attach_alternative( "<p><b>HTML:</b> with <a href='http://example.com'>link</a>" "and image: <img src='cid:%s'></div>" % cid, "text/html") message.attach_alternative( "Amazon SES SendRawEmail actually supports multiple alternative parts", "text/x-note-for-email-geeks") message.send() self.assertEqual(message.anymail_status.status, {'queued'})
def test_merge_data(self): message = AnymailMessage( subject="Anymail merge_data test: %field%", body="This body includes merge data: %field%", from_email="Test From <*****@*****.**>", to=[ "*****@*****.**", "Recipient 2 <*****@*****.**>" ], reply_to=[ '"Merge data in reply name: %field%" <*****@*****.**>' ], merge_data={ '*****@*****.**': { 'field': 'one' }, '*****@*****.**': { 'field': 'two' }, }, esp_extra={ 'merge_field_format': '%{}%', }, ) message.send() recipient_status = message.anymail_status.recipients self.assertEqual(recipient_status['*****@*****.**'].status, 'queued') self.assertEqual(recipient_status['*****@*****.**'].status, 'queued')
def flag_for_review(self, approval_request): """ Mark this hosting provider as in need of review by admins. Sends an notification via email to admins. """ # notify_admin_via_email(approval_request) provider = approval_request.hostingprovider link_path = reverse( "greenweb_admin:accounts_hostingprovider_change", args=[provider.id] ) link_url = f"{settings.SITE_URL}{link_path}" ctx = { "approval_request": approval_request, "provider": provider, "link_url": link_url, } notification_subject = ( f"TGWF: {approval_request.hostingprovider} - " "has been updated and needs a review" ) notification_email_copy = render_to_string("flag_for_review_text.txt", ctx) notification_email_html = render_to_string("flag_for_review_text.html", ctx) msg = AnymailMessage( subject=notification_subject, body=notification_email_copy, to=["*****@*****.**"], ) msg.attach_alternative(notification_email_html, "text/html") msg.send()
def test_stored_template(self): # Using a template created like this: # boto3.client('ses').create_template(Template={ # "TemplateName": "TestTemplate", # "SubjectPart": "Your order {{order}} shipped", # "HtmlPart": "<h1>Dear {{name}}:</h1><p>Your order {{order}} shipped {{ship_date}}.</p>", # "TextPart": "Dear {{name}}:\r\nYour order {{order}} shipped {{ship_date}}." # }) message = AnymailMessage( template_id='TestTemplate', from_email='"Test From" <*****@*****.**>', to=["First Recipient <*****@*****.**>", "*****@*****.**"], merge_data={ '*****@*****.**': {'order': 12345, 'name': "Test Recipient"}, '*****@*****.**': {'order': 6789}, }, merge_global_data={ 'name': "Customer", # default 'ship_date': "today" }, ) message.send() recipient_status = message.anymail_status.recipients self.assertEqual(recipient_status['*****@*****.**'].status, 'queued') self.assertRegex(recipient_status['*****@*****.**'].message_id, r'[0-9a-f-]+') self.assertEqual(recipient_status['*****@*****.**'].status, 'queued') self.assertRegex(recipient_status['*****@*****.**'].message_id, r'[0-9a-f-]+')
def test_all_options(self): message = AnymailMessage( subject="Anymail Mailjet all-options integration test", body="This is the text body", from_email='"Test Sender, Inc." <*****@*****.**>', to=[ '*****@*****.**', '"Recipient, 2nd" <*****@*****.**>' ], cc=['*****@*****.**', 'Copy 2 <*****@*****.**>'], bcc=[ '*****@*****.**', 'Blind Copy 2 <*****@*****.**>' ], reply_to=['"Reply, To" <*****@*****.**>' ], # Mailjet only supports single reply_to headers={"X-Anymail-Test": "value"}, metadata={ "meta1": "simple string", "meta2": 2 }, tags=["tag 1"], # Mailjet only allows a single tag track_clicks=True, track_opens=True, ) message.attach("attachment1.txt", "Here is some\ntext for you", "text/plain") message.attach("attachment2.csv", "ID,Name\n1,Amy Lina", "text/csv") cid = message.attach_inline_image_file(sample_image_path()) message.attach_alternative( "<p><b>HTML:</b> with <a href='http://example.com'>link</a>" "and image: <img src='cid:%s'></div>" % cid, "text/html") message.send() self.assertEqual(message.anymail_status.status, {'sent'})
def test_merge_data(self): message = AnymailMessage( subject="Anymail merge_data test: {{ value }}", body="This body includes merge data: {{ value }}\n" "And global merge data: {{ global }}", from_email="Test From <*****@*****.**>", to=[ "*****@*****.**", "Recipient 2 <*****@*****.**>" ], merge_data={ '*****@*****.**': { 'value': 'one' }, '*****@*****.**': { 'value': 'two' }, }, merge_global_data={'global': 'global_value'}, ) message.send() recipient_status = message.anymail_status.recipients self.assertEqual( recipient_status['*****@*****.**'].status, 'queued') self.assertEqual( recipient_status['*****@*****.**'].status, 'queued')
def test_all_options(self): send_at = datetime.now().replace(microsecond=0) + timedelta(minutes=2) message = AnymailMessage( subject="Anymail all-options integration test", body="This is the text body", from_email='"Test From, with comma" <*****@*****.**>', to=["*****@*****.**", '"Recipient 2, OK?" <*****@*****.**>'], cc=["*****@*****.**", "Copy 2 <*****@*****.**>"], bcc=["*****@*****.**", "Blind Copy 2 <*****@*****.**>"], reply_to=['"Reply, with comma" <*****@*****.**>'], # v3 only supports single reply-to headers={"X-Anymail-Test": "value", "X-Anymail-Count": 3}, metadata={"meta1": "simple string", "meta2": 2}, send_at=send_at, tags=["tag 1", "tag 2"], track_clicks=True, track_opens=True, # esp_extra={'asm': {'group_id': 1}}, # this breaks activity feed if you don't have an asm group ) message.attach("attachment1.txt", "Here is some\ntext for you", "text/plain") message.attach("attachment2.csv", "ID,Name\n1,Amy Lina", "text/csv") cid = message.attach_inline_image_file(sample_image_path()) message.attach_alternative( "<p><b>HTML:</b> with <a href='http://example.com'>link</a>" "and image: <img src='cid:%s'></div>" % cid, "text/html") message.send() self.assertEqual(message.anymail_status.status, {'queued'}) # SendGrid always queues
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 <*****@*****.**>"], tags=["using-template"], headers={"X-Anymail-Test": "group: A, variation: C"}, merge_global_data={ # The Anymail test template includes `{{ params.SHIP_DATE }}` # and `{{ params.ORDER_ID }}` substitutions "SHIP_DATE": "yesterday", "ORDER_ID": "12345", }, metadata={"customer-id": "ZXK9123", "meta2": 2}, ) # Normal attachments don't work with Sendinblue templates: # message.attach("attachment1.txt", "Here is some\ntext for you", "text/plain") # If you can host the attachment content on some publicly-accessible URL, # this *non-portable* alternative allows sending attachments with templates: message.esp_extra = { 'attachment': [{ 'name': 'attachment1.txt', # URL where Sendinblue can download the attachment content while sending: 'url': 'https://raw.githubusercontent.com/anymail/django-anymail/master/AUTHORS.txt', }] } message.send() self.assertEqual(message.anymail_status.status, {'queued'}) # SendinBlue always queues self.assertRegex(message.anymail_status.message_id, r'\<.+@.+\>')
def test_all_options(self): 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 <*****@*****.**>, [email protected]", to=["*****@*****.**", "Recipient 2 <*****@*****.**>"], cc=["*****@*****.**", "Copy 2 <*****@*****.**>"], bcc=["*****@*****.**", "Blind Copy 2 <*****@*****.**>"], reply_to=["*****@*****.**", "Reply 2 <*****@*****.**>"], headers={"X-Anymail-Test": "value"}, # no metadata, send_at, track_clicks support tags=["tag 1"], # max one tag track_opens=True, track_clicks=True, ) message.attach("attachment1.txt", "Here is some\ntext for you", "text/plain") message.attach("attachment2.csv", "ID,Name\n1,Amy Lina", "text/csv") cid = message.attach_inline_image_file(sample_image_path()) message.attach_alternative( "<p><b>HTML:</b> with <a href='http://example.com'>link</a>" "and image: <img src='cid:%s'></div>" % cid, "text/html") message.send() self.assertEqual(message.anymail_status.status, {'sent'})
def test_merge_data(self): message = AnymailMessage( 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=[ "*****@*****.**", "Recipient 2 <*****@*****.**>" ], merge_data={ '*****@*****.**': { 'value': 'one' }, '*****@*****.**': { 'value': 'two' }, }, merge_global_data={'global': 'global_value'}, ) message.send() recipient_status = message.anymail_status.recipients self.assertEqual(recipient_status['*****@*****.**'].status, 'sent') self.assertEqual(recipient_status['*****@*****.**'].status, 'sent')
def test_merge_data(self): self.set_mock_response(raw=self._mock_batch_response) message = AnymailMessage( from_email='*****@*****.**', template_id= 1234567, # Postmark only supports merge_data content in a template to=['*****@*****.**', 'Bob <*****@*****.**>'], merge_data={ '*****@*****.**': { 'name': "Alice", 'group': "Developers" }, '*****@*****.**': { 'name': "Bob" }, # and leave group undefined '*****@*****.**': { 'name': "Not a recipient for this message" }, }, merge_global_data={ 'group': "Users", 'site': "ExampleCo" }) message.send() self.assert_esp_called('/email/batchWithTemplates') data = self.get_api_call_json() messages = data["Messages"] self.assertEqual(len(messages), 2) self.assertEqual( messages[0], { "From": "*****@*****.**", "To": "*****@*****.**", "TemplateId": 1234567, "TemplateModel": { "name": "Alice", "group": "Developers", "site": "ExampleCo" }, }) self.assertEqual( messages[1], { "From": "*****@*****.**", "To": "Bob <*****@*****.**>", "TemplateId": 1234567, "TemplateModel": { "name": "Bob", "group": "Users", "site": "ExampleCo" }, }) recipients = message.anymail_status.recipients self.assertEqual(recipients['*****@*****.**'].status, 'sent') self.assertEqual(recipients['*****@*****.**'].message_id, 'b7bc2f4a-e38e-4336-af7d-e6c392c2f817') self.assertEqual(recipients['*****@*****.**'].status, 'sent') self.assertEqual(recipients['*****@*****.**'].message_id, 'e2ecbbfc-fe12-463d-b933-9fe22915106d')
def email_export_xlsx(*, object_type, user, session, endpoint_path, filters, export_description, attachment_name): if not get_language(): language = getattr(settings, 'LANGUAGE_CODE', 'en') activate(language) if object_type == 'credits': export_message = gettext( 'Attached are the credits you exported from ‘%(service_name)s’.') elif object_type == 'disbursements': export_message = (gettext( 'Attached are the bank transfer and cheque disbursements you exported from ‘%(service_name)s’.' ) + ' ' + gettext('You can’t see cash or postal orders here.')) elif object_type == 'senders': export_message = gettext( 'Attached is a list of payment sources you exported from ‘%(service_name)s’.' ) elif object_type == 'prisoners': export_message = gettext( 'Attached is a list of prisoners you exported from ‘%(service_name)s’.' ) else: raise NotImplementedError(f'Cannot export {object_type}') api_session = get_api_session_with_session(user, session) generated_at = timezone.now() object_list = convert_date_fields( retrieve_all_pages_for_path(api_session, endpoint_path, **filters)) serialiser = ObjectListSerialiser.serialiser_for(object_type) workbook = serialiser.make_workbook(object_list) output = save_virtual_workbook(workbook) attachment_type = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' template_context = prepare_context({ 'export_description': export_description, 'generated_at': generated_at, 'export_message': export_message % { 'service_name': gettext('Prisoner money intelligence') } }) subject = '%s - %s' % (gettext('Prisoner money intelligence'), gettext('Exported data')) text_body = template_loader.get_template( 'security/email/export.txt').render(template_context) html_body = template_loader.get_template( 'security/email/export.html').render(template_context) email = AnymailMessage( subject=subject, body=text_body.strip('\n'), from_email=default_from_address(), to=[user.email], tags=['export'], ) email.attach_alternative(html_body, mimetype='text/html') email.attach(attachment_name, output, mimetype=attachment_type) email.send()
def send_reset_password(email, token): message = AnymailMessage(to=[email], ) # Anymail extra attributes: message.template_id = 1 # use this Sendinblue template message.from_email = None # to use the template's default sender message.merge_global_data = { 'token': str(token), } message.send()
def test_send_anymail_message_without_template(self): # Make sure SendRawEmail is used for non-template_id messages message = AnymailMessage(from_email="*****@*****.**", to=["*****@*****.**"], subject="subject") message.send() self.assert_esp_not_called(operation_name="send_bulk_templated_email") self.get_send_params(operation_name="send_raw_email" ) # fails if send_raw_email not called
def test_all_options(self): send_at = datetime.now().replace(microsecond=0) + timedelta(minutes=2) send_at_timestamp = mktime(send_at.timetuple()) # python3: send_at.timestamp() message = AnymailMessage( subject="Anymail all-options integration test", body="This is the text body", from_email="Test From <*****@*****.**>", to=["*****@*****.**", "Recipient 2 <*****@*****.**>"], cc=["*****@*****.**", "Copy 2 <*****@*****.**>"], bcc=["*****@*****.**", "Blind Copy 2 <*****@*****.**>"], reply_to=["*****@*****.**", "Reply 2 <*****@*****.**>"], headers={"X-Anymail-Test": "value"}, metadata={"meta1": "simple string", "meta2": 2}, send_at=send_at, tags=["tag 1", "tag 2"], track_clicks=False, track_opens=True, ) message.attach("attachment1.txt", "Here is some\ntext for you", "text/plain") message.attach("attachment2.csv", "ID,Name\n1,3", "text/csv") cid = message.attach_inline_image_file(sample_image_path(), domain=MAILGUN_TEST_DOMAIN) message.attach_alternative( "<div>This is the <i>html</i> body <img src='cid:%s'></div>" % cid, "text/html") message.send() self.assertEqual(message.anymail_status.status, {'queued'}) # Mailgun always queues message_id = message.anymail_status.message_id events = self.fetch_mailgun_events(message_id, event="accepted") if events is None: self.skipTest("No Mailgun 'accepted' event after 30sec -- can't complete this test") return event = events.pop() self.assertCountEqual(event["tags"], ["tag 1", "tag 2"]) # don't care about order self.assertEqual(event["user-variables"], {"meta1": "simple string", "meta2": "2"}) # all metadata values become strings self.assertEqual(event["message"]["scheduled-for"], send_at_timestamp) self.assertCountEqual(event["message"]["recipients"], ['*****@*****.**', '*****@*****.**', '*****@*****.**', '*****@*****.**', '*****@*****.**', '*****@*****.**']) # don't care about order headers = event["message"]["headers"] self.assertEqual(headers["from"], "Test From <*****@*****.**>") self.assertEqual(headers["to"], "[email protected], Recipient 2 <*****@*****.**>") self.assertEqual(headers["subject"], "Anymail all-options integration test") attachments = event["message"]["attachments"] self.assertEqual(len(attachments), 2) # because inline image shouldn't be an attachment self.assertEqual(attachments[0]["filename"], "attachment1.txt") self.assertEqual(attachments[0]["content-type"], "text/plain") self.assertEqual(attachments[1]["filename"], "attachment2.csv") self.assertEqual(attachments[1]["content-type"], "text/csv")
def send_email(identifier, to, subject, template_data): logger.info("Sending receipt email id:%s to:%s subject:%s", identifier, to, subject) message = AnymailMessage() message.template_id = identifier message.to = [ to, ] message.subject = subject message.merge_global_data = template_data message.send()
def test_template_alias(self): # Anymail template_id can be either Postmark TemplateId or TemplateAlias message = AnymailMessage( from_email='*****@*****.**', to=['*****@*****.**'], template_id='welcome-message', ) message.send() self.assert_esp_called('/email/withTemplate/') data = self.get_api_call_json() self.assertEqual(data['TemplateAlias'], 'welcome-message')
def test_no_debug_logging(self): # Make sure it doesn't output anything when DEBUG_API_REQUESTS is not set message = AnymailMessage('Subject', 'Text Body', '*****@*****.**', ['*****@*****.**']) message._payload_init = dict( data="Request body", headers={ "Content-Type": "text/plain", "Accept": "application/json", }, ) with self.assertPrints("", match="equal"): message.send()
def test_template(self): message = AnymailMessage( from_email="*****@*****.**", to=["*****@*****.**", "Second Recipient <*****@*****.**>"], template_id=POSTMARK_TEST_TEMPLATE_ID, merge_data={ "*****@*****.**": {"name": "Recipient 1", "order_no": "12345"}, "*****@*****.**": {"order_no": "6789"}, }, merge_global_data={"name": "Valued Customer"}, ) message.send() self.assertEqual(message.anymail_status.status, {'sent'})
def test_merge_data_no_template(self): # merge_data={} can be used to force batch sending without a template self.set_mock_response(raw=json.dumps( [{ "ErrorCode": 0, "Message": "OK", "To": "*****@*****.**", "SubmittedAt": "2016-03-12T15:27:50.4468803-05:00", "MessageID": "b7bc2f4a-e38e-4336-af7d-e6c392c2f817", }, { "ErrorCode": 0, "Message": "OK", "To": "*****@*****.**", "SubmittedAt": "2016-03-12T15:27:50.4468803-05:00", "MessageID": "e2ecbbfc-fe12-463d-b933-9fe22915106d", }]).encode('utf-8')) message = AnymailMessage( from_email='*****@*****.**', to=['*****@*****.**', 'Bob <*****@*****.**>'], merge_data={}, subject="Test batch send", body="Test body", ) message.send() self.assert_esp_called('/email/batch') data = self.get_api_call_json() self.assertEqual(len(data), 2) self.assertEqual( data[0], { "From": "*****@*****.**", "To": "*****@*****.**", "Subject": "Test batch send", "TextBody": "Test body", }) self.assertEqual( data[1], { "From": "*****@*****.**", "To": "Bob <*****@*****.**>", "Subject": "Test batch send", "TextBody": "Test body", }) recipients = message.anymail_status.recipients self.assertEqual(recipients['*****@*****.**'].status, 'sent') self.assertEqual(recipients['*****@*****.**'].message_id, 'b7bc2f4a-e38e-4336-af7d-e6c392c2f817') self.assertEqual(recipients['*****@*****.**'].status, 'sent') self.assertEqual(recipients['*****@*****.**'].message_id, 'e2ecbbfc-fe12-463d-b933-9fe22915106d')
def send_email(self, subject, body, tag='Default'): try: validate_email(self.email) except: print('Invalid email: %s' % self.email) return message = AnymailMessage( subject=subject, body=body, to=["%s <%s>" % (self.full_name or self.username, self.email)], tags=[tag], ) message.send()
def password_reset_prelim(request): if request.method == 'POST': _password_reset_prelim_form = PasswordResetPrelimForm(request.POST) if _password_reset_prelim_form.is_valid(): _email = _password_reset_prelim_form.cleaned_data.get('email') _user = get_user_model() if _user.objects.user_exists(_email): _timedSigner = TimestampSigner() _signed_email = _timedSigner.sign(_email) _message = AnymailMessage(to=[_email], tags=['Account']) _message.template_id = 'a4753e4f-f898-4a97-8ca1-a52ef8cc7aeb' _message.merge_global_data = { 'signed_email': _signed_email, } _message.metadata = {'signed-email': _signed_email} _message.track_clicks = True _message.send() return redirect('accounts:password-reset-prelim-success') else: _password_reset_prelim_form.add_error( None, mark_safe( 'This email is not registered with us. Do you want to <a href="' + reverse('accounts:signup-prelim') + '">signup</a> instead?')) return render(request, 'accounts/password_reset_prelim.html', { 'password_reset_prelim_form': _password_reset_prelim_form, }) else: return render( request, 'accounts/password_reset_prelim.html', { 'password_reset_prelim_form': _password_reset_prelim_form, }) else: auth.logout(request) _password_reset_prelim_form = PasswordResetPrelimForm() return render( request, 'accounts/password_reset_prelim.html', { 'password_reset_prelim_form': _password_reset_prelim_form, })
def test_stored_template(self): message = AnymailMessage( from_email="Test From <*****@*****.**>", to=["*****@*****.**"], template_id=SENDGRID_TEST_TEMPLATE_ID, # The test template in the Anymail Test account has a substitution "-field-": merge_global_data={ 'field': 'value from merge_global_data', }, esp_extra={ 'merge_field_format': '-{}-', }, ) message.send() self.assertEqual(message.anymail_status.status, {'queued'})
def test_stored_template(self): message = AnymailMessage( template_id='test-template', # a real template in our SparkPost test account to=["*****@*****.**"], merge_data={ '*****@*****.**': { 'name': "Test Recipient", } }, merge_global_data={ 'order': '12345', }, ) message.send() recipient_status = message.anymail_status.recipients self.assertEqual(recipient_status['*****@*****.**'].status, 'queued')
def test_stored_template(self): message = AnymailMessage( template_id='176375', # ID of the real template named 'test-template' in our Mailjet test account to=["*****@*****.**"], merge_data={ '*****@*****.**': { 'name': "Test Recipient", } }, merge_global_data={ 'order': '12345', }, ) message.from_email = None # use the template's sender email/name message.send() recipient_status = message.anymail_status.recipients self.assertEqual(recipient_status['*****@*****.**'].status, 'sent')
def test_merge_data(self): message = AnymailMessage( subject="Anymail merge_data test: %value%", body="This body includes merge data: %value%", from_email="Test From <*****@*****.**>", to=["*****@*****.**", "Recipient 2 <*****@*****.**>"], merge_data={ '*****@*****.**': {'value': 'one'}, '*****@*****.**': {'value': 'two'}, }, esp_extra={ 'merge_field_format': '%{}%', }, ) message.send() recipient_status = message.anymail_status.recipients self.assertEqual(recipient_status['*****@*****.**'].status, 'queued') self.assertEqual(recipient_status['*****@*****.**'].status, 'queued')
def test_merge_data(self): message = AnymailMessage( subject="Anymail 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=["*****@*****.**", "Recipient 2 <*****@*****.**>"], merge_data={ '*****@*****.**': {'value': 'one'}, '*****@*****.**': {'value': 'two'}, }, merge_global_data={ 'global': 'global_value' }, ) message.send() recipient_status = message.anymail_status.recipients self.assertEqual(recipient_status['*****@*****.**'].status, 'sent') self.assertEqual(recipient_status['*****@*****.**'].status, 'sent')
def test_merge_data(self): message = AnymailMessage( subject="Anymail merge_data test: {{ value }}", body="This body includes merge data: {{ value }}\n" "And global merge data: {{ global }}", from_email="Test From <*****@*****.**>", to=["*****@*****.**", "Recipient 2 <*****@*****.**>"], merge_data={ '*****@*****.**': {'value': 'one'}, '*****@*****.**': {'value': 'two'}, }, merge_global_data={ 'global': 'global_value' }, ) message.send() recipient_status = message.anymail_status.recipients self.assertEqual(recipient_status['*****@*****.**'].status, 'queued') self.assertEqual(recipient_status['*****@*****.**'].status, 'queued')
class SendGridBackendIntegrationTests(SimpleTestCase, AnymailTestMixin): """SendGrid API integration tests These tests run against the **live** SendGrid API, using the environment variable `SENDGRID_TEST_API_KEY` as the API key If those variables are not set, these tests won't run. SendGrid doesn't offer a test mode -- it tries to send everything you ask. To avoid stacking up a pile of undeliverable @example.com emails, the tests use SendGrid's "sink domain" @sink.sendgrid.net. https://support.sendgrid.com/hc/en-us/articles/201995663-Safely-Test-Your-Sending-Speed """ def setUp(self): super(SendGridBackendIntegrationTests, self).setUp() self.message = AnymailMessage('Anymail SendGrid integration test', 'Text content', '*****@*****.**', ['*****@*****.**']) self.message.attach_alternative('<p>HTML content</p>', "text/html") def test_simple_send(self): # Example of getting the SendGrid send status and message id from the message sent_count = self.message.send() self.assertEqual(sent_count, 1) anymail_status = self.message.anymail_status sent_status = anymail_status.recipients['*****@*****.**'].status message_id = anymail_status.recipients['*****@*****.**'].message_id self.assertEqual(sent_status, 'queued') # SendGrid always queues self.assertRegex(message_id, r'\<.+@example\.com\>') # should use from_email's domain self.assertEqual(anymail_status.status, {sent_status}) # set of all recipient statuses self.assertEqual(anymail_status.message_id, message_id) def test_all_options(self): send_at = datetime.now().replace(microsecond=0) + timedelta(minutes=2) message = AnymailMessage( subject="Anymail all-options integration test FILES", body="This is the text body", from_email="Test From <*****@*****.**>", to=["*****@*****.**", "Recipient 2 <*****@*****.**>"], cc=["*****@*****.**", "Copy 2 <*****@*****.**>"], bcc=["*****@*****.**", "Blind Copy 2 <*****@*****.**>"], reply_to=["*****@*****.**", "Reply 2 <*****@*****.**>"], headers={"X-Anymail-Test": "value"}, metadata={"meta1": "simple string", "meta2": 2}, send_at=send_at, tags=["tag 1", "tag 2"], track_clicks=True, track_opens=True, ) message.attach("attachment1.txt", "Here is some\ntext for you", "text/plain") message.attach("attachment2.csv", "ID,Name\n1,Amy Lina", "text/csv") cid = message.attach_inline_image_file(sample_image_path()) message.attach_alternative( "<p><b>HTML:</b> with <a href='http://example.com'>link</a>" "and image: <img src='cid:%s'></div>" % cid, "text/html") message.send() self.assertEqual(message.anymail_status.status, {'queued'}) # SendGrid always queues def test_merge_data(self): message = AnymailMessage( subject="Anymail merge_data test: %value%", body="This body includes merge data: %value%", from_email="Test From <*****@*****.**>", to=["*****@*****.**", "Recipient 2 <*****@*****.**>"], merge_data={ '*****@*****.**': {'value': 'one'}, '*****@*****.**': {'value': 'two'}, }, esp_extra={ 'merge_field_format': '%{}%', }, ) message.send() recipient_status = message.anymail_status.recipients self.assertEqual(recipient_status['*****@*****.**'].status, 'queued') self.assertEqual(recipient_status['*****@*****.**'].status, 'queued') @override_settings(ANYMAIL_SENDGRID_API_KEY="Hey, that's not an API key!") def test_invalid_api_key(self): with self.assertRaises(AnymailAPIError) as cm: self.message.send() err = cm.exception self.assertEqual(err.status_code, 400) # Make sure the exception message includes SendGrid's response: self.assertIn("authorization grant is invalid", str(err))
class SparkPostBackendIntegrationTests(SimpleTestCase, AnymailTestMixin): """SparkPost API integration tests These tests run against the **live** SparkPost API, using the environment variable `SPARKPOST_TEST_API_KEY` as the API key If that variable is not set, these tests won't run. SparkPost doesn't offer a test mode -- it tries to send everything you ask. To avoid stacking up a pile of undeliverable @example.com emails, the tests use SparkPost's "sink domain" @*.sink.sparkpostmail.com. https://support.sparkpost.com/customer/en/portal/articles/2361300-how-to-test-integrations SparkPost also doesn't support arbitrary senders (so no [email protected]). We've set up @test-sp.anymail.info as a validated sending domain for these tests. """ def setUp(self): super(SparkPostBackendIntegrationTests, self).setUp() self.message = AnymailMessage('Anymail SparkPost integration test', 'Text content', '*****@*****.**', ['*****@*****.**']) self.message.attach_alternative('<p>HTML content</p>', "text/html") def test_simple_send(self): # Example of getting the SparkPost send status and transmission id from the message sent_count = self.message.send() self.assertEqual(sent_count, 1) anymail_status = self.message.anymail_status sent_status = anymail_status.recipients['*****@*****.**'].status message_id = anymail_status.recipients['*****@*****.**'].message_id self.assertEqual(sent_status, 'queued') # SparkPost always queues self.assertRegex(message_id, r'.+') # this is actually the transmission_id; should be non-blank self.assertEqual(anymail_status.status, {sent_status}) # set of all recipient statuses self.assertEqual(anymail_status.message_id, message_id) def test_all_options(self): send_at = datetime.now() + timedelta(minutes=2) message = AnymailMessage( subject="Anymail all-options integration test", body="This is the text body", from_email="Test From <*****@*****.**>", to=["*****@*****.**", "Recipient 2 <*****@*****.**>"], cc=["*****@*****.**", "Copy 2 <*****@*****.**>"], bcc=["*****@*****.**", "Blind Copy 2 <*****@*****.**>"], reply_to=["*****@*****.**", "Reply 2 <*****@*****.**>"], headers={"X-Anymail-Test": "value"}, metadata={"meta1": "simple string", "meta2": 2}, send_at=send_at, tags=["tag 1"], # SparkPost only supports single tags track_clicks=True, track_opens=True, ) message.attach("attachment1.txt", "Here is some\ntext for you", "text/plain") message.attach("attachment2.csv", "ID,Name\n1,Amy Lina", "text/csv") cid = message.attach_inline_image_file(sample_image_path()) message.attach_alternative( "<p><b>HTML:</b> with <a href='http://example.com'>link</a>" "and image: <img src='cid:%s'></div>" % cid, "text/html") message.send() self.assertEqual(message.anymail_status.status, {'queued'}) # SparkPost always queues def test_merge_data(self): message = AnymailMessage( subject="Anymail merge_data test: {{ value }}", body="This body includes merge data: {{ value }}\n" "And global merge data: {{ global }}", from_email="Test From <*****@*****.**>", to=["*****@*****.**", "Recipient 2 <*****@*****.**>"], merge_data={ '*****@*****.**': {'value': 'one'}, '*****@*****.**': {'value': 'two'}, }, merge_global_data={ 'global': 'global_value' }, ) message.send() recipient_status = message.anymail_status.recipients self.assertEqual(recipient_status['*****@*****.**'].status, 'queued') self.assertEqual(recipient_status['*****@*****.**'].status, 'queued') def test_stored_template(self): message = AnymailMessage( template_id='test-template', # a real template in our SparkPost test account to=["*****@*****.**"], merge_data={ '*****@*****.**': { 'name': "Test Recipient", } }, merge_global_data={ 'order': '12345', }, ) message.send() recipient_status = message.anymail_status.recipients self.assertEqual(recipient_status['*****@*****.**'].status, 'queued') @override_settings(ANYMAIL_SPARKPOST_API_KEY="Hey, that's not an API key!") def test_invalid_api_key(self): with self.assertRaises(AnymailAPIError) as cm: self.message.send() err = cm.exception self.assertEqual(err.status_code, 403) # Make sure the exception message includes SparkPost's response: self.assertIn("Forbidden", str(err))
class PostmarkBackendIntegrationTests(SimpleTestCase, AnymailTestMixin): """Postmark API integration tests These tests run against the **live** Postmark API, but using a test key that's not capable of sending actual email. """ def setUp(self): super(PostmarkBackendIntegrationTests, self).setUp() self.message = AnymailMessage('Anymail Postmark integration test', 'Text content', '*****@*****.**', ['*****@*****.**']) self.message.attach_alternative('<p>HTML content</p>', "text/html") def test_simple_send(self): # Example of getting the SendGrid send status and message id from the message sent_count = self.message.send() self.assertEqual(sent_count, 1) anymail_status = self.message.anymail_status sent_status = anymail_status.recipients['*****@*****.**'].status message_id = anymail_status.recipients['*****@*****.**'].message_id self.assertEqual(sent_status, 'sent') self.assertGreater(len(message_id), 0) # non-empty string self.assertEqual(anymail_status.status, {sent_status}) # set of all recipient statuses self.assertEqual(anymail_status.message_id, message_id) def test_all_options(self): message = AnymailMessage( subject="Anymail all-options integration test", body="This is the text body", from_email="Test From <*****@*****.**>", to=["*****@*****.**", "Recipient 2 <*****@*****.**>"], cc=["*****@*****.**", "Copy 2 <*****@*****.**>"], bcc=["*****@*****.**", "Blind Copy 2 <*****@*****.**>"], reply_to=["*****@*****.**", "Reply 2 <*****@*****.**>"], headers={"X-Anymail-Test": "value"}, # no metadata, send_at, track_clicks support tags=["tag 1"], # max one tag track_opens=True, ) message.attach("attachment1.txt", "Here is some\ntext for you", "text/plain") message.attach("attachment2.csv", "ID,Name\n1,Amy Lina", "text/csv") cid = message.attach_inline_image_file(sample_image_path()) message.attach_alternative( "<p><b>HTML:</b> with <a href='http://example.com'>link</a>" "and image: <img src='cid:%s'></div>" % cid, "text/html") message.send() self.assertEqual(message.anymail_status.status, {'sent'}) def test_invalid_from(self): self.message.from_email = 'webmaster@localhost' # Django's default From with self.assertRaises(AnymailAPIError) as cm: self.message.send() err = cm.exception self.assertEqual(err.status_code, 422) self.assertIn("Invalid 'From' address", str(err)) @override_settings(ANYMAIL_POSTMARK_SERVER_TOKEN="Hey, that's not a server token!") def test_invalid_server_token(self): with self.assertRaises(AnymailAPIError) as cm: self.message.send() err = cm.exception self.assertEqual(err.status_code, 401) # Make sure the exception message includes Postmark's response: self.assertIn("Bad or missing Server API token", str(err))
class MailgunBackendIntegrationTests(SimpleTestCase, AnymailTestMixin): """Mailgun API integration tests These tests run against the **live** Mailgun API, using the environment variable `MAILGUN_TEST_API_KEY` as the API key and `MAILGUN_TEST_DOMAIN` as the sender domain. If those variables are not set, these tests won't run. """ def setUp(self): super(MailgunBackendIntegrationTests, self).setUp() self.message = AnymailMessage('Anymail integration test', 'Text content', '*****@*****.**', ['*****@*****.**']) self.message.attach_alternative('<p>HTML content</p>', "text/html") def fetch_mailgun_events(self, message_id, event=None, initial_delay=2, retry_delay=2, max_retries=5): """Return list of Mailgun events related to message_id""" url = "https://api.mailgun.net/v3/%s/events" % MAILGUN_TEST_DOMAIN auth = ("api", MAILGUN_TEST_API_KEY) # Despite the docs, Mailgun's events API actually expects the message-id # without the <...> brackets (so, not exactly "as returned by the messages API") # https://documentation.mailgun.com/api-events.html#filter-field params = {'message-id': message_id[1:-1]} # strip <...> if event is not None: params['event'] = event # It can take a few seconds for the events to show up # in Mailgun's logs, so retry a few times if necessary: sleep(initial_delay) response = None for retry in range(max_retries): if retry > 0: sleep(retry_delay) response = requests.get(url, auth=auth, params=params) if 200 == response.status_code: items = response.json()["items"] if len(items) > 0: return items # else no matching events found yet, so try again after delay elif 500 <= response.status_code < 600: # server error (hopefully transient); try again after delay pass elif 403 == response.status_code: # "forbidden": this may be related to API throttling; try again after delay pass else: response.raise_for_status() # Max retries exceeded: if response is not None and 200 != response.status_code: logging.warning("Ignoring Mailgun events API error %d:\n%s" % (response.status_code, response.text)) return None def test_simple_send(self): # Example of getting the Mailgun send status and message id from the message sent_count = self.message.send() self.assertEqual(sent_count, 1) anymail_status = self.message.anymail_status sent_status = anymail_status.recipients['*****@*****.**'].status message_id = anymail_status.recipients['*****@*****.**'].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 self.assertEqual(anymail_status.status, {sent_status}) # set of all recipient statuses self.assertEqual(anymail_status.message_id, message_id) def test_all_options(self): send_at = datetime.now().replace(microsecond=0) + timedelta(minutes=2) send_at_timestamp = mktime(send_at.timetuple()) # python3: send_at.timestamp() message = AnymailMessage( subject="Anymail all-options integration test", body="This is the text body", from_email="Test From <*****@*****.**>", to=["*****@*****.**", "Recipient 2 <*****@*****.**>"], cc=["*****@*****.**", "Copy 2 <*****@*****.**>"], bcc=["*****@*****.**", "Blind Copy 2 <*****@*****.**>"], reply_to=["*****@*****.**", "Reply 2 <*****@*****.**>"], headers={"X-Anymail-Test": "value"}, metadata={"meta1": "simple string", "meta2": 2}, send_at=send_at, tags=["tag 1", "tag 2"], track_clicks=False, track_opens=True, ) message.attach("attachment1.txt", "Here is some\ntext for you", "text/plain") message.attach("attachment2.csv", "ID,Name\n1,3", "text/csv") cid = message.attach_inline_image_file(sample_image_path(), domain=MAILGUN_TEST_DOMAIN) message.attach_alternative( "<div>This is the <i>html</i> body <img src='cid:%s'></div>" % cid, "text/html") message.send() self.assertEqual(message.anymail_status.status, {'queued'}) # Mailgun always queues message_id = message.anymail_status.message_id events = self.fetch_mailgun_events(message_id, event="accepted") if events is None: self.skipTest("No Mailgun 'accepted' event after 30sec -- can't complete this test") return event = events.pop() self.assertCountEqual(event["tags"], ["tag 1", "tag 2"]) # don't care about order self.assertEqual(event["user-variables"], {"meta1": "simple string", "meta2": "2"}) # all metadata values become strings self.assertEqual(event["message"]["scheduled-for"], send_at_timestamp) self.assertCountEqual(event["message"]["recipients"], ['*****@*****.**', '*****@*****.**', '*****@*****.**', '*****@*****.**', '*****@*****.**', '*****@*****.**']) # don't care about order headers = event["message"]["headers"] self.assertEqual(headers["from"], "Test From <*****@*****.**>") self.assertEqual(headers["to"], "[email protected], Recipient 2 <*****@*****.**>") self.assertEqual(headers["subject"], "Anymail all-options integration test") attachments = event["message"]["attachments"] self.assertEqual(len(attachments), 2) # because inline image shouldn't be an attachment self.assertEqual(attachments[0]["filename"], "attachment1.txt") self.assertEqual(attachments[0]["content-type"], "text/plain") self.assertEqual(attachments[1]["filename"], "attachment2.csv") self.assertEqual(attachments[1]["content-type"], "text/csv") # No other fields are verifiable from the event data. # (We could try fetching the message from event["storage"]["url"] # to verify content and other headers.) def test_invalid_from(self): self.message.from_email = 'webmaster' with self.assertRaises(AnymailAPIError) as cm: self.message.send() err = cm.exception self.assertEqual(err.status_code, 400) self.assertIn("'from' parameter is not a valid address", str(err)) @override_settings(ANYMAIL_MAILGUN_API_KEY="Hey, that's not an API key!") def test_invalid_api_key(self): with self.assertRaises(AnymailAPIError) as cm: self.message.send() err = cm.exception self.assertEqual(err.status_code, 401)
class MailjetBackendIntegrationTests(SimpleTestCase, AnymailTestMixin): """Mailjet API integration tests These tests run against the **live** Mailjet API, using the environment variables `MAILJET_TEST_API_KEY` and `MAILJET_TEST_SECRET_KEY` as the API key and API secret key, respectively. If those variables are not set, these tests won't run. Mailjet doesn't (in v3.0) offer a test/sandbox mode -- it tries to send everything you ask. Mailjet also doesn't support unverified senders (so no [email protected]). We've set up @test-mj.anymail.info as a validated sending domain for these tests. """ def setUp(self): super(MailjetBackendIntegrationTests, self).setUp() self.message = AnymailMessage('Anymail Mailjet integration test', 'Text content', '*****@*****.**', ['*****@*****.**']) self.message.attach_alternative('<p>HTML content</p>', "text/html") def test_simple_send(self): # Example of getting the Mailjet send status and message id from the message sent_count = self.message.send() self.assertEqual(sent_count, 1) anymail_status = self.message.anymail_status sent_status = anymail_status.recipients['*****@*****.**'].status message_id = anymail_status.recipients['*****@*****.**'].message_id self.assertEqual(sent_status, 'sent') self.assertRegex(message_id, r'.+') self.assertEqual(anymail_status.status, {sent_status}) # set of all recipient statuses self.assertEqual(anymail_status.message_id, message_id) def test_all_options(self): message = AnymailMessage( subject="Anymail all-options integration test", body="This is the text body", from_email='"Test Sender, Inc." <*****@*****.**>', to=['*****@*****.**', '"Recipient, 2nd" <*****@*****.**>'], cc=['*****@*****.**', 'Copy 2 <*****@*****.**>'], bcc=['*****@*****.**', 'Blind Copy 2 <*****@*****.**>'], reply_to=['*****@*****.**', '"Reply, 2nd" <*****@*****.**>'], headers={"X-Anymail-Test": "value"}, metadata={"meta1": "simple string", "meta2": 2}, tags=["tag 1"], # Mailjet only allows a single tag track_clicks=True, track_opens=True, ) message.attach("attachment1.txt", "Here is some\ntext for you", "text/plain") message.attach("attachment2.csv", "ID,Name\n1,Amy Lina", "text/csv") cid = message.attach_inline_image_file(sample_image_path()) message.attach_alternative( "<p><b>HTML:</b> with <a href='http://example.com'>link</a>" "and image: <img src='cid:%s'></div>" % cid, "text/html") message.send() self.assertEqual(message.anymail_status.status, {'sent'}) def test_merge_data(self): message = AnymailMessage( subject="Anymail 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=["*****@*****.**", "Recipient 2 <*****@*****.**>"], merge_data={ '*****@*****.**': {'value': 'one'}, '*****@*****.**': {'value': 'two'}, }, merge_global_data={ 'global': 'global_value' }, ) message.send() recipient_status = message.anymail_status.recipients self.assertEqual(recipient_status['*****@*****.**'].status, 'sent') self.assertEqual(recipient_status['*****@*****.**'].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=["*****@*****.**"], merge_data={ '*****@*****.**': { 'name': "Test Recipient", } }, merge_global_data={ 'order': '12345', }, ) message.from_email = None # use the template's sender email/name message.send() recipient_status = message.anymail_status.recipients self.assertEqual(recipient_status['*****@*****.**'].status, 'sent') @override_settings(ANYMAIL_MAILJET_API_KEY="Hey, that's not an API key!") def test_invalid_api_key(self): with self.assertRaises(AnymailAPIError) as cm: self.message.send() err = cm.exception self.assertEqual(err.status_code, 401) self.assertIn("Invalid Mailjet API key or secret", str(err))
class SendGridBackendIntegrationTests(SimpleTestCase, AnymailTestMixin): """SendGrid v3 API integration tests These tests run against the **live** SendGrid API, using the environment variable `SENDGRID_TEST_API_KEY` as the API key If those variables are not set, these tests won't run. The SEND_DEFAULTS above force SendGrid's v3 sandbox mode, which avoids sending mail. (Sandbox sends also don't show in the activity feed, so disable that for live debugging.) 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(SendGridBackendIntegrationTests, self).setUp() self.message = AnymailMessage('Anymail SendGrid integration test', 'Text content', '*****@*****.**', ['*****@*****.**']) self.message.attach_alternative('<p>HTML content</p>', "text/html") def test_simple_send(self): # Example of getting the SendGrid send status and message id from the message sent_count = self.message.send() self.assertEqual(sent_count, 1) anymail_status = self.message.anymail_status sent_status = anymail_status.recipients['*****@*****.**'].status message_id = anymail_status.recipients['*****@*****.**'].message_id self.assertEqual(sent_status, 'queued') # SendGrid always queues self.assertRegex(message_id, r'\<.+@example\.com\>') # should use from_email's domain self.assertEqual(anymail_status.status, {sent_status}) # set of all recipient statuses self.assertEqual(anymail_status.message_id, message_id) def test_all_options(self): send_at = datetime.now().replace(microsecond=0) + timedelta(minutes=2) message = AnymailMessage( subject="Anymail all-options integration test", body="This is the text body", from_email='"Test From, with comma" <*****@*****.**>', to=["*****@*****.**", '"Recipient 2, OK?" <*****@*****.**>'], cc=["*****@*****.**", "Copy 2 <*****@*****.**>"], bcc=["*****@*****.**", "Blind Copy 2 <*****@*****.**>"], reply_to=['"Reply, with comma" <*****@*****.**>'], # v3 only supports single reply-to headers={"X-Anymail-Test": "value", "X-Anymail-Count": 3}, metadata={"meta1": "simple string", "meta2": 2}, send_at=send_at, tags=["tag 1", "tag 2"], track_clicks=True, track_opens=True, # esp_extra={'asm': {'group_id': 1}}, # this breaks activity feed if you don't have an asm group ) message.attach("attachment1.txt", "Here is some\ntext for you", "text/plain") message.attach("attachment2.csv", "ID,Name\n1,Amy Lina", "text/csv") cid = message.attach_inline_image_file(sample_image_path()) message.attach_alternative( "<p><b>HTML:</b> with <a href='http://example.com'>link</a>" "and image: <img src='cid:%s'></div>" % cid, "text/html") message.send() self.assertEqual(message.anymail_status.status, {'queued'}) # SendGrid always queues def test_merge_data(self): message = AnymailMessage( subject="Anymail merge_data test: %field%", body="This body includes merge data: %field%", from_email="Test From <*****@*****.**>", to=["*****@*****.**", "Recipient 2 <*****@*****.**>"], reply_to=['"Merge data in reply name: %field%" <*****@*****.**>'], merge_data={ '*****@*****.**': {'field': 'one'}, '*****@*****.**': {'field': 'two'}, }, esp_extra={ 'merge_field_format': '%{}%', }, ) message.send() recipient_status = message.anymail_status.recipients self.assertEqual(recipient_status['*****@*****.**'].status, 'queued') self.assertEqual(recipient_status['*****@*****.**'].status, 'queued') @unittest.skipUnless(SENDGRID_TEST_TEMPLATE_ID, "Set the SENDGRID_TEST_TEMPLATE_ID environment variable " "to a template in your SendGrid account to test stored templates") def test_stored_template(self): message = AnymailMessage( from_email="Test From <*****@*****.**>", to=["*****@*****.**"], template_id=SENDGRID_TEST_TEMPLATE_ID, # The test template in the Anymail Test account has a substitution "-field-": merge_global_data={ 'field': 'value from merge_global_data', }, esp_extra={ 'merge_field_format': '-{}-', }, ) message.send() self.assertEqual(message.anymail_status.status, {'queued'}) @override_settings(ANYMAIL_SENDGRID_API_KEY="Hey, that's not an API key!") def test_invalid_api_key(self): with self.assertRaises(AnymailAPIError) as cm: self.message.send() err = cm.exception self.assertEqual(err.status_code, 401) # Make sure the exception message includes SendGrid's response: self.assertIn("authorization grant is invalid", str(err))