From f93023bafef49e8e76e3c9628834909383f2fc7a Mon Sep 17 00:00:00 2001
From: Shadi Naif <shadinaif@gmail.com>
Date: Thu, 13 Sep 2018 14:51:16 +0300
Subject: [PATCH] Convert Account Activation Emails to edx-ACE

---
 cms/templates/emails/activation_email.txt     |  11 --
 .../emails/activation_email_subject.txt       |   2 -
 common/djangoapps/student/helpers.py          |   4 +-
 common/djangoapps/student/message_types.py    |   7 +
 common/djangoapps/student/tasks.py            |  22 ++-
 common/djangoapps/student/tests/test_email.py | 128 +++++-------------
 common/djangoapps/student/tests/test_tasks.py |  49 ++++++-
 common/djangoapps/student/views/management.py | 102 ++++++--------
 .../edx_ace/accountactivation/email/body.html |  57 ++++++++
 .../edx_ace/accountactivation/email/body.txt  |  11 ++
 .../accountactivation/email/from_name.txt     |   1 +
 .../edx_ace/accountactivation/email/head.html |   1 +
 .../accountactivation/email/subject.txt       |   4 +
 .../templates/emails/activation_email.txt     |  16 ---
 .../emails/activation_email_subject.txt       |   3 -
 lms/templates/emails/activation_email.txt     |  24 ----
 .../emails/activation_email_subject.txt       |   3 -
 .../core/djangoapps/user_authn/views/login.py |   8 +-
 18 files changed, 227 insertions(+), 226 deletions(-)
 delete mode 100644 cms/templates/emails/activation_email.txt
 delete mode 100644 cms/templates/emails/activation_email_subject.txt
 create mode 100644 common/templates/student/edx_ace/accountactivation/email/body.html
 create mode 100644 common/templates/student/edx_ace/accountactivation/email/body.txt
 create mode 100644 common/templates/student/edx_ace/accountactivation/email/from_name.txt
 create mode 100644 common/templates/student/edx_ace/accountactivation/email/head.html
 create mode 100644 common/templates/student/edx_ace/accountactivation/email/subject.txt
 delete mode 100644 common/test/test_sites/test_site/templates/emails/activation_email.txt
 delete mode 100644 common/test/test_sites/test_site/templates/emails/activation_email_subject.txt
 delete mode 100644 lms/templates/emails/activation_email.txt
 delete mode 100644 lms/templates/emails/activation_email_subject.txt

diff --git a/cms/templates/emails/activation_email.txt b/cms/templates/emails/activation_email.txt
deleted file mode 100644
index 7a54fc6b35f..00000000000
--- a/cms/templates/emails/activation_email.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-<%! from django.utils.translation import ugettext as _ %>
-
-${_("Thank you for signing up for {studio_name}! To activate your account, please copy and paste this address into your web browser's address bar:").format(studio_name=settings.STUDIO_NAME)}
-
-% if is_secure:
-  https://${ site }/activate/${ key }
-% else:
-  http://${ site }/activate/${ key }
-% endif
-
-${_("If you didn't request this, you don't need to do anything; you won't receive any more email from us. Please do not reply to this e-mail; if you require assistance, check the help section of the {studio_name} web site.").format(studio_name=settings.STUDIO_NAME)}
diff --git a/cms/templates/emails/activation_email_subject.txt b/cms/templates/emails/activation_email_subject.txt
deleted file mode 100644
index d6388657107..00000000000
--- a/cms/templates/emails/activation_email_subject.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-<%! from django.utils.translation import ugettext as _ %>
-${_("Your account for {studio_name}").format(studio_name=settings.STUDIO_NAME)}
diff --git a/common/djangoapps/student/helpers.py b/common/djangoapps/student/helpers.py
index 09cb91c3790..e84d440a4b3 100644
--- a/common/djangoapps/student/helpers.py
+++ b/common/djangoapps/student/helpers.py
@@ -347,7 +347,9 @@ def generate_activation_email_context(user, registration):
         'key': registration.activation_key,
         'lms_url': configuration_helpers.get_value('LMS_ROOT_URL', settings.LMS_ROOT_URL),
         'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME),
-        'support_url': configuration_helpers.get_value('SUPPORT_SITE_LINK', settings.SUPPORT_SITE_LINK),
+        'support_url': configuration_helpers.get_value(
+            'ACTIVATION_EMAIL_SUPPORT_LINK', settings.ACTIVATION_EMAIL_SUPPORT_LINK
+        ) or settings.SUPPORT_SITE_LINK,
         'support_email': configuration_helpers.get_value('CONTACT_EMAIL', settings.CONTACT_EMAIL),
     }
 
diff --git a/common/djangoapps/student/message_types.py b/common/djangoapps/student/message_types.py
index 4e64e7d6017..e9da1966f0c 100644
--- a/common/djangoapps/student/message_types.py
+++ b/common/djangoapps/student/message_types.py
@@ -33,3 +33,10 @@ class RecoveryEmailCreate(BaseMessageType):
         super(RecoveryEmailCreate, self).__init__(*args, **kwargs)
 
         self.options['transactional'] = True
+
+
+class AccountActivation(BaseMessageType):
+    def __init__(self, *args, **kwargs):
+        super(AccountActivation, self).__init__(*args, **kwargs)
+
+        self.options['transactional'] = True
diff --git a/common/djangoapps/student/tasks.py b/common/djangoapps/student/tasks.py
index ed32d952ada..1e96fe523e0 100644
--- a/common/djangoapps/student/tasks.py
+++ b/common/djangoapps/student/tasks.py
@@ -5,30 +5,40 @@ from __future__ import absolute_import
 
 import logging
 
-from boto.exception import NoAuthHandlerFound
 from celery.exceptions import MaxRetriesExceededError
 from celery.task import task  # pylint: disable=no-name-in-module, import-error
 from django.conf import settings
-from django.core import mail
+from edx_ace import ace
+from edx_ace.errors import RecoverableChannelDeliveryError
+from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
 
 log = logging.getLogger('edx.celery.task')
 
 
 @task(bind=True)
-def send_activation_email(self, subject, message, from_address, dest_addr):
+def send_activation_email(self, msg, from_address=None):
     """
     Sending an activation email to the user.
     """
     max_retries = settings.RETRY_ACTIVATION_EMAIL_MAX_ATTEMPTS
     retries = self.request.retries
+
+    if from_address is None:
+        from_address = configuration_helpers.get_value('ACTIVATION_EMAIL_FROM_ADDRESS') or (
+            configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL)
+        )
+    msg.options['from_address'] = from_address
+
+    dest_addr = msg.recipient.email_address
+
     try:
-        mail.send_mail(subject, message, from_address, [dest_addr], fail_silently=False)
+        ace.send(msg)
         # Log that the Activation Email has been sent to user without an exception
         log.info("Activation Email has been sent to User {user_email}".format(
             user_email=dest_addr
         ))
-    except NoAuthHandlerFound:  # pylint: disable=broad-except
-        log.info('Retrying sending email to user {dest_addr}, attempt # {attempt} of {max_attempts}'. format(
+    except RecoverableChannelDeliveryError:
+        log.info('Retrying sending email to user {dest_addr}, attempt # {attempt} of {max_attempts}'.format(
             dest_addr=dest_addr,
             attempt=retries,
             max_attempts=max_retries
diff --git a/common/djangoapps/student/tests/test_email.py b/common/djangoapps/student/tests/test_email.py
index 5089596d909..4901c390b73 100644
--- a/common/djangoapps/student/tests/test_email.py
+++ b/common/djangoapps/student/tests/test_email.py
@@ -25,7 +25,7 @@ from openedx.core.djangoapps.user_api.config.waffle import PREVENT_AUTH_USER_WRI
 from openedx.core.djangolib.testing.utils import CacheIsolationMixin, CacheIsolationTestCase
 from openedx.core.lib.request_utils import safe_get_host
 from student.models import PendingEmailChange, Registration, UserProfile
-from student.tests.factories import PendingEmailChangeFactory, RegistrationFactory, UserFactory
+from student.tests.factories import PendingEmailChangeFactory, UserFactory
 from student.views import (
     SETTING_CHANGE_INITIATED,
     confirm_email_change,
@@ -91,8 +91,9 @@ class EmailTestMixin(object):
         self.addCleanup(settings.ALLOWED_HOSTS.pop)
 
 
+@ddt.ddt
 @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
-class ActivationEmailTests(CacheIsolationTestCase):
+class ActivationEmailTests(EmailTemplateTagMixin, CacheIsolationTestCase):
     """
     Test sending of the activation email.
     """
@@ -102,22 +103,36 @@ class ActivationEmailTests(CacheIsolationTestCase):
     # Text fragments we expect in the body of an email
     # sent from an OpenEdX installation.
     OPENEDX_FRAGMENTS = [
-        u"high-quality {platform} courses".format(platform=settings.PLATFORM_NAME),
-        "http://edx.org/activate/",
         (
-            u"please use our web form at "
-            u"{support_url} ".format(support_url=settings.SUPPORT_SITE_LINK)
-        )
+            u"You're almost there! Use the link below to activate your account to access engaging, "
+            u"high-quality {platform_name} courses. Note that you will not be able to log back into your "
+            u"account until you have activated it.".format(
+                platform_name=settings.PLATFORM_NAME
+            )
+        ),
+        u"{}/activate/".format(settings.LMS_ROOT_URL),
+        u"If you need help, please use our web form at ", (
+            settings.ACTIVATION_EMAIL_SUPPORT_LINK or settings.SUPPORT_SITE_LINK
+        ),
+        settings.CONTACT_EMAIL,
+        u"This email message was automatically sent by ",
+        settings.LMS_ROOT_URL,
+        u" because someone attempted to create an account on {platform_name}".format(
+            platform_name=settings.PLATFORM_NAME
+        ),
+        u" using this email address."
     ]
 
-    def test_activation_email(self):
+    @ddt.data('plain_text', 'html')
+    def test_activation_email(self, test_body_type):
         self._create_account()
-        self._assert_activation_email(self.ACTIVATION_SUBJECT, self.OPENEDX_FRAGMENTS)
+        self._assert_activation_email(self.ACTIVATION_SUBJECT, self.OPENEDX_FRAGMENTS, test_body_type)
 
     @with_comprehensive_theme("edx.org")
-    def test_activation_email_edx_domain(self):
+    @ddt.data('plain_text', 'html')
+    def test_activation_email_edx_domain(self, test_body_type):
         self._create_account()
-        self._assert_activation_email(self.ACTIVATION_SUBJECT, self.OPENEDX_FRAGMENTS)
+        self._assert_activation_email(self.ACTIVATION_SUBJECT, self.OPENEDX_FRAGMENTS, test_body_type)
 
     def _create_account(self):
         """
@@ -141,15 +156,23 @@ class ActivationEmailTests(CacheIsolationTestCase):
             )
         )
 
-    def _assert_activation_email(self, subject, body_fragments):
+    def _assert_activation_email(self, subject, body_fragments, test_body_type):
         """
         Verify that the activation email was sent.
         """
         self.assertEqual(len(mail.outbox), 1)
         msg = mail.outbox[0]
         self.assertEqual(msg.subject, subject)
+
+        body_text = {
+            'plain_text': msg.body,
+            'html': msg.alternatives[0][0]
+        }
+        assert test_body_type in body_text
+        body_to_be_tested = body_text[test_body_type]
+
         for fragment in body_fragments:
-            self.assertIn(fragment, msg.body)
+            self.assertIn(fragment, body_to_be_tested)
 
     def test_do_not_send_email_and_do_activate(self):
         """
@@ -197,85 +220,6 @@ class ActivationEmailTests(CacheIsolationTestCase):
                 )
 
 
-@patch('student.views.management.render_to_string', Mock(side_effect=mock_render_to_string, autospec=True))
-@patch('django.contrib.auth.models.User.email_user')
-class ReactivationEmailTests(EmailTestMixin, CacheIsolationTestCase):
-    """
-    Test sending a reactivation email to a user
-    """
-
-    def setUp(self):
-        super(ReactivationEmailTests, self).setUp()
-        self.user = UserFactory.create()
-        self.unregisteredUser = UserFactory.create()
-        self.registration = RegistrationFactory.create(user=self.user)
-
-    def reactivation_email(self, user):
-        """
-        Send the reactivation email to the specified user,
-        and return the response as json data.
-        """
-        return json.loads(send_reactivation_email_for_user(user).content.decode('utf-8'))
-
-    def assertReactivateEmailSent(self, email_user):
-        """
-        Assert that the correct reactivation email has been sent
-        """
-        context = generate_activation_email_context(self.user, self.registration)
-
-        self.assertEmailUser(
-            email_user,
-            'emails/activation_email_subject.txt',
-            context,
-            'emails/activation_email.txt',
-            context
-        )
-
-        # Thorough tests for safe_get_host are elsewhere; here we just want a quick URL sanity check
-        request = RequestFactory().post('unused_url')
-        request.user = self.user
-        request.META['HTTP_HOST'] = "aGenericValidHostName"
-        self.append_allowed_hosts("aGenericValidHostName")
-
-        with patch('edxmako.request_context.get_current_request', return_value=request):
-            body = render_to_string('emails/activation_email.txt', context)
-            host = safe_get_host(request)
-
-        self.assertIn(host, body)
-
-    def test_reactivation_email_failure(self, email_user):
-        self.user.email_user.side_effect = Exception
-        response_data = self.reactivation_email(self.user)
-
-        self.assertReactivateEmailSent(email_user)
-        self.assertFalse(response_data['success'])
-
-    def test_reactivation_for_unregistered_user(self, email_user):  # pylint: disable=unused-argument
-        """
-        Test that trying to send a reactivation email to an unregistered
-        user fails without throwing a 500 error.
-        """
-        response_data = self.reactivation_email(self.unregisteredUser)
-
-        self.assertFalse(response_data['success'])
-
-    def test_reactivation_for_no_user_profile(self, email_user):  # pylint: disable=unused-argument
-        """
-        Test that trying to send a reactivation email to a user without
-        user profile fails without throwing 500 error.
-        """
-        user = UserFactory.build(username='test_user', email='test_user@test.com')
-        user.save()
-        response_data = self.reactivation_email(user)
-        self.assertFalse(response_data['success'])
-
-    def test_reactivation_email_success(self, email_user):
-        response_data = self.reactivation_email(self.user)
-
-        self.assertReactivateEmailSent(email_user)
-        self.assertTrue(response_data['success'])
-
-
 @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', "Test only valid in LMS")
 class EmailChangeRequestTests(EventTestMixin, EmailTemplateTagMixin, CacheIsolationTestCase):
     """
diff --git a/common/djangoapps/student/tests/test_tasks.py b/common/djangoapps/student/tests/test_tasks.py
index a377aea9527..b14e8dc3e6e 100644
--- a/common/djangoapps/student/tests/test_tasks.py
+++ b/common/djangoapps/student/tests/test_tasks.py
@@ -5,13 +5,15 @@ Tests for the Sending activation email celery tasks
 from __future__ import absolute_import
 
 import mock
-from boto.exception import NoAuthHandlerFound
 from django.conf import settings
 from django.test import TestCase
 from six.moves import range
 
+from edx_ace.errors import ChannelError, RecoverableChannelDeliveryError
 from lms.djangoapps.courseware.tests.factories import UserFactory
+from student.models import Registration
 from student.tasks import send_activation_email
+from student.views.management import compose_activation_email
 
 
 class SendActivationEmailTestCase(TestCase):
@@ -23,18 +25,32 @@ class SendActivationEmailTestCase(TestCase):
         super(SendActivationEmailTestCase, self).setUp()
         self.student = UserFactory()
 
+        registration = Registration()
+        registration.register(self.student)
+
+        self.msg = compose_activation_email("http://www.example.com", self.student, registration)
+
+    def test_ComposeEmail(self):
+        """
+        Tests that attributes of the message are being filled correctly in compose_activation_email
+        """
+        self.assertEquals(self.msg.recipient.username, self.student.username)
+        self.assertEquals(self.msg.recipient.email_address, self.student.email)
+        self.assertEquals(self.msg.context['routed_user'], self.student.username)
+        self.assertEquals(self.msg.context['routed_user_email'], self.student.email)
+        self.assertEquals(self.msg.context['routed_profile_name'], '')
+
     @mock.patch('time.sleep', mock.Mock(return_value=None))
     @mock.patch('student.tasks.log')
-    @mock.patch('django.core.mail.send_mail', mock.Mock(side_effect=NoAuthHandlerFound))
-    def test_send_email(self, mock_log):
+    @mock.patch('student.tasks.ace.send', mock.Mock(side_effect=RecoverableChannelDeliveryError(None, None)))
+    def test_RetrySendUntilFail(self, mock_log):
         """
         Tests retries when the activation email doesn't send
         """
         from_address = 'task_testing@example.com'
         email_max_attempts = settings.RETRY_ACTIVATION_EMAIL_MAX_ATTEMPTS
 
-        # pylint: disable=no-member
-        send_activation_email.delay('Task_test', 'Task_test_message', from_address, self.student.email)
+        send_activation_email.delay(self.msg, from_address=from_address)
 
         # Asserts sending email retry logging.
         for attempt in range(email_max_attempts):
@@ -54,3 +70,26 @@ class SendActivationEmailTestCase(TestCase):
             exc_info=True
         )
         self.assertEquals(mock_log.error.call_count, 1)
+
+    @mock.patch('student.tasks.log')
+    @mock.patch('student.tasks.ace.send', mock.Mock(side_effect=ChannelError))
+    def test_UnrecoverableSendError(self, mock_log):
+        """
+        Tests that a major failure of the send is logged
+        """
+        from_address = 'task_testing@example.com'
+
+        send_activation_email.delay(self.msg, from_address=from_address)
+
+        # Asserts that the error was logged
+        mock_log.exception.assert_called_with(
+            'Unable to send activation email to user from "%s" to "%s"',
+            from_address,
+            self.student.email,
+            exc_info=True
+        )
+
+        # Assert that nothing else was logged
+        self.assertEquals(mock_log.info.call_count, 0)
+        self.assertEquals(mock_log.error.call_count, 0)
+        self.assertEquals(mock_log.exception.call_count, 1)
diff --git a/common/djangoapps/student/views/management.py b/common/djangoapps/student/views/management.py
index 7e2905a6e56..6cc3b60f32f 100644
--- a/common/djangoapps/student/views/management.py
+++ b/common/djangoapps/student/views/management.py
@@ -66,7 +66,7 @@ from openedx.core.djangoapps.user_authn.message_types import PasswordReset
 from openedx.core.djangolib.markup import HTML, Text
 from student.forms import AccountCreationForm
 from student.helpers import DISABLE_UNENROLL_CERT_STATES, cert_info, generate_activation_email_context
-from student.message_types import EmailChange, EmailChangeConfirmation, RecoveryEmailCreate
+from student.message_types import AccountActivation, EmailChange, EmailChangeConfirmation, RecoveryEmailCreate
 from student.models import (
     AccountRecovery,
     CourseEnrollment,
@@ -178,76 +178,56 @@ def index(request, extra_context=None, user=AnonymousUser()):
     return render_to_response('index.html', context)
 
 
-def compose_and_send_activation_email(user, profile, user_registration=None):
+def compose_activation_email(root_url, user, user_registration=None, route_enabled=False, profile_name=''):
     """
-    Construct all the required params and send the activation email
+    Construct all the required params for the activation email
     through celery task
-
-    Arguments:
-        user: current logged-in user
-        profile: profile object of the current logged-in user
-        user_registration: registration of the current logged-in user
     """
-    dest_addr = user.email
     if user_registration is None:
         user_registration = Registration.objects.get(user=user)
-    context = generate_activation_email_context(user, user_registration)
-    subject = render_to_string('emails/activation_email_subject.txt', context)
-    # Email subject *must not* contain newlines
-    subject = ''.join(subject.splitlines())
-    message_for_activation = render_to_string('emails/activation_email.txt', context)
-    from_address = configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL)
-    from_address = configuration_helpers.get_value('ACTIVATION_EMAIL_FROM_ADDRESS', from_address)
-    if settings.FEATURES.get('REROUTE_ACTIVATION_EMAIL'):
+
+    message_context = generate_activation_email_context(user, user_registration)
+    message_context.update({
+        'confirm_activation_link': '{root_url}/activate/{activation_key}'.format(
+            root_url=root_url,
+            activation_key=message_context['key']
+        ),
+        'route_enabled': route_enabled,
+        'routed_user': user.username,
+        'routed_user_email': user.email,
+        'routed_profile_name': profile_name,
+    })
+
+    if route_enabled:
         dest_addr = settings.FEATURES['REROUTE_ACTIVATION_EMAIL']
-        message_for_activation = ("Activation for %s (%s): %s\n" % (user, user.email, profile.name) +
-                                  '-' * 80 + '\n\n' + message_for_activation)
-    send_activation_email.delay(subject, message_for_activation, from_address, dest_addr)
+    else:
+        dest_addr = user.email
 
+    msg = AccountActivation().personalize(
+        recipient=Recipient(user.username, dest_addr),
+        language=preferences_api.get_user_preference(user, LANGUAGE_KEY),
+        user_context=message_context,
+    )
 
-def send_reactivation_email_for_user(user):
-    try:
-        registration = Registration.objects.get(user=user)
-    except Registration.DoesNotExist:
-        return JsonResponse({
-            "success": False,
-            "error": _('No inactive user with this e-mail exists'),
-        })
+    return msg
 
-    try:
-        context = generate_activation_email_context(user, registration)
-    except ObjectDoesNotExist:
-        log.error(
-            u'Unable to send reactivation email due to unavailable profile for the user "%s"',
-            user.username,
-            exc_info=True
-        )
-        return JsonResponse({
-            "success": False,
-            "error": _('Unable to send reactivation email')
-        })
 
-    subject = render_to_string('emails/activation_email_subject.txt', context)
-    subject = ''.join(subject.splitlines())
-    message = render_to_string('emails/activation_email.txt', context)
-    from_address = configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL)
-    from_address = configuration_helpers.get_value('ACTIVATION_EMAIL_FROM_ADDRESS', from_address)
+def compose_and_send_activation_email(user, profile, user_registration=None):
+    """
+    Construct all the required params and send the activation email
+    through celery task
 
-    try:
-        user.email_user(subject, message, from_address)
-    except Exception:  # pylint: disable=broad-except
-        log.error(
-            u'Unable to send reactivation email from "%s" to "%s"',
-            from_address,
-            user.email,
-            exc_info=True
-        )
-        return JsonResponse({
-            "success": False,
-            "error": _('Unable to send reactivation email')
-        })
+    Arguments:
+        user: current logged-in user
+        profile: profile object of the current logged-in user
+        user_registration: registration of the current logged-in user
+    """
+    route_enabled = settings.FEATURES.get('REROUTE_ACTIVATION_EMAIL')
 
-    return JsonResponse({"success": True})
+    root_url = configuration_helpers.get_value('LMS_ROOT_URL', settings.LMS_ROOT_URL)
+    msg = compose_activation_email(root_url, user, user_registration, route_enabled, profile.name)
+
+    send_activation_email.delay(msg)
 
 
 @login_required
@@ -551,7 +531,9 @@ def activate_account(request, key):
                 '{html_start}Your account could not be activated{html_end}'
                 'Something went wrong, please <a href="{support_url}">contact support</a> to resolve this issue.'
             )).format(
-                support_url=configuration_helpers.get_value('SUPPORT_SITE_LINK', settings.SUPPORT_SITE_LINK),
+                support_url=configuration_helpers.get_value(
+                    'ACTIVATION_EMAIL_SUPPORT_LINK', settings.ACTIVATION_EMAIL_SUPPORT_LINK
+                ) or settings.SUPPORT_SITE_LINK,
                 html_start=HTML('<p class="message-title">'),
                 html_end=HTML('</p>'),
             ),
diff --git a/common/templates/student/edx_ace/accountactivation/email/body.html b/common/templates/student/edx_ace/accountactivation/email/body.html
new file mode 100644
index 00000000000..d4497672da1
--- /dev/null
+++ b/common/templates/student/edx_ace/accountactivation/email/body.html
@@ -0,0 +1,57 @@
+{% extends 'ace_common/edx_ace/common/base_body.html' %}
+
+{% load i18n %}
+{% load static %}
+{% block content %}
+<table width="100%" align="left" border="0" cellpadding="0" cellspacing="0" role="presentation">
+{% if route_enabled %}
+    <tr>
+        <td>
+            <p style="color: rgba(0,0,0,.75);">
+                {% blocktrans %}This is a routed Account Activation email for {{ routed_user }} ({{ routed_user_email }}): {{ routed_profile_name }}{% endblocktrans %}
+                <br />
+            </p>
+        </td>
+    </tr>
+{% endif %}
+    <tr>
+        <td>
+            <h1>
+                {% trans "Account Activation" %}
+            </h1>
+            <p style="color: rgba(0,0,0,.75);">
+                {% blocktrans %}You're almost there! Use the link below to activate your account to access engaging, high-quality {{ platform_name }} courses. Note that you will not be able to log back into your account until you have activated it.{% endblocktrans %}
+                <br />
+            </p>
+
+            {% trans "Activate Your Account" as course_cta_text %}
+            {% include "ace_common/edx_ace/common/return_to_course_cta.html" with course_cta_text=course_cta_text course_cta_url=confirm_activation_link %}
+        </td>
+    </tr>
+    <tr>
+        <td>
+            <p style="color: rgba(0,0,0,.75);">
+                {% blocktrans %}Enjoy learning with {{ platform_name }}.{% endblocktrans %}
+                <br />
+            </p>
+        </td>
+    </tr>
+        <td>
+            <p style="color: rgba(0,0,0,.75);">
+                {% blocktrans %}If you need help, please use our web form at <a href="{{ support_url }}">{{ support_url }}</a> or email <a href="mailto:{{ support_email }}">{{ support_email }}</a>.{% endblocktrans %}
+                <br />
+            </p>
+        </td>
+    <tr>
+
+    </tr>
+    <tr>
+        <td>
+            <p style="color: rgba(0,0,0,.75);">
+                {% blocktrans %}This email message was automatically sent by {{ lms_url }} because someone attempted to create an account on {{ platform_name }} using this email address.{% endblocktrans %}
+                <br />
+            </p>
+        </td>
+    </tr>
+</table>
+{% endblock %}
diff --git a/common/templates/student/edx_ace/accountactivation/email/body.txt b/common/templates/student/edx_ace/accountactivation/email/body.txt
new file mode 100644
index 00000000000..5efa1e2ad4b
--- /dev/null
+++ b/common/templates/student/edx_ace/accountactivation/email/body.txt
@@ -0,0 +1,11 @@
+{% load i18n %}{% autoescape off %}
+{% blocktrans %}You're almost there! Use the link below to activate your account to access engaging, high-quality {{ platform_name }} courses. Note that you will not be able to log back into your account until you have activated it.{% endblocktrans %}
+
+{{ confirm_activation_link }}
+
+{% blocktrans %}Enjoy learning with {{ platform_name }}.{% endblocktrans %}
+
+{% blocktrans %}If you need help, please use our web form at {{ support_url }} or email {{ support_email }}.{% endblocktrans %}
+
+{% blocktrans %}This email message was automatically sent by {{ lms_url }} because someone attempted to create an account on {{ platform_name }} using this email address.{% endblocktrans %}
+{% endautoescape %}
diff --git a/common/templates/student/edx_ace/accountactivation/email/from_name.txt b/common/templates/student/edx_ace/accountactivation/email/from_name.txt
new file mode 100644
index 00000000000..dcbc23c0048
--- /dev/null
+++ b/common/templates/student/edx_ace/accountactivation/email/from_name.txt
@@ -0,0 +1 @@
+{{ platform_name }}
diff --git a/common/templates/student/edx_ace/accountactivation/email/head.html b/common/templates/student/edx_ace/accountactivation/email/head.html
new file mode 100644
index 00000000000..366ada7ad92
--- /dev/null
+++ b/common/templates/student/edx_ace/accountactivation/email/head.html
@@ -0,0 +1 @@
+{% extends 'ace_common/edx_ace/common/base_head.html' %}
diff --git a/common/templates/student/edx_ace/accountactivation/email/subject.txt b/common/templates/student/edx_ace/accountactivation/email/subject.txt
new file mode 100644
index 00000000000..1fbb6356e86
--- /dev/null
+++ b/common/templates/student/edx_ace/accountactivation/email/subject.txt
@@ -0,0 +1,4 @@
+{% load i18n %}
+{% autoescape off %}
+{% blocktrans trimmed %}Action Required: Activate your {{ platform_name }} account{% endblocktrans %}
+{% endautoescape %}
diff --git a/common/test/test_sites/test_site/templates/emails/activation_email.txt b/common/test/test_sites/test_site/templates/emails/activation_email.txt
deleted file mode 100644
index 97b3c4f5e06..00000000000
--- a/common/test/test_sites/test_site/templates/emails/activation_email.txt
+++ /dev/null
@@ -1,16 +0,0 @@
-<%! from django.utils.translation import ugettext as _ %>
-
-${_("Thank you for signing up for Open edX! To activate "
-"your account, please copy and paste this address into your web "
-"browser's address bar:")}
-
-% if is_secure:
-  https://${ site }/activate/${ key }
-% else:
-  http://${ site }/activate/${ key }
-% endif
-
-${_("If you didn't request this, you don't need to do anything; you won't "
-    "receive any more email from us. Please do not reply to this e-mail; "
-    "if you require assistance, check the help section of the "
-    "Open edX web site.")}
diff --git a/common/test/test_sites/test_site/templates/emails/activation_email_subject.txt b/common/test/test_sites/test_site/templates/emails/activation_email_subject.txt
deleted file mode 100644
index fc4ef4425c0..00000000000
--- a/common/test/test_sites/test_site/templates/emails/activation_email_subject.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-<%! from django.utils.translation import ugettext as _ %>
-
-${_("Your account for Open edX")}
diff --git a/lms/templates/emails/activation_email.txt b/lms/templates/emails/activation_email.txt
deleted file mode 100644
index f5b1f093313..00000000000
--- a/lms/templates/emails/activation_email.txt
+++ /dev/null
@@ -1,24 +0,0 @@
-## mako
-<%! from django.utils.translation import ugettext as _ %>
-${_("You're almost there! Use the link to activate your account to access engaging, high-quality "
-"{platform_name} courses. Note that you will not be able to log back into your account until "
-"you have activated it.").format(platform_name=platform_name)}
-
-% if is_secure:
-https://${ site }/activate/${ key }
-% else:
-http://${ site }/activate/${ key }
-% endif
-
-${_("Enjoy learning with {platform_name}.").format(platform_name=platform_name)}
-
-${_("The {platform_name} Team").format(platform_name=platform_name)}
-
-${_("If you need help, please use our web form at {support_url} or email {support_email}.").format(
-  support_url=support_url, support_email=support_email
-)}
-
-${_("This email message was automatically sent by {lms_url} because someone attempted to create an "
-"account on {platform_name} using this email address.").format(
-  lms_url=lms_url, platform_name=platform_name
-)}
\ No newline at end of file
diff --git a/lms/templates/emails/activation_email_subject.txt b/lms/templates/emails/activation_email_subject.txt
deleted file mode 100644
index a6961b8e6a0..00000000000
--- a/lms/templates/emails/activation_email_subject.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-## mako
-<%! from django.utils.translation import ugettext as _ %>
-${_("Action Required: Activate your {platform_name} account").format(platform_name=platform_name)}
diff --git a/openedx/core/djangoapps/user_authn/views/login.py b/openedx/core/djangoapps/user_authn/views/login.py
index 81d7647a34c..28ada2e02cc 100644
--- a/openedx/core/djangoapps/user_authn/views/login.py
+++ b/openedx/core/djangoapps/user_authn/views/login.py
@@ -40,8 +40,8 @@ from openedx.core.djangoapps.user_authn.config.waffle import (
 )
 from openedx.core.djangolib.markup import HTML, Text
 from openedx.core.lib.api.view_utils import require_post_params
-from student.models import LoginFailures, AllowedAuthUser
-from student.views import send_reactivation_email_for_user
+from student.models import LoginFailures, AllowedAuthUser, UserProfile
+from student.views import compose_and_send_activation_email
 from third_party_auth import pipeline, provider
 import third_party_auth
 from track import segment
@@ -178,7 +178,9 @@ def _log_and_raise_inactive_user_auth_error(unauthenticated_user):
             unauthenticated_user.username)
         )
 
-    send_reactivation_email_for_user(unauthenticated_user)
+    profile = UserProfile.objects.get(user=unauthenticated_user)
+    compose_and_send_activation_email(unauthenticated_user, profile)
+
     raise AuthFailedError(_generate_not_activated_message(unauthenticated_user))
 
 
-- 
GitLab