Skip to content

Commit 95368b5

Browse files
feat: add SES delivery support for course update emails
1 parent 5d3e9c7 commit 95368b5

File tree

3 files changed

+58
-2
lines changed

3 files changed

+58
-2
lines changed

openedx/core/djangoapps/ace_common/utils.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,18 @@
22
Utility functions for edx-ace.
33
"""
44
import logging
5+
from openedx.features.course_experience import (
6+
ENABLE_SES_FOR_COURSEUPDATE,
7+
)
58

69
log = logging.getLogger(__name__)
710

811

12+
SES_MESSAGE_FLAG_MAP = {
13+
'course_update': ENABLE_SES_FOR_COURSEUPDATE,
14+
}
15+
16+
917
def setup_firebase_app(firebase_credentials, app_name='fcm-app'):
1018
"""
1119
Returns a Firebase app instance if the Firebase credentials are provided.
@@ -19,3 +27,18 @@ def setup_firebase_app(firebase_credentials, app_name='fcm-app'):
1927
certificate = firebase_admin.credentials.Certificate(firebase_credentials)
2028
app = firebase_admin.initialize_app(certificate, name=app_name)
2129
return app
30+
31+
32+
def should_route_to_ses(msg):
33+
"""
34+
Determine whether an ACE message should be routed via SES.
35+
36+
Routing is controlled by message-specific waffle flags.
37+
"""
38+
flag = SES_MESSAGE_FLAG_MAP.get(msg.name)
39+
40+
if not flag:
41+
return False
42+
43+
# Environment-level flag (not course-scoped)
44+
return flag.is_enabled()

openedx/core/djangoapps/schedules/tasks.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from django.core.exceptions import ValidationError
1313
from django.db.utils import DatabaseError
1414
from edx_ace import ace
15+
from edx_ace.errors import RecoverableChannelDeliveryError
1516
from edx_ace.message import Message
1617
from edx_ace.utils.date import deserialize, serialize
1718
from edx_django_utils.monitoring import (
@@ -23,10 +24,12 @@
2324
from importlib import import_module
2425
from opaque_keys.edx.keys import CourseKey
2526

27+
from openedx.core.djangoapps.ace_common.utils import should_route_to_ses
2628
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
2729
from openedx.core.djangoapps.schedules import message_types, resolvers
2830
from openedx.core.djangoapps.schedules.models import Schedule, ScheduleConfig
2931
from openedx.core.lib.celery.task_utils import emulate_http_request
32+
from openedx.features.course_experience import ENABLE_SES_FOR_COURSEUPDATE
3033
from common.djangoapps.track import segment
3134

3235
LOG = logging.getLogger(__name__)
@@ -283,15 +286,35 @@ def _schedule_send(msg_str, site_id, delivery_config_var, log_prefix): # lint-a
283286
if _is_delivery_enabled(site, delivery_config_var, log_prefix):
284287
msg = Message.from_string(msg_str)
285288
msg.options['skip_disable_user_policy'] = True
286-
287289
user = User.objects.get(id=msg.recipient.lms_user_id)
288290
if not user.has_usable_password():
289291
LOG.info(f'{delivery_config_var} Scheduled email User is disabled {user.username}')
290292
return
291293
with emulate_http_request(site=site, user=user):
292294
_annonate_send_task_for_monitoring(msg)
293295
LOG.debug('%s: Sending message = %s', log_prefix, msg_str)
294-
ace.send(msg)
296+
if should_route_to_ses(msg):
297+
msg.options.update({
298+
'from_address': settings.LMS_COMM_DEFAULT_FROM_EMAIL,
299+
'override_default_channel': 'django_email',
300+
'transactional': True,
301+
})
302+
303+
try:
304+
ace.send(msg)
305+
except RecoverableChannelDeliveryError:
306+
LOG.warning(
307+
'%s: SES send failed for user %s, raising for retry',
308+
log_prefix,
309+
user.id,
310+
)
311+
raise
312+
LOG.info(
313+
'%s: Course Update email for user %s sent via %s',
314+
log_prefix,
315+
user.id,
316+
'SES' if should_route_to_ses(msg) else 'default ACE channel',
317+
)
295318
_track_message_sent(site, user, msg)
296319

297320

openedx/features/course_experience/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,16 @@
8383
# .. toggle_tickets: https://openedx.atlassian.net/browse/AA-36
8484
CALENDAR_SYNC_FLAG = CourseWaffleFlag(f'{WAFFLE_FLAG_NAMESPACE}.calendar_sync', __name__) # lint-amnesty, pylint: disable=toggle-missing-annotation
8585

86+
# .. toggle_name: course_experience.enable_ses_for_courseupdate
87+
# .. toggle_implementation: CourseWaffleFlag
88+
# .. toggle_default: False
89+
# .. toggle_description: Used to determine whether or not to use AWS SES to send Course Update emails for the course.
90+
# .. toggle_use_cases: opt_in, temporary
91+
# .. toggle_creation_date: 2026-01-27
92+
# .. toggle_target_removal_date: None
93+
# .. toggle_warning: This temporary feature toggle does not have a target removal date.
94+
ENABLE_SES_FOR_COURSEUPDATE = CourseWaffleFlag(f'{WAFFLE_FLAG_NAMESPACE}.enable_ses_for_courseupdate', __name__) # lint-amnesty, pylint: disable=toggle-missing-annotation
95+
8696

8797
def course_home_page_title(_course):
8898
"""

0 commit comments

Comments
 (0)