Skip to content
Merged
Show file tree
Hide file tree
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
9 changes: 5 additions & 4 deletions lms/djangoapps/bulk_email/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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']}"
)
Expand Down
7 changes: 6 additions & 1 deletion lms/djangoapps/bulk_email/tests/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down