fix: make bulk_email send email task handles a retryable exception (#29080)

* fix: make bulk_email send email task handles a retryable exception
According to bulk_email and SMTP doc, when an error code is between
(400-499) it can be retried after sometime, this commit adds a new
type of exception called SMTPSenderRefused, so it can be retried
**if** it's code fails under above constraint.

* test: add test to handle SMTPSenderResfused

Co-authored-by: Justin Hynes <jhynes@edx.org>
This commit is contained in:
Ghassan Maslamani
2022-03-30 21:45:08 +03:00
committed by GitHub
parent f2a44ada41
commit e1d9746d61
2 changed files with 11 additions and 5 deletions

View File

@@ -10,7 +10,7 @@ import re
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 @@ LIMITED_RETRY_ERRORS = (
# 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']}"
)

View File

@@ -11,7 +11,7 @@ from datetime import datetime
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 @@ class TestBulkEmailInstructorTask(InstructorTaskCourseTestCase):
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")