Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: welcome emails for xPRO Learners #3017

Merged
merged 10 commits into from
Jul 12, 2024

Conversation

mudassir-hafeez
Copy link
Contributor

@mudassir-hafeez mudassir-hafeez commented Jun 11, 2024

What are the relevant tickets?

#2470

Description (What does it do?)

This PR implements the welcome email feature, which sends a welcome email based on each user's course enrollment.

How can this be tested?

  1. Checkout to this branch and make sure openedx is configured.

  2. Try to enroll any user to any course through frontend, or you can use create enrollment command to enroll a user.

    • docker-compose exec web ./manage.py create_enrollment --user "" --run "<course_run>" --code="test7e91c44ea5a999beasdfadsfadfadfwer"
  3. Check if you receive welcome email along with enrollment email.

  4. Verify the content of the email with welcome letter provided in the the ticket https://github.com/mitodl/hq/issues/2470#issue-1912198810 or you can see the link here: https://docs.google.com/document/d/1gGdXSVT4U34_bIADqGzZvcEyWuulM6VK8VDbqyDF1MY/edit

  5. Now, again try to enroll user with transfer enrollment, and follow steps 3 and 4.

Screenshots (if appropriate):

Note: The logo is missing from the email attachment above because the links point to a local server. However,
you can refer to the screenshot below to review the logo, which should ideally be the same once deployed:

image

Additional Context

@mudassir-hafeez mudassir-hafeez force-pushed the mudassir/Welcome-emails-for-xpro-learners branch from cf98a55 to f26a2e3 Compare June 11, 2024 15:58
@odlbot odlbot temporarily deployed to xpro-ci-pr-3017 June 11, 2024 15:58 Inactive
@odlbot odlbot temporarily deployed to xpro-ci-pr-3017 June 11, 2024 16:00 Inactive
@odlbot odlbot temporarily deployed to xpro-ci-pr-3017 June 11, 2024 17:15 Inactive
@odlbot odlbot temporarily deployed to xpro-ci-pr-3017 June 11, 2024 17:16 Inactive
@mudassir-hafeez mudassir-hafeez force-pushed the mudassir/Welcome-emails-for-xpro-learners branch from d8e766b to 29b61aa Compare June 11, 2024 17:24
@odlbot odlbot temporarily deployed to xpro-ci-pr-3017 June 11, 2024 17:25 Inactive
@mudassir-hafeez mudassir-hafeez marked this pull request as ready for review June 14, 2024 18:36
@Anas12091101 Anas12091101 self-assigned this Jun 21, 2024
Copy link
Contributor

@Anas12091101 Anas12091101 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initial review

Dear
{{ user.name }},
</p>
<p style="margin: 0 0 10px">Welcome to MIT xPRO!</p>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the document provided, there are spaces before this line

Suggested change
<p style="margin: 0 0 10px">Welcome to MIT xPRO!</p>
<p style="margin: 0 70 10px">Welcome to MIT xPRO!</p>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have set the left margin to 50 for this line as it closely matches what is in the document.
image

<p style="margin: 0 0 10px">Welcome to MIT xPRO!</p>
<p style="margin: 0 0 10px">
We're thrilled to have you on board for our upcoming course,
<strong>{{ enrollment.run }}</strong>. This is a significant step
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any specific reason, you used run instead of course?

Suggested change
<strong>{{ enrollment.run }}</strong>. This is a significant step
<strong>{{ enrollment.run.course }}</strong>. This is a significant step

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used enrollment.run here and in all other instances you mentioned because it shows the course name along with the course run that makes it more descriptive. We've used a similar approach in other templates, which might be the reason. However, if it only needs to be the course name, we can change it accordingly.

</p>
<ul style="margin: 0 0 10px; padding-left: 20px">
<li>
Course Name: <strong>{{ enrollment.run }}</strong>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Course Name: <strong>{{ enrollment.run }}</strong>
Course Name: <strong>{{ enrollment.run.course }}</strong>

Upon successfully completing the course, you'll receive a digital
Professional Certificate from MIT xPRO, validating your
accomplishment and expertise in
<strong>{{ enrollment.run }}</strong>.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<strong>{{ enrollment.run }}</strong>.
<strong>{{ enrollment.run.course }}</strong>.

<p style="margin: 0 0 10px">Best of luck with your studies,</p>
<p style="margin: 0 0 10px">MIT xPRO Team</p>
<p style="margin: 0 0 10px">
P.S. Don't forget to check out the "Getting Started" section in your
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
P.S. Don't forget to check out the "Getting Started" section in your
<strong>P.S.</strong> Don't forget to check out the "Getting Started" section in your

Copy link
Contributor

@Anas12091101 Anas12091101 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The functionality looks good, just a small change in the code

Comment on lines 205 to 224
run_start_date = run_end_date = ""
run_start_time = ""
if enrollment.run.start_date:
run_start_date = enrollment.run.start_date
run_start_time = run_start_date.astimezone(datetime.timezone.utc).strftime(
EMAIL_TIME_FORMAT
)
if enrollment.run.end_date:
run_end_date = enrollment.run.end_date
if run_start_date and run_end_date:
date_range = (
f"{run_start_date.strftime(EMAIL_DATE_FORMAT)} - "
f"{run_end_date.strftime(EMAIL_DATE_FORMAT)}"
)
else:
date_range = ""
try:
user = enrollment.user
api.send_message(
api.message_for_recipient(
user.email,
api.context_for_user(
user=user,
extra_context={
"enrollment": enrollment,
"run_start_date": run_start_date.strftime(EMAIL_DATE_FORMAT)
if run_start_date
else run_start_date,
"run_start_time": run_start_time,
"run_date_range": date_range,
},
),
EMAIL_WELCOME_COURSE_RUN_ENROLLMENT,
)
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the code can be refactored as:

Suggested change
run_start_date = run_end_date = ""
run_start_time = ""
if enrollment.run.start_date:
run_start_date = enrollment.run.start_date
run_start_time = run_start_date.astimezone(datetime.timezone.utc).strftime(
EMAIL_TIME_FORMAT
)
if enrollment.run.end_date:
run_end_date = enrollment.run.end_date
if run_start_date and run_end_date:
date_range = (
f"{run_start_date.strftime(EMAIL_DATE_FORMAT)} - "
f"{run_end_date.strftime(EMAIL_DATE_FORMAT)}"
)
else:
date_range = ""
try:
user = enrollment.user
api.send_message(
api.message_for_recipient(
user.email,
api.context_for_user(
user=user,
extra_context={
"enrollment": enrollment,
"run_start_date": run_start_date.strftime(EMAIL_DATE_FORMAT)
if run_start_date
else run_start_date,
"run_start_time": run_start_time,
"run_date_range": date_range,
},
),
EMAIL_WELCOME_COURSE_RUN_ENROLLMENT,
)
)
run_start_date = run_end_date = ""
run_start_time = ""
if enrollment.run.start_date:
run_start_date = enrollment.run.start_date.strftime(EMAIL_DATE_FORMAT)
run_start_time = enrollment.run.start_date.astimezone(datetime.timezone.utc).strftime(
EMAIL_TIME_FORMAT
)
if enrollment.run.end_date:
run_end_date = enrollment.run.end_date.strftime(EMAIL_DATE_FORMAT)
date_range = f"{run_start_date} - {run_end_date}" if run_start_date and run_end_date else ""
try:
user = enrollment.user
api.send_message(
api.message_for_recipient(
user.email,
api.context_for_user(
user=user,
extra_context={
"enrollment": enrollment,
"run_start_date": run_start_date,
"run_start_time": run_start_time,
"run_date_range": date_range,
},
),
EMAIL_WELCOME_COURSE_RUN_ENROLLMENT,
)
)

@mudassir-hafeez mudassir-hafeez force-pushed the mudassir/Welcome-emails-for-xpro-learners branch from 075b984 to f790184 Compare July 1, 2024 10:42
@mudassir-hafeez
Copy link
Contributor Author

@arslanashraf7 would you please do an quick review before merging this PR?

@arslanashraf7
Copy link
Contributor

@arslanashraf7 would you please do an quick review before merging this PR?

I'll take a look, Could you also get a review from @cachob on the screenshots?

@mudassir-hafeez
Copy link
Contributor Author

mudassir-hafeez commented Jul 1, 2024

I'll take a look, Could you also get a review from @cachob on the screenshots?

I've updated the PR description with the new screenshots and email attachments for @cachob to review.

Copy link
Contributor

@arslanashraf7 arslanashraf7 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just requested a couple of changes. Along with that, I would recommend:

  1. Share the complete screenshot of the email with @cachob - You can add it to the PR description.
  2. The order of the enrollment and welcome emails is not specified - In my testing the order was random e.g. sometimes I got the welcome email before the enrollment email. (We should check with @cachob If that matters)

@@ -191,6 +194,46 @@ def send_course_run_unenrollment_email(enrollment):
log.exception("Error sending unenrollment success email: %s", exp) # noqa: TRY401


def send_welcome_course_run_enrollment_email(enrollment):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def send_welcome_course_run_enrollment_email(enrollment):
def send_course_run_enrollment_welcome_email(enrollment):

@@ -191,6 +194,46 @@ def send_course_run_unenrollment_email(enrollment):
log.exception("Error sending unenrollment success email: %s", exp) # noqa: TRY401


def send_welcome_course_run_enrollment_email(enrollment):
"""
Welcome the user of successful enrollment for a course run
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Welcome the user of successful enrollment for a course run
Send welcome email to the user on successful enrollment

).strftime(EMAIL_TIME_FORMAT)
if enrollment.run.end_date:
run_end_date = enrollment.run.end_date.strftime(EMAIL_DATE_FORMAT)
date_range = (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
date_range = (
run_duration = (

)
)
except: # noqa: E722
log.exception("Error sending welcome success email")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
log.exception("Error sending welcome success email")
log.exception("Error sending welcome email")

Comment on lines 206 to 210
if enrollment.run.start_date:
run_start_date = enrollment.run.start_date.strftime(EMAIL_DATE_FORMAT)
run_start_time = enrollment.run.start_date.astimezone(
datetime.timezone.utc
).strftime(EMAIL_TIME_FORMAT)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: We can format this in one go. e.g. a format with - in it and then we can split date and time.

Comment on lines 212 to 213
run_end_date = enrollment.run.end_date.strftime(EMAIL_DATE_FORMAT)
date_range = (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: This and above method for start dates could be a utility function that we can call from here.

tuple: A tuple containing the formatted date and time strings.
"""
if run_date:
from ecommerce.mail_api import EMAIL_DATE_FORMAT
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use import here to avoid circular import

@mudassir-hafeez mudassir-hafeez force-pushed the mudassir/Welcome-emails-for-xpro-learners branch from 58f6c3c to af424be Compare July 2, 2024 11:49
@mudassir-hafeez
Copy link
Contributor Author

mudassir-hafeez commented Jul 2, 2024

Share the complete screenshot of the email with @cachob - You can add it to the PR description.

Added

The order of the enrollment and welcome emails is not specified - In my testing the order was random e.g. sometimes I got the welcome email before the enrollment email. (We should check with @cachob If that matters)

At my end, it seems to be in order, first enrollment email and then welcome email. Also we are sending them synchronously. Not sure if it is due to server delay or latency at your end. @cachob would you please look into the Arslan's comment #3017 (review) if that matters?

@cachob
Copy link

cachob commented Jul 2, 2024

@arslanashraf7 / @mudassir-hafeez - I don't think the order of which emails come first is critical. Are we able to ensure the order? I assumed that these could be subject to delays depending on the email service, user email settings, etc.

The screenshots look good. Do you mind giving me a quick overview of how this feature will work?

@arslanashraf7
Copy link
Contributor

@arslanashraf7 / @mudassir-hafeez - I don't think the order of which emails come first is critical. Are we able to ensure the order? I assumed that these could be subject to delays depending on the email service, user email settings, etc.

We can get around this with:

  1. Send the welcome email in a celery task with a delay of a few seconds after sending the successful enrollment email. (Will help maintain the order in general but sometimes it might not work for reasons you mentioned)
  2. Send the enrollment email and wait on it's success and then send the welcome email. (Good but since the order won't matter we can skip this as well)

A question for you @cachob:

Do you this we should keep this feature behind a feature flag as well? Any chance the course team might want to turn this off at some point?

The screenshots look good. Do you mind giving me a quick overview of how this feature will work?

I'd let @mudassir-hafeez answer this.

@mudassir-hafeez
Copy link
Contributor Author

The screenshots look good. Do you mind giving me a quick overview of how this feature will work?

@cachob, this feature works similarly to the enrollment email. There is no admin interaction required. The welcome email will be delivered to the user along with the enrollment email for each course enrollment. If we enroll a user in a course (or program), the welcome email will be automatically delivered on enrollment. This also applies to program enrollment, where the welcome email will be delivered for each course within the program. This ensures that learners receive the welcome email automatically at the moment of enrollment.

CC: @arslanashraf7

@mudassir-hafeez
Copy link
Contributor Author

Also discussed with @arslanashraf7 specifically for the case of a program. For example, if we have 5 courses in a program (in production) and 5 enrollment emails were being delivered previously. With this feature, now it will add 5 welcome emails for these course enrollments in the program, which will make a total of 10 emails. This number will vary if we have more or fewer courses in the program.

We thought to check with @blarghmatey from DevOps to ensure we wouldn't encounter any issues (e.g., rate limiting, emails being marked as spam or any other potential issues) with this number of emails being sent.

Copy link
Contributor

@arslanashraf7 arslanashraf7 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Looks good! Thanks for making all the changes. We can merge this PR once we have responses from @cachob on the above conversation and nothing new is to be incorporated.

@cachob
Copy link

cachob commented Jul 10, 2024

@arslanashraf7 / @mudassir-hafeez - thanks for the responses. Let's set this up behind a feature flag as well. I think moving forward all enhancements like these should be behind one as well.

@mudassir-hafeez
Copy link
Contributor Author

@arslanashraf7 would you please re-review as I have made some changes to make this feature behind a feature flag?
Related ol-infrastructure PR is here mitodl/ol-infrastructure#2548.

Copy link
Contributor

@arslanashraf7 arslanashraf7 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One small change. Otherwise looks good 👍

Before you merge:

  1. Rebase
  2. Apply the requested change

@@ -200,6 +200,9 @@ def send_course_run_enrollment_welcome_email(enrollment):
Args:
enrollment (CourseRunEnrollment): the enrollment for which to send the welcome email
"""
if not settings.FEATURES.get("ENROLLMENT_WELCOME_EMAIL", False):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The feature flags are more readable when applied to the initiation of something. In this case, the flag should be placed where we are calling send_course_run_enrollment_welcome_email from. Someone looking at the code in api.py might think that the email would be sent but that would be blocked in this function. So, If we take this flag to the api.py before calling this function that would make more sense for code reader.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It’s a good suggestion, but we will need to make this change in both places. Actually, I tried to make this change in a common place as it is being called not only through api.py but also through courses/management/utils.py for transfer enrollment.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, Let's keep it here then.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, Let's keep it here then.

Sorry I was looking into addressing your request, and I pushed commit as didn't notice we were agreed upon.
I have reverted it. Now It seems we are good and ready to go to merge.

@mudassir-hafeez mudassir-hafeez force-pushed the mudassir/Welcome-emails-for-xpro-learners branch from 7ac04f8 to 560b541 Compare July 12, 2024 11:47
@mudassir-hafeez mudassir-hafeez force-pushed the mudassir/Welcome-emails-for-xpro-learners branch from d607fc5 to 8079eb4 Compare July 12, 2024 11:49
@mudassir-hafeez mudassir-hafeez merged commit 82cb1a2 into master Jul 12, 2024
7 checks passed
@mudassir-hafeez mudassir-hafeez deleted the mudassir/Welcome-emails-for-xpro-learners branch July 12, 2024 12:54
@odlbot odlbot mentioned this pull request Jul 12, 2024
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants