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 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 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_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_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_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_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_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_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 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 test_template_unsupported(self): """A lot of options are not compatible with SendBulkTemplatedEmail""" message = AnymailMessage(template_id="welcome_template", to=['*****@*****.**']) message.subject = "nope, can't change template subject" with self.assertRaisesMessage(AnymailUnsupportedFeature, "overriding template subject"): message.send() message.subject = None message.body = "nope, can't change text body" with self.assertRaisesMessage(AnymailUnsupportedFeature, "overriding template body content"): message.send() message.content_subtype = "html" with self.assertRaisesMessage(AnymailUnsupportedFeature, "overriding template body content"): message.send() message.body = None message.attach("attachment.txt", "this is an attachment", "text/plain") with self.assertRaisesMessage(AnymailUnsupportedFeature, "attachments with template"): message.send() message.attachments = [] message.extra_headers = {"X-Custom": "header"} with self.assertRaisesMessage(AnymailUnsupportedFeature, "extra_headers with template"): message.send() message.extra_headers = {} message.metadata = {"meta": "data"} with self.assertRaisesMessage(AnymailUnsupportedFeature, "metadata with template"): message.send() message.metadata = None message.tags = ["tag 1", "tag 2"] with self.assertRaisesMessage(AnymailUnsupportedFeature, "tags with template"): message.send() message.tags = None
def send_csv(prison, date, batches, csv_contents, total, count): prison_name = prison.get('short_name') or prison['name'] some_batch = batches[0] now = timezone.localtime(timezone.now()) csv_name = 'payment_%s_%s.csv' % ( prison['cms_establishment_code'], now.strftime('%Y%m%d_%H%M%S'), ) zipped_csv = io.BytesIO() with zipfile.ZipFile(zipped_csv, 'w') as z: z.writestr(csv_name, csv_contents) template_context = prepare_context({ 'prison_name': prison_name, 'date': date, 'total': total, 'count': count, }) text_body = template_loader.get_template( 'bank_admin/emails/private-estate.txt').render(template_context) html_body = template_loader.get_template( 'bank_admin/emails/private-estate.html').render(template_context) email = AnymailMessage( subject= 'Credits received from "Send money to someone in prison" for %s on %s' % ( prison_name, date.strftime('%d/%m/%Y'), ), body=text_body.strip('\n'), from_email=default_from_address(), to=some_batch['remittance_emails'], tags=['private-csv'], ) email.attach_alternative(html_body, mimetype='text/html') email.attach(csv_name + '.zip', zipped_csv.getvalue(), mimetype='application/zip') email.send() logger.info('Sent private estate batch for %s' % prison_name)
def test_all_options(self): message = AnymailMessage( subject="Anymail SendinBlue 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" <*****@*****.**>'], # SendinBlue API v3 only supports single reply-to headers={"X-Anymail-Test": "value", "X-Anymail-Count": 3}, metadata={"meta1": "simple string", "meta2": 2}, tags=["tag 1", "tag 2"], ) message.attach_alternative('<p>HTML content</p>', "text/html") # SendinBlue requires an HTML body 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'}) # SendinBlue always queues self.assertRegex(message.anymail_status.message_id, r'\<.+@.+\>')
def prepare_email_message(self): """ Returns a django ``EmailMessage`` or ``EmailMultiAlternatives`` object, depending on whether html_message is empty. """ if get_override_recipients(): self.to = get_override_recipients() if self.template is not None: engine = get_template_engine() subject = engine.from_string(self.template.subject).render( self.context) plaintext_message = engine.from_string( self.template.content).render(self.context) multipart_template = engine.from_string(self.template.html_content) html_message = multipart_template.render(self.context) else: subject = smart_str(self.subject) plaintext_message = self.message multipart_template = None html_message = self.html_message connection = connections[self.backend_alias or 'default'] if isinstance(self.headers, dict) or self.expires_at: headers = dict(self.headers or {}) if self.expires_at: headers.update({ 'Expires': self.expires_at.strftime("%a, %-d %b %H:%M:%S %z") }) else: headers = None if html_message: if plaintext_message: msg = AnymailMessage(subject=subject, body=plaintext_message, from_email=self.from_email, to=self.to, bcc=self.bcc, cc=self.cc, headers=headers, connection=connection) msg.attach_alternative(html_message, "text/html") else: msg = AnymailMessage(subject=subject, body=html_message, from_email=self.from_email, to=self.to, bcc=self.bcc, cc=self.cc, headers=headers, connection=connection) msg.content_subtype = 'html' if hasattr(multipart_template, 'attach_related'): multipart_template.attach_related(msg) else: msg = AnymailMessage(subject=subject, body=plaintext_message, from_email=self.from_email, to=self.to, bcc=self.bcc, cc=self.cc, headers=headers, connection=connection) for attachment in self.attachments.all(): if attachment.headers: mime_part = MIMENonMultipart(*attachment.mimetype.split('/')) mime_part.set_payload(attachment.file.read()) for key, val in attachment.headers.items(): try: mime_part.replace_header(key, val) except KeyError: mime_part.add_header(key, val) msg.attach(mime_part) else: msg.attach(attachment.name, attachment.file.read(), mimetype=attachment.mimetype or None) attachment.file.close() self._cached_email_message = msg return msg
def email( subject, template_name, attachments=[], batch_size=500, bcc=None, merge_data={}, merge_global_data={}, mixed_subtype=None, preheader=None, recipients=[], reply_to=None, ): if not (subject and template_name and recipients): raise NameError() if not isinstance(recipients, list): raise TypeError("recipients must be a list") # bcc is set to False by default. # make sure bcc is in a list form when sent over if bcc not in [False, None] and not isinstance(bcc, list): raise TypeError("recipients must be a list") merge_global_data['subject'] = subject merge_global_data['current_year'] = timezone.now().year merge_global_data['company_name'] = settings.SITE_NAME merge_global_data['site_url'] = settings.SITE_URL merge_global_data['preheader'] = preheader body = render_to_string(f"{template_name}.html", merge_global_data) # If we send values that don't exist in the template, # SendGrid divides by zero, doesn't pass go, does not collect $200. merge_field_format = "*|{}|*" final_merge_global_data = {} for key, val in merge_global_data.items(): if merge_field_format.format(key) in body: final_merge_global_data[key] = "" if val is None else str(val) for recipients_batch in batches(recipients, batch_size): msg = AnymailMessage( subject=subject, body=body, from_email=f"We All Code<{settings.DEFAULT_FROM_EMAIL}>", to=recipients_batch, reply_to=reply_to, merge_data=merge_data, merge_global_data=final_merge_global_data, esp_extra={ 'merge_field_format': merge_field_format, 'categories': [template_name], }, ) msg.content_subtype = "html" if mixed_subtype: msg.mixed_subtype = mixed_subtype for attachment in attachments: msg.attach(attachment) try: msg.send() except Exception as e: logger.error(e) logger.error(msg) raise e for recipient in msg.anymail_status.recipients.keys(): send_attempt = msg.anymail_status.recipients[recipient] if send_attempt.status not in ['queued', 'sent']: logger.error(f"user: {recipient}, {timezone.now()}") from coderdojochi.models import CDCUser user = CDCUser.objects.get(email=recipient) user.is_active = False user.admin_notes = f"User '{send_attempt.reject_reason}' when checked on {timezone.now()}" user.save()