Skip to content

Commit 524938a

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

3 files changed

Lines changed: 57 additions & 2 deletions

File tree

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: 24 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,6 +24,7 @@
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
@@ -283,15 +285,35 @@ def _schedule_send(msg_str, site_id, delivery_config_var, log_prefix): # lint-a
283285
if _is_delivery_enabled(site, delivery_config_var, log_prefix):
284286
msg = Message.from_string(msg_str)
285287
msg.options['skip_disable_user_policy'] = True
286-
287288
user = User.objects.get(id=msg.recipient.lms_user_id)
288289
if not user.has_usable_password():
289290
LOG.info(f'{delivery_config_var} Scheduled email User is disabled {user.username}')
290291
return
291292
with emulate_http_request(site=site, user=user):
292293
_annonate_send_task_for_monitoring(msg)
293294
LOG.debug('%s: Sending message = %s', log_prefix, msg_str)
294-
ace.send(msg)
295+
if should_route_to_ses(msg):
296+
msg.options.update({
297+
'from_address': settings.LMS_COMM_DEFAULT_FROM_EMAIL,
298+
'override_default_channel': 'django_email',
299+
'transactional': True,
300+
})
301+
302+
try:
303+
ace.send(msg)
304+
except RecoverableChannelDeliveryError:
305+
LOG.warning(
306+
'%s: SES send failed for user %s, raising for retry',
307+
log_prefix,
308+
user.id,
309+
)
310+
raise
311+
LOG.info(
312+
'%s: Course Update email for user %s sent via %s',
313+
log_prefix,
314+
user.id,
315+
'SES' if should_route_to_ses(msg) else 'default ACE channel',
316+
)
295317
_track_message_sent(site, user, msg)
296318

297319

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)