Skip to content

Commit

Permalink
Move the email digests template into Python/Jinja2
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
seanh committed Sep 5, 2023
1 parent d86fe25 commit 3f92f59
Show file tree
Hide file tree
Showing 9 changed files with 991 additions and 19 deletions.
6 changes: 3 additions & 3 deletions lms/services/digest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)

Expand Down Expand Up @@ -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,
Expand Down
64 changes: 64 additions & 0 deletions lms/services/mailchimp.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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,
Expand Down
Binary file added lms/static/images/email_header.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions lms/tasks/mailchimp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,),
Expand Down
Loading

0 comments on commit 3f92f59

Please sign in to comment.