From 3f92f59e6f55a16ab23350c7576a071bf7f699bc Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Tue, 29 Aug 2023 17:43:57 +0100 Subject: [PATCH] Move the email digests template into Python/Jinja2 Move the email digests template from Mailchimp into Python/Jinja2. This involves: 1. Creating a new Jinja2 template for the email at `lms/templates/email/instructor_email_digest/` based on the template that we previously had in Mailchimp. This is basically exactly the same as the template that we have in Mailchimp except that I fixed the formatting/indentation and I replaced Mailchimp's template tags with Jinja2 ones. 2. Adding a new `MailchimpService.send()` method (instead of the existing `MailchimpService.send_template()`) that uses Jinja2 to render the new template and then calls the Mailchimp API passing the rendered text to send the email. 3. Adding a new Celery task `send()` (instead of the existing `send_template()` task) that calls `MailchimpService.send()` instead of `send_template()`. 4. Changing `DigestService` to call the new `send()` task instead of the old `send_template()` task when sending digest emails. I also added one image file that was previously hosted on Mailchimp into our app's static files. The new email template references this file. The original `MailchimpService.send_template()` method and `send_template()` Celery task are still present: these will be needed in case there are any `send_template` messages remaining on the queue. A follow-up commit will remove these methods and their tests once the queue has been drained. --- lms/services/digest.py | 6 +- lms/services/mailchimp.py | 64 ++ lms/static/images/email_header.png | Bin 0 -> 55531 bytes lms/tasks/mailchimp.py | 20 + .../instructor_email_digest/body.html.jinja2 | 759 ++++++++++++++++++ .../instructor_email_digest/subject.jinja2 | 1 + tests/unit/lms/services/digest_test.py | 29 +- tests/unit/lms/services/mailchimp_test.py | 108 +++ tests/unit/lms/tasks/mailchimp_test.py | 23 +- 9 files changed, 991 insertions(+), 19 deletions(-) create mode 100644 lms/static/images/email_header.png create mode 100644 lms/templates/email/instructor_email_digest/body.html.jinja2 create mode 100644 lms/templates/email/instructor_email_digest/subject.jinja2 diff --git a/lms/services/digest.py b/lms/services/digest.py index caf4db6eac..463f86e4bb 100644 --- a/lms/services/digest.py +++ b/lms/services/digest.py @@ -19,7 +19,7 @@ from lms.services.email_unsubscribe import EmailUnsubscribeService from lms.services.h_api import HAPI from lms.services.mailchimp import EmailRecipient, EmailSender -from lms.tasks.mailchimp import send_template +from lms.tasks.mailchimp import send LOG = logging.getLogger(__name__) @@ -75,9 +75,9 @@ def send_instructor_email_digests( # pylint:disable=too-many-arguments else: task_done_key = None - send_template.delay( + send.delay( task_done_key=task_done_key, - template_name="instructor-email-digest", + template="lms:templates/email/instructor_email_digest/", sender=asdict(self._sender), recipient=asdict(EmailRecipient(to_email, unified_user.display_name)), template_vars=digest, diff --git a/lms/services/mailchimp.py b/lms/services/mailchimp.py index 2a01b586f8..2568401df4 100644 --- a/lms/services/mailchimp.py +++ b/lms/services/mailchimp.py @@ -1,8 +1,10 @@ import logging from dataclasses import dataclass +from pathlib import Path from typing import Optional import mailchimp_transactional +from pyramid.renderers import render from sqlalchemy import select from lms.models import TaskDone @@ -40,6 +42,68 @@ def __init__(self, db, api_key): self.db = db self.mailchimp_client = mailchimp_transactional.Client(api_key) + def send( # pylint:disable=too-many-arguments + self, + template: str, + sender: EmailSender, + recipient: EmailRecipient, + template_vars: dict, + unsubscribe_url: Optional[str] = None, + task_done_key: Optional[str] = None, + ): + """ + Send an email using Mailchimp Transactional's API. + + https://mailchimp.com/developer/transactional/api/messages/send-new-message/ + """ + + if task_done_key: + if self.db.execute( + select(TaskDone).filter_by(key=task_done_key) + ).one_or_none(): + LOG.info("Not sending duplicate email %s", task_done_key) + return + + headers = {} + + if unsubscribe_url: + template_vars["unsubscribe_url"] = unsubscribe_url + headers["List-Unsubscribe"] = unsubscribe_url + + subject = render(str(template / Path("subject.jinja2")), template_vars) + + params = { + "message": { + "subject": subject, + "html": "...", # For logging only, will be replaced below. + "subaccount": sender.subaccount, + "from_email": sender.email, + "from_name": sender.name, + "to": [{"email": recipient.email, "name": recipient.name}], + "track_opens": True, + "track_clicks": True, + "auto_text": True, + "headers": headers, + }, + "async": True, + } + + LOG.info("mailchimp_client.send(%r)", params) + + # The HTML body might be long so add it to the params *after* logging. + params["message"]["html"] = render( + str(template / Path("body.html.jinja2")), template_vars + ) + + try: + self.mailchimp_client.messages.send(params) + except Exception as exc: + raise MailchimpError() from exc + + if task_done_key: + # Record the email send in the DB to avoid sending duplicates. + self.db.add(TaskDone(key=task_done_key)) + def send_template( # pylint:disable=too-many-arguments self, template_name: str, diff --git a/lms/static/images/email_header.png b/lms/static/images/email_header.png new file mode 100644 index 0000000000000000000000000000000000000000..43560202810ef67f2f3958a65601e7cdc8dc3b3f GIT binary patch literal 55531 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 E0XhX>fdBvi literal 0 HcmV?d00001 diff --git a/lms/tasks/mailchimp.py b/lms/tasks/mailchimp.py index b0ce544098..4ced32c042 100644 --- a/lms/tasks/mailchimp.py +++ b/lms/tasks/mailchimp.py @@ -4,6 +4,26 @@ from lms.tasks.celery import app +@app.task( + acks_late=True, + autoretry_for=(Exception,), + max_retries=2, + retry_backoff=3600, + retry_backoff_max=7200, +) +def send(*, sender, recipient, **kwargs) -> None: + """Send an email using Mailchimp's API.""" + + sender = EmailSender(**sender) + recipient = EmailRecipient(**recipient) + + with app.request_context() as request: # pylint:disable=no-member + mailchimp_service = request.find_service(name="mailchimp") + print(request.environ) + with request.tm: + mailchimp_service.send(sender=sender, recipient=recipient, **kwargs) + + @app.task( acks_late=True, autoretry_for=(Exception,), diff --git a/lms/templates/email/instructor_email_digest/body.html.jinja2 b/lms/templates/email/instructor_email_digest/body.html.jinja2 new file mode 100644 index 0000000000..f5d1d26dcf --- /dev/null +++ b/lms/templates/email/instructor_email_digest/body.html.jinja2 @@ -0,0 +1,759 @@ + + + + + + + + + There has been some activity in Hypothesis + + + +
+ + + + +
+ + + + + + + + + + + + + +
+ + + + +
+ + + + +
+
+
+ + + + +
+ + + + +
+ + + + + + +
+ + + + + + +
+ +
+
+ + + + + + +
+ + + + + + +
+ +
+
+
+
+
+ + + + +
+ + + + + + + +
+ + + + +
+ + + + + + +
+ + + + + + + + +
+

There has been some activity in Hypothesis

+
+ + +
+ + + + + + +
+ + + + + + + + +
+ +

Hi,

+ +

+ {% if total_annotations == 1 %} + One of your students made an annotation in an assignment yesterday. + {% else %} + Your students made {{ total_annotations }} annotations in assignments + yesterday. + {% endif %} + Let your students know about the activity to encourage the ongoing + conversation. +

+ + {% for course in courses %} +

+ {{ course.title }}
+ {% if course.num_annotations == 1 %} + A student made an annotation in an assignment. + {% else %} + Students made {{ course.num_annotations }} annotations in + assignments. + {% endif %} +

+ {% endfor %} + +

We'd love your feedback so we can continue to make improvements + to our product. Take our survey + to tell us what you think of these new daily emails for + instructors.

+ +
+ + +
+ + + + + + +
+ + + + + + +
+ Share Feedback +
+
+
+
+ +
+
+
+ + + + +
+ + + + +
+ + + + + + +
+ + + + + + + + +
+ Hypothesis, 2261 Market Street, #632, San Francisco, California 94114, United States of America
+
+
+ Unsubscribe from this list. +
+ + +
+
+
+
+
+
+ + diff --git a/lms/templates/email/instructor_email_digest/subject.jinja2 b/lms/templates/email/instructor_email_digest/subject.jinja2 new file mode 100644 index 0000000000..580b4b5079 --- /dev/null +++ b/lms/templates/email/instructor_email_digest/subject.jinja2 @@ -0,0 +1 @@ +There has been some activity in Hypothesis diff --git a/tests/unit/lms/services/digest_test.py b/tests/unit/lms/services/digest_test.py index 896d5d3f59..fd5122f67b 100644 --- a/tests/unit/lms/services/digest_test.py +++ b/tests/unit/lms/services/digest_test.py @@ -27,7 +27,7 @@ def test_send_instructor_email_digests( context, DigestContext, db_session, - send_template, + send, sender, email_unsubscribe_service, ): @@ -50,10 +50,10 @@ def test_send_instructor_email_digests( assert context.instructor_digest.call_args_list == [ call(user.h_userid) for user in context.unified_users ] - assert send_template.delay.call_args_list == [ + assert send.delay.call_args_list == [ call( task_done_key=f"instructor_email_digest::{unified_user.h_userid}::2023-04-30", - template_name="instructor-email-digest", + template="lms:templates/email/instructor_email_digest/", sender=asdict(sender), recipient=asdict( EmailRecipient(unified_user.email, unified_user.display_name) @@ -65,7 +65,7 @@ def test_send_instructor_email_digests( ] def test_send_instructor_email_digests_without_deduplication( - self, svc, context, send_template + self, svc, context, send ): context.unified_users = [UnifiedUserFactory()] context.instructor_digest.side_effect = [{"total_annotations": 1}] @@ -78,13 +78,13 @@ def test_send_instructor_email_digests_without_deduplication( ) # If deduplicate=True is passed to DigestService then it passes - # task_done_key=None to send_template() which disables deduplication of + # task_done_key=None to send() which disables deduplication of # sent emails. This is used by admin pages that actually want to allow # you to send duplicate emails if requested. - assert not send_template.delay.call_args.kwargs["task_done_key"] + assert not send.delay.call_args.kwargs["task_done_key"] def test_send_instructor_email_digests_doesnt_send_empty_digests( - self, svc, context, send_template + self, svc, context, send ): context.unified_users = UnifiedUserFactory.create_batch(2) context.instructor_digest.return_value = {"total_annotations": 0} @@ -93,10 +93,10 @@ def test_send_instructor_email_digests_doesnt_send_empty_digests( [sentinel.h_userid], sentinel.updated_after, sentinel.updated_before ) - send_template.delay.assert_not_called() + send.delay.assert_not_called() def test_send_instructor_email_digests_ignores_instructors_with_no_email_address( - self, svc, context, send_template + self, svc, context, send ): context.unified_users = [UnifiedUserFactory(email=None)] context.instructor_digest.return_value = {"total_annotations": 1} @@ -105,11 +105,11 @@ def test_send_instructor_email_digests_ignores_instructors_with_no_email_address sentinel.audience, sentinel.updated_after, sentinel.updated_before ) - send_template.delay.assert_not_called() + send.delay.assert_not_called() @pytest.mark.usefixtures("email_unsubscribe_service") def test_send_instructor_email_digests_uses_override_to_email( - self, svc, context, send_template + self, svc, context, send ): context.unified_users = [UnifiedUserFactory()] context.instructor_digest.return_value = {"total_annotations": 1} @@ -122,8 +122,7 @@ def test_send_instructor_email_digests_uses_override_to_email( ) assert ( - send_template.delay.call_args[1]["recipient"]["email"] - == sentinel.override_to_email + send.delay.call_args[1]["recipient"]["email"] == sentinel.override_to_email ) @pytest.fixture(autouse=True) @@ -663,5 +662,5 @@ def make_learner(user, course): @pytest.fixture(autouse=True) -def send_template(patch): - return patch("lms.services.digest.send_template") +def send(patch): + return patch("lms.services.digest.send") diff --git a/tests/unit/lms/services/mailchimp_test.py b/tests/unit/lms/services/mailchimp_test.py index a06d89aebc..997abede8a 100644 --- a/tests/unit/lms/services/mailchimp_test.py +++ b/tests/unit/lms/services/mailchimp_test.py @@ -13,6 +13,114 @@ ) +class TestSend: + def test_it( + self, svc, mailchimp_transactional, mailchimp_client, caplog, sender, recipient + ): + caplog.set_level(logging.INFO) + + svc.send( + "lms:templates/email/instructor_email_digest/", + sender, + recipient, + template_vars={"foo": "FOO", "bar": "BAR"}, + ) + + assert caplog.record_tuples == [ + ( + "lms.services.mailchimp", + logging.INFO, + Any.string.matching(r"^mailchimp_client\.send\({'message': {"), + ) + ] + mailchimp_transactional.Client.assert_called_once_with(sentinel.api_key) + mailchimp_client.messages.send.assert_called_once_with( + { + "message": { + "subject": Any.string(), + "html": Any.string(), + "subaccount": sentinel.subaccount_id, + "from_email": sentinel.from_email, + "from_name": sentinel.from_name, + "to": [{"email": sentinel.to_email, "name": sentinel.to_name}], + "track_opens": True, + "track_clicks": True, + "auto_text": True, + "headers": {}, + }, + "async": True, + } + ) + + def test_it_doesnt_send_duplicate_emails( + self, svc, mailchimp_client, sender, recipient + ): + # Try to send the same email twice. + for _ in range(2): + svc.send( + "lms:templates/email/instructor_email_digest/", + sender, + recipient, + template_vars={}, + task_done_key="test_key", + ) + + # It should only have sent the email once. + assert len(mailchimp_client.messages.send.call_args_list) == 1 + + def test_with_unsubscribe_url(self, svc, mailchimp_client, sender, recipient): + svc.send( + "lms:templates/email/instructor_email_digest/", + sender, + recipient, + {}, + sentinel.unsubscribe_url, + ) + + mailchimp_client.messages.send.assert_called_once_with( + Any.dict.containing( + { + "message": Any.dict.containing( + {"headers": {"List-Unsubscribe": sentinel.unsubscribe_url}} + ) + } + ) + ) + + def test_if_mailchimp_client_raises(self, svc, mailchimp_client, sender, recipient): + original_exception = RuntimeError("Mailchimp crashed!") + mailchimp_client.messages.send.side_effect = original_exception + + with pytest.raises(MailchimpError) as exc_info: + svc.send( + "lms:templates/email/instructor_email_digest/", sender, recipient, {} + ) + assert exc_info.value.__cause__ == original_exception + + @pytest.fixture + def mailchimp_client(self, mailchimp_transactional): + return mailchimp_transactional.Client.return_value + + @pytest.fixture + def sender(self): + return EmailSender( + sentinel.subaccount_id, + sentinel.from_email, + sentinel.from_name, + ) + + @pytest.fixture + def recipient(self): + return EmailRecipient( + sentinel.to_email, + sentinel.to_name, + ) + + @pytest.fixture + def svc(self, db_session): + return MailchimpService(db_session, sentinel.api_key) + + class TestSendTemplate: def test_it( self, svc, mailchimp_transactional, mailchimp_client, caplog, sender, recipient diff --git a/tests/unit/lms/tasks/mailchimp_test.py b/tests/unit/lms/tasks/mailchimp_test.py index 38319658a4..0d535dc232 100644 --- a/tests/unit/lms/tasks/mailchimp_test.py +++ b/tests/unit/lms/tasks/mailchimp_test.py @@ -5,7 +5,28 @@ import pytest from lms.services.mailchimp import EmailRecipient, EmailSender -from lms.tasks.mailchimp import send_template +from lms.tasks.mailchimp import send, send_template + + +@pytest.mark.usefixtures("mailchimp_service") +class TestSend: + def test_it(self, mailchimp_service): + sender = EmailSender("subaccount", "sender_email", "sender_name") + recipient = EmailRecipient("recipient_email", "recipient_name") + + send( + sender=asdict(sender), + recipient=asdict(recipient), + template=sentinel.template, + template_vars=sentinel.template_vars, + ) + + mailchimp_service.send.assert_called_once_with( + sender=sender, + recipient=recipient, + template=sentinel.template, + template_vars=sentinel.template_vars, + ) @pytest.mark.usefixtures("mailchimp_service")