Commit ba6477ab by Brian Wilson

Handle all exceptions returned by django-ses.

parent ac891b38
...@@ -12,11 +12,15 @@ from time import sleep ...@@ -12,11 +12,15 @@ from time import sleep
from dogapi import dog_stats_api from dogapi import dog_stats_api
from smtplib import SMTPServerDisconnected, SMTPDataError, SMTPConnectError, SMTPException from smtplib import SMTPServerDisconnected, SMTPDataError, SMTPConnectError, SMTPException
from boto.ses.exceptions import ( from boto.ses.exceptions import (
SESAddressNotVerifiedError,
SESIdentityNotVerifiedError,
SESDomainNotConfirmedError,
SESAddressBlacklistedError,
SESDailyQuotaExceededError, SESDailyQuotaExceededError,
SESMaxSendingRateExceededError, SESMaxSendingRateExceededError,
SESAddressBlacklistedError, SESDomainEndsWithDotError,
SESIllegalAddressError,
SESLocalAddressCharacterError, SESLocalAddressCharacterError,
SESIllegalAddressError,
) )
from boto.exception import AWSConnectionError from boto.exception import AWSConnectionError
...@@ -50,11 +54,20 @@ log = get_task_logger(__name__) ...@@ -50,11 +54,20 @@ log = get_task_logger(__name__)
# Errors that an individual email is failing to be sent, and should just # Errors that an individual email is failing to be sent, and should just
# be treated as a fail. # be treated as a fail.
SINGLE_EMAIL_FAILURE_ERRORS = (SESAddressBlacklistedError, SESIllegalAddressError, SESLocalAddressCharacterError) SINGLE_EMAIL_FAILURE_ERRORS = (
SESAddressBlacklistedError, # Recipient's email address has been temporarily blacklisted.
SESDomainEndsWithDotError, # Recipient's email address' domain ends with a period/dot.
SESIllegalAddressError, # Raised when an illegal address is encountered.
SESLocalAddressCharacterError, # An address contained a control or whitespace character.
)
# Exceptions that, if caught, should cause the task to be re-tried. # Exceptions that, if caught, should cause the task to be re-tried.
# These errors will be caught a limited number of times before the task fails. # These errors will be caught a limited number of times before the task fails.
LIMITED_RETRY_ERRORS = (SMTPConnectError, SMTPServerDisconnected, AWSConnectionError) LIMITED_RETRY_ERRORS = (
SMTPConnectError,
SMTPServerDisconnected,
AWSConnectionError,
)
# Errors that indicate that a mailing task should be retried without limit. # Errors that indicate that a mailing task should be retried without limit.
# An example is if email is being sent too quickly, but may succeed if sent # An example is if email is being sent too quickly, but may succeed if sent
...@@ -63,12 +76,21 @@ LIMITED_RETRY_ERRORS = (SMTPConnectError, SMTPServerDisconnected, AWSConnectionE ...@@ -63,12 +76,21 @@ LIMITED_RETRY_ERRORS = (SMTPConnectError, SMTPServerDisconnected, AWSConnectionE
# Note that the SMTPDataErrors here are only those within the 4xx range. # Note that the SMTPDataErrors here are only those within the 4xx range.
# Those not in this range (i.e. in the 5xx range) are treated as hard failures # Those not in this range (i.e. in the 5xx range) are treated as hard failures
# and thus like SINGLE_EMAIL_FAILURE_ERRORS. # and thus like SINGLE_EMAIL_FAILURE_ERRORS.
INFINITE_RETRY_ERRORS = (SESMaxSendingRateExceededError, SMTPDataError) INFINITE_RETRY_ERRORS = (
SESMaxSendingRateExceededError, # Your account's requests/second limit has been exceeded.
SMTPDataError,
)
# Errors that are known to indicate an inability to send any more emails, # Errors that are known to indicate an inability to send any more emails,
# and should therefore not be retried. For example, exceeding a quota for emails. # and should therefore not be retried. For example, exceeding a quota for emails.
# Also, any SMTP errors that are not explicitly enumerated above. # Also, any SMTP errors that are not explicitly enumerated above.
BULK_EMAIL_FAILURE_ERRORS = (SESDailyQuotaExceededError, SMTPException) BULK_EMAIL_FAILURE_ERRORS = (
SESAddressNotVerifiedError, # Raised when a "Reply-To" address has not been validated in SES yet.
SESIdentityNotVerifiedError, # Raised when an identity has not been verified in SES yet.
SESDomainNotConfirmedError, # Raised when domain ownership is not confirmed for DKIM.
SESDailyQuotaExceededError, # 24-hour allotment of outbound email has been exceeded.
SMTPException,
)
def _get_recipient_queryset(user_id, to_option, course_id, course_location): def _get_recipient_queryset(user_id, to_option, course_id, course_location):
......
...@@ -11,11 +11,15 @@ from itertools import cycle, chain, repeat ...@@ -11,11 +11,15 @@ from itertools import cycle, chain, repeat
from mock import patch, Mock from mock import patch, Mock
from smtplib import SMTPServerDisconnected, SMTPDataError, SMTPConnectError, SMTPAuthenticationError from smtplib import SMTPServerDisconnected, SMTPDataError, SMTPConnectError, SMTPAuthenticationError
from boto.ses.exceptions import ( from boto.ses.exceptions import (
SESAddressNotVerifiedError,
SESIdentityNotVerifiedError,
SESDomainNotConfirmedError,
SESAddressBlacklistedError,
SESDailyQuotaExceededError, SESDailyQuotaExceededError,
SESMaxSendingRateExceededError, SESMaxSendingRateExceededError,
SESAddressBlacklistedError, SESDomainEndsWithDotError,
SESIllegalAddressError,
SESLocalAddressCharacterError, SESLocalAddressCharacterError,
SESIllegalAddressError,
) )
from boto.exception import AWSConnectionError from boto.exception import AWSConnectionError
...@@ -271,6 +275,10 @@ class TestBulkEmailInstructorTask(InstructorTaskCourseTestCase): ...@@ -271,6 +275,10 @@ class TestBulkEmailInstructorTask(InstructorTaskCourseTestCase):
# Test that celery handles permanent SMTPDataErrors by failing and not retrying. # Test that celery handles permanent SMTPDataErrors by failing and not retrying.
self._test_email_address_failures(SESLocalAddressCharacterError(554, "Email address contains a bad character")) self._test_email_address_failures(SESLocalAddressCharacterError(554, "Email address contains a bad character"))
def test_ses_domain_ends_with_dot(self):
# Test that celery handles permanent SMTPDataErrors by failing and not retrying.
self._test_email_address_failures(SESDomainEndsWithDotError(554, "Email address ends with a dot"))
def _test_retry_after_limited_retry_error(self, exception): def _test_retry_after_limited_retry_error(self, exception):
"""Test that celery handles connection failures by retrying.""" """Test that celery handles connection failures by retrying."""
# If we want the batch to succeed, we need to send fewer emails # If we want the batch to succeed, we need to send fewer emails
...@@ -396,3 +404,12 @@ class TestBulkEmailInstructorTask(InstructorTaskCourseTestCase): ...@@ -396,3 +404,12 @@ class TestBulkEmailInstructorTask(InstructorTaskCourseTestCase):
def test_failure_on_ses_quota_exceeded(self): def test_failure_on_ses_quota_exceeded(self):
self._test_immediate_failure(SESDailyQuotaExceededError(403, "You're done for the day!")) self._test_immediate_failure(SESDailyQuotaExceededError(403, "You're done for the day!"))
def test_failure_on_ses_address_not_verified(self):
self._test_immediate_failure(SESAddressNotVerifiedError(403, "Who *are* you?"))
def test_failure_on_ses_identity_not_verified(self):
self._test_immediate_failure(SESIdentityNotVerifiedError(403, "May I please see an ID!"))
def test_failure_on_ses_domain_not_confirmed(self):
self._test_immediate_failure(SESDomainNotConfirmedError(403, "You're out of bounds!"))
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment