diff --git a/api/app/settings/common.py b/api/app/settings/common.py index b94308409a36..d105a006e1a2 100644 --- a/api/app/settings/common.py +++ b/api/app/settings/common.py @@ -1335,6 +1335,11 @@ ENABLE_API_USAGE_ALERTING = env.bool("ENABLE_API_USAGE_ALERTING", default=False) +API_USAGE_ALERT_CC_RECIPIENT_LIST = env.list( + "API_USAGE_ALERT_CC_RECIPIENT_LIST", + subcast=str, + default=[], +) # See DomainAuthMethods in flagsmith-auth-controller repository with auth_controller.models module GLOBAL_DOMAIN_AUTH_METHODS = env.dict( diff --git a/api/organisations/task_helpers.py b/api/organisations/task_helpers.py index ee00b648ec94..d404a5105cb9 100644 --- a/api/organisations/task_helpers.py +++ b/api/organisations/task_helpers.py @@ -3,7 +3,7 @@ from dateutil.relativedelta import relativedelta from django.conf import settings -from django.core.mail import send_mail +from django.core.mail import EmailMultiAlternatives from django.template.loader import render_to_string from django.utils import timezone @@ -39,14 +39,15 @@ def send_api_flags_blocked_notification(organisation: Organisation) -> None: message = "organisations/api_flags_blocked_notification.txt" html_message = "organisations/api_flags_blocked_notification.html" - send_mail( + msg = EmailMultiAlternatives( subject="Flagsmith API use has been blocked due to overuse", - message=render_to_string(message, context), + body=render_to_string(message, context), from_email=settings.DEFAULT_FROM_EMAIL, - recipient_list=list(recipient_list.values_list("email", flat=True)), - html_message=render_to_string(html_message, context), - fail_silently=True, + to=list(recipient_list.values_list("email", flat=True)), + cc=settings.API_USAGE_ALERT_CC_RECIPIENT_LIST, ) + msg.attach_alternative(render_to_string(html_message, context), "text/html") + msg.send(fail_silently=True) def _send_api_usage_notification( @@ -83,14 +84,15 @@ def _send_api_usage_notification( "usage_url": f"{url}/organisation/{organisation.id}/usage", } - send_mail( + msg = EmailMultiAlternatives( subject=f"Flagsmith API use has reached {matched_threshold}%", - message=render_to_string(message, context), + body=render_to_string(message, context), from_email=settings.DEFAULT_FROM_EMAIL, - recipient_list=list(recipient_list.values_list("email", flat=True)), - html_message=render_to_string(html_message, context), - fail_silently=True, + to=list(recipient_list.values_list("email", flat=True)), + cc=settings.API_USAGE_ALERT_CC_RECIPIENT_LIST, ) + msg.attach_alternative(render_to_string(html_message, context), "text/html") + msg.send(fail_silently=True) OrganisationAPIUsageNotification.objects.create( organisation=organisation, diff --git a/api/tests/unit/organisations/test_unit_organisations_tasks.py b/api/tests/unit/organisations/test_unit_organisations_tasks.py index 8916f70dcb83..1b2bf9d9102f 100644 --- a/api/tests/unit/organisations/test_unit_organisations_tasks.py +++ b/api/tests/unit/organisations/test_unit_organisations_tasks.py @@ -2155,3 +2155,90 @@ def test_update_organisation_subscription_information_cache__called__calls_updat SubscriptionCacheEntity.CHARGEBEE, SubscriptionCacheEntity.API_USAGE ) ] + + +@pytest.mark.freeze_time("2023-01-19T09:09:47.325132+00:00") +def test_handle_api_usage_notifications__cc_recipient_list_set__includes_cc_in_email( + mocker: MockerFixture, + organisation: Organisation, + mailoutbox: list[EmailMultiAlternatives], + enable_features: EnableFeaturesFixture, + settings: SettingsWrapper, +) -> None: + # Given + cs_email = "cs@flagsmith.com" + settings.API_USAGE_ALERT_CC_RECIPIENT_LIST = [cs_email] + + now = timezone.now() + organisation.subscription.plan = SCALE_UP + organisation.subscription.subscription_id = "fancy_id" + organisation.subscription.save() + OrganisationSubscriptionInformationCache.objects.create( + organisation=organisation, + allowed_30d_api_calls=100, + current_billing_term_starts_at=now - timedelta(days=45), + current_billing_term_ends_at=now + timedelta(days=320), + api_calls_30d=90, + ) + mock_api_usage = mocker.patch( + "organisations.task_helpers.get_current_api_usage", + ) + mock_api_usage.return_value = 91 + enable_features("api_usage_alerting") + + # When + handle_api_usage_notifications() + + # Then + assert len(mailoutbox) == 1 + assert mailoutbox[0].cc == [cs_email] + + +@pytest.mark.freeze_time("2023-01-19T09:09:47.325132+00:00") +def test_restrict_use_due_to_api_limit_grace_period_over__cc_recipient_list_set__includes_cc_in_blocked_email( + mocker: MockerFixture, + organisation: Organisation, + freezer: FrozenDateTimeFactory, + mailoutbox: list[EmailMultiAlternatives], + enable_features: EnableFeaturesFixture, + settings: SettingsWrapper, +) -> None: + # Given + cs_email = "cs@flagsmith.com" + settings.API_USAGE_ALERT_CC_RECIPIENT_LIST = [cs_email] + + enable_features( + "api_limiting_stop_serving_flags", + "api_limiting_block_access_to_admin", + ) + + now = timezone.now() + OrganisationSubscriptionInformationCache.objects.create( + organisation=organisation, + allowed_seats=10, + allowed_projects=3, + allowed_30d_api_calls=10_000, + ) + organisation.subscription.plan = FREE_PLAN_ID + organisation.subscription.save() + + mocker.patch( + "organisations.tasks.get_current_api_usage", + return_value=12_005, + ) + + OrganisationAPIUsageNotification.objects.create( + notified_at=now, + organisation=organisation, + percent_usage=120, + ) + + now = now + timedelta(days=API_USAGE_GRACE_PERIOD + 1) + freezer.move_to(now) + + # When + restrict_use_due_to_api_limit_grace_period_over() + + # Then + assert len(mailoutbox) == 1 + assert mailoutbox[0].cc == [cs_email] diff --git a/infrastructure/aws/production/ecs-task-definition-task-processor.json b/infrastructure/aws/production/ecs-task-definition-task-processor.json index 1196200f540c..761a711580b5 100644 --- a/infrastructure/aws/production/ecs-task-definition-task-processor.json +++ b/infrastructure/aws/production/ecs-task-definition-task-processor.json @@ -184,6 +184,10 @@ { "name": "SEGMENT_RULES_CONDITIONS_EXPLICIT_ORDERING_ENABLED", "value": "True" + }, + { + "name": "API_USAGE_ALERT_CC_RECIPIENT_LIST", + "value": "customersuccess@flagsmith.com" } ], "secrets": [