Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions common/djangoapps/student/views/management.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
from common.djangoapps.util.json_request import JsonResponse
from common.djangoapps.student.signals import USER_EMAIL_CHANGED
from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order
from edx_toggles.toggles import WaffleFlag

log = logging.getLogger("edx.student")

Expand All @@ -110,6 +111,19 @@
REGISTRATION_UTM_CREATED_AT = 'registration_utm_created_at'
USER_ACCOUNT_ACTIVATED = 'edx.user.account.activated'

# .. toggle_name: user_authn.enable_ses_for_account_activation
# .. toggle_implementation: WaffleFlag
# .. toggle_default: False
# .. toggle_description: Route account activation emails via SES using ACE.
# .. toggle_use_cases: opt_in, temporary
# .. toggle_creation_date: 2026-01-22
# .. toggle_target_removal_date: None
# .. toggle_warning: This toggle controls email delivery routing and should be enabled cautiously.
ENABLE_SES_FOR_ACCOUNT_ACTIVATION = WaffleFlag(
'user_authn.enable_ses_for_account_activation',
__name__,
)


def csrf_token(context):
"""
Expand Down Expand Up @@ -229,6 +243,21 @@ def compose_activation_email(
language=preferences_api.get_user_preference(user, LANGUAGE_KEY),
user_context=message_context,
)
if ENABLE_SES_FOR_ACCOUNT_ACTIVATION.is_enabled():

Choose a reason for hiding this comment

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

Hi @nthriveni-sonata-ship-it, couple of suggestions on refactor:

  1. Centralize the Routing Logic
    Currently, the SES routing logic is tied specifically to the management view. To ensure it also works for initial registrations (via Celery tasks) and user-requested resends, we should pull the logic into a centralized utility.
    Action: Move the WaffleFlag definitions and the check logic to: openedx/core/djangoapps/ace_common/utils.py

  2. Implement an Active Fallback (Safe-to-Fail) in openedx/core/djangoapps/user_authn/tasks.py
    The current implementation introduces a single point of failure. If the Waffle flag is ON but the SES backend (django_email) is unreachable, the email will never be sent. We should prioritize SES but fallback to the default ACE path if it fails.

Why?
One function handles routing for all activation/course-update flows.
Even if SES has an outage, user registrations won't be blocked.
Management views and Celery tasks don't need to know the names of individual Waffle flags.

log.info(
"ACCOUNT ACTIVATION EMAIL | SES flag=%s | override_channel=%s",
ENABLE_SES_FOR_ACCOUNT_ACTIVATION.is_enabled(),
msg.options.get('override_default_channel')
)
msg.options.update({
'transactional': True,
'override_default_channel': 'django_email',
'from_address': configuration_helpers.get_value(
'ACTIVATION_EMAIL_FROM_ADDRESS'
) or configuration_helpers.get_value(
'email_from_address', settings.DEFAULT_FROM_EMAIL
),
})

return msg

Expand Down
Loading