diff --git a/lms/djangoapps/bulk_email/tasks.py b/lms/djangoapps/bulk_email/tasks.py index d6b8393f470e..4d86edcc103e 100644 --- a/lms/djangoapps/bulk_email/tasks.py +++ b/lms/djangoapps/bulk_email/tasks.py @@ -10,7 +10,7 @@ import time from collections import Counter from datetime import datetime -from smtplib import SMTPConnectError, SMTPDataError, SMTPException, SMTPServerDisconnected +from smtplib import SMTPConnectError, SMTPDataError, SMTPException, SMTPServerDisconnected, SMTPSenderRefused from time import sleep from boto.exception import AWSConnectionError @@ -87,12 +87,13 @@ # An example is if email is being sent too quickly, but may succeed if sent # more slowly. When caught by a task, it triggers an exponential backoff and retry. # Retries happen continuously until the email is sent. -# Note that the SMTPDataErrors here are only those within the 4xx range. +# Note that the (SMTPDataErrors and SMTPSenderRefused) here are only those within the 4xx range. # Those not in this range (i.e. in the 5xx range) are treated as hard failures # and thus like SINGLE_EMAIL_FAILURE_ERRORS. INFINITE_RETRY_ERRORS = ( SESMaxSendingRateExceededError, # Your account's requests/second limit has been exceeded. SMTPDataError, + SMTPSenderRefused, ) # Errors that are known to indicate an inability to send any more emails, @@ -565,11 +566,11 @@ def _send_course_email(entry_id, email_id, to_list, global_email_context, subtas f"{recipient_num}/{total_recipients}, Recipient UserId: {current_recipient['pk']}" ) message.send() - except SMTPDataError as exc: + except (SMTPDataError, SMTPSenderRefused) as exc: # According to SMTP spec, we'll retry error codes in the 4xx range. 5xx range indicates hard failure. total_recipients_failed += 1 log.exception( - f"BulkEmail ==> Status: Failed(SMTPDataError), Task: {parent_task_id}, SubTask: {task_id}, " + f"BulkEmail ==> Status: Failed({exc.smtp_error}), Task: {parent_task_id}, SubTask: {task_id}, " f"EmailId: {email_id}, Recipient num: {recipient_num}/{total_recipients}, Recipient UserId: " f"{current_recipient['pk']}" ) diff --git a/lms/djangoapps/bulk_email/tests/test_tasks.py b/lms/djangoapps/bulk_email/tests/test_tasks.py index 51f7b4161c88..e73db046aab8 100644 --- a/lms/djangoapps/bulk_email/tests/test_tasks.py +++ b/lms/djangoapps/bulk_email/tests/test_tasks.py @@ -11,7 +11,7 @@ from dateutil.relativedelta import relativedelta import json # lint-amnesty, pylint: disable=wrong-import-order from itertools import chain, cycle, repeat # lint-amnesty, pylint: disable=wrong-import-order -from smtplib import SMTPAuthenticationError, SMTPConnectError, SMTPDataError, SMTPServerDisconnected # lint-amnesty, pylint: disable=wrong-import-order +from smtplib import SMTPAuthenticationError, SMTPConnectError, SMTPDataError, SMTPServerDisconnected, SMTPSenderRefused # lint-amnesty, pylint: disable=wrong-import-order from unittest.mock import Mock, patch # lint-amnesty, pylint: disable=wrong-import-order from uuid import uuid4 # lint-amnesty, pylint: disable=wrong-import-order import pytest @@ -411,6 +411,11 @@ def _test_retry_after_unlimited_retry_error(self, exception): def test_retry_after_smtp_throttling_error(self): self._test_retry_after_unlimited_retry_error(SMTPDataError(455, "Throttling: Sending rate exceeded")) + def test_retry_after_smtp_sender_refused_error(self): + self._test_retry_after_unlimited_retry_error( + SMTPSenderRefused(421, "Throttling: Sending rate exceeded", self.instructor.email) + ) + def test_retry_after_ses_throttling_error(self): self._test_retry_after_unlimited_retry_error( SESMaxSendingRateExceededError(455, "Throttling: Sending rate exceeded")