bringing the repo up to date with the code

git-svn-id: https://vault.zeeksgeeks.com/svn/django_aws_ses/trunk@6 ed966f06-d3d6-432b-bc91-693151a5c6b4
This commit is contained in:
Raymond Jessop 2023-11-07 19:32:25 +00:00
parent 54b5c97ce9
commit 823d6c1b2d
7 changed files with 316 additions and 119 deletions

View File

@ -6,7 +6,8 @@ from .models import (
AwsSesUserAddon, AwsSesUserAddon,
ComplaintRecord, ComplaintRecord,
SendRecord, SendRecord,
UnknownRecord UnknownRecord,
BlackListedDomains,
) )
from . import settings from . import settings
@ -62,15 +63,22 @@ class ComplaintRecordAdmin(admin.ModelAdmin):
admin.site.register(ComplaintRecord, ComplaintRecordAdmin) admin.site.register(ComplaintRecord, ComplaintRecordAdmin)
class SendRecordAdmin(admin.ModelAdmin): class SendRecordAdmin(admin.ModelAdmin):
model = ComplaintRecord model = SendRecord
list_display = ('source', 'destination', 'subject', 'timestamp', 'status') list_display = ('source', 'destination', 'subject', 'timestamp', 'status')
list_filter = ('source', 'destination', 'subject', 'timestamp', 'status') list_filter = ('source', 'destination', 'subject', 'timestamp', 'status')
admin.site.register(SendRecord, SendRecordAdmin) admin.site.register(SendRecord, SendRecordAdmin)
class UnknownRecordAdmin(admin.ModelAdmin): class UnknownRecordAdmin(admin.ModelAdmin):
model = ComplaintRecord model = UnknownRecord
list_display = ('event_type', 'aws_data') list_display = ('event_type', 'aws_data')
list_filter = ('event_type', 'aws_data') list_filter = ('event_type', 'aws_data')
admin.site.register(UnknownRecord, UnknownRecordAdmin) admin.site.register(UnknownRecord, UnknownRecordAdmin)
class BlackListedDomainsAdmin(admin.ModelAdmin):
model = BlackListedDomains
list_display = ('domain', 'timestamp')
list_filter = ('domain', 'timestamp')
admin.site.register(BlackListedDomains, BlackListedDomainsAdmin)

View File

@ -2,5 +2,6 @@ from django.apps import AppConfig
class DjangoAwsSesBackendConfig(AppConfig): class DjangoAwsSesBackendConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'django_aws_ses' name = 'django_aws_ses'
verbose_name = 'Django AWS SES' verbose_name = 'Django AWS SES'

View File

@ -13,7 +13,8 @@ import sys
from . import settings from . import settings
from . import signals from . import signals
from . import utils from . import utils
from .models import BounceRecord from .models import BounceRecord
from saleor.core.utils import email_exclusion_filter
logger = settings.logger logger = settings.logger
@ -82,6 +83,8 @@ class SESBackend(BaseEmailBackend):
except Exception: except Exception:
if not self.fail_silently: if not self.fail_silently:
raise raise
return True
def close(self): def close(self):
"""Close any open HTTP connections to the API server. """Close any open HTTP connections to the API server.
@ -90,10 +93,13 @@ class SESBackend(BaseEmailBackend):
def send_messages(self, email_messages): def send_messages(self, email_messages):
"""Sends one or more EmailMessage objects and returns the number of """Sends one or more EmailMessage objects and returns the number of
email messages sent. email messages sent and a list of filtered emails.
""" """
logger.info("1 --- start of send_messages")
list_of_response = []
num_sent = 0
not_sent_list = []
sent_message = {"Sent":""}
calling_func = '' calling_func = ''
try: try:
fcount = 0 fcount = 0
@ -103,22 +109,31 @@ class SESBackend(BaseEmailBackend):
calling_func = sys._getframe(fcount).f_code.co_name calling_func = sys._getframe(fcount).f_code.co_name
except Exception as e: except Exception as e:
logger.info("fcount:%s, called from exception = %s" , (fcount, e)) logger.info("fcount:%s, called from exception = %s" % (fcount, e))
logger.info("called from %s" , (calling_func)) logger.info("called from %s current throttle:%s" % (calling_func, self._throttle))
logger.info("send_messages") logger.info("send_messages")
if not email_messages: if not email_messages:
return logger.info("no email messages returning")
list_of_response.append({'error':'no email messages returning'})
new_conn_created = self.open() new_conn_created = self.open()
if new_conn_created:
logger.info("created a new connection")
if not self.connection: if not self.connection:
# Failed silently # Failed silently
return logger.info("no connection returning")
list_of_response.append({'error':'no connection returning'})
logger.info("DEBUGING EMAILS --- list_of_response:%s" % (list_of_response))
logger.info("DEBUGING EMAILS --- return %s" % (num_sent))
return num_sent
num_sent = 0
source = settings.AWS_SES_RETURN_PATH source = settings.AWS_SES_RETURN_PATH
logger.info("email_messages: %s" % email_messages) logger.info("email_messages: %s" % email_messages)
@ -131,16 +146,43 @@ class SESBackend(BaseEmailBackend):
# If settings.AWS_SES_CONFIGURATION_SET is a callable, pass it the # If settings.AWS_SES_CONFIGURATION_SET is a callable, pass it the
# message object and dkim settings and expect it to return a string # message object and dkim settings and expect it to return a string
# containing the SES Configuration Set name. # containing the SES Configuration Set name.
message.aws_ses_response = {'error':'not sent yet'}
logger.info("Sending signal(email_pre_send)") logger.info("Sending signal(email_pre_send)")
signals.email_pre_send.send_robust(self.__class__, message=message) logger.info("message to: %s, cc: %s, bcc: %s" % (message.to, message.cc, message.bcc))
signals.email_pre_send.send_robust(self.__class__, message=message)
# for log in dir(message):
# logger.info(log)
pre_filter_recipients = message.recipients()
logger.info("message.recipients() = %s" % message.recipients())
marketing = message.extra_headers.get("marketing","False")
message.to = email_exclusion_filter(message.to,marketing)
message.cc = email_exclusion_filter(message.cc,marketing)
message.bcc = email_exclusion_filter(message.bcc,marketing)
message.to = utils.filter_recipiants(message.recipients()) message.to = utils.filter_recipiants(message.to)
message.cc = utils.filter_recipiants(message.cc)
message.bcc = utils.filter_recipiants(message.bcc)
logger.info("message.recipients() after email_pre_send: %s" % message.recipients()) logger.info("message.recipients() after email_pre_send: %s" % message.recipients())
if not message.recipients(): if not message.recipients():
logger.info("no recipients left after the filter") logger.info("no recipients left after the filter")
return False list_of_response.append({'error':'no recipients left after the filter'})
message.aws_ses_response = {'error':'no recipients left after the filter'}
sent_message = {"Not Sent":"No recipients left after filters"}
continue
#raise Exception('No emails left after filters!')
else:
for email in pre_filter_recipients:
if email not in message.recipients():
not_sent_list.append(email)
if (settings.AWS_SES_CONFIGURATION_SET if (settings.AWS_SES_CONFIGURATION_SET
and 'X-SES-CONFIGURATION-SET' not in message.extra_headers): and 'X-SES-CONFIGURATION-SET' not in message.extra_headers):
@ -164,13 +206,16 @@ class SESBackend(BaseEmailBackend):
# Set the setting to 0 or None to disable throttling. # Set the setting to 0 or None to disable throttling.
if self._throttle: if self._throttle:
global recent_send_times global recent_send_times
logger.info("inside if _throttle recent_send_times:%s" % recent_send_times)
now = datetime.now() now = datetime.now()
logger.info("inside if _throttle now:%s" % now)
# Get and cache the current SES max-per-second rate limit # Get and cache the current SES max-per-second rate limit
# returned by the SES API. # returned by the SES API.
logger.info("inside if _throttle calling get_rate_limit")
rate_limit = self.get_rate_limit() rate_limit = self.get_rate_limit()
logger.debug(u"send_messages.throttle rate_limit='{}'".format(rate_limit)) logger.info("send_messages.throttle rate_limit='{}'".format(rate_limit))
# Prune from recent_send_times anything more than a few seconds # Prune from recent_send_times anything more than a few seconds
# ago. Even though SES reports a maximum per-second, the way # ago. Even though SES reports a maximum per-second, the way
@ -208,7 +253,7 @@ class SESBackend(BaseEmailBackend):
logger.info("Try to send raw email") logger.info("Try to send raw email")
#logger.info('message.message().as_string() = %s' % message.message().as_string()) #logger.info('message.message().as_string() = %s' % message.message().as_string())
logger.info("source = %s" % source) logger.info("source = %s" % source)
logger.info("message.from_email = %s" % self.dkim_key) logger.info("message.from_email = %s" % message.from_email)
logger.info("message.recipients() = %s" % message.recipients()) logger.info("message.recipients() = %s" % message.recipients())
logger.info("dkim_key = %s" % self.dkim_key) logger.info("dkim_key = %s" % self.dkim_key)
@ -221,16 +266,21 @@ class SESBackend(BaseEmailBackend):
# todo attachments? # todo attachments?
RawMessage={'Data': dkim_sign(message.message().as_string(), RawMessage={'Data': dkim_sign(message.message().as_string(),
dkim_key=self.dkim_key, dkim_key=self.dkim_key,
dkim_domain=self.dkim_domain, dkim_domain=self.dkim_domain,
dkim_selector=self.dkim_selector, dkim_selector=self.dkim_selector,
dkim_headers=self.dkim_headers)} dkim_headers=self.dkim_headers)}
) )
list_of_response.append(response)
message.aws_ses_response = response
message.extra_headers['status'] = 200 message.extra_headers['status'] = 200
message.extra_headers['message_id'] = response['MessageId'] message.extra_headers['message_id'] = response['MessageId']
message.extra_headers['request_id'] = response['ResponseMetadata']['RequestId'] message.extra_headers['request_id'] = response['ResponseMetadata']['RequestId']
num_sent += 1 num_sent += 1
if 'X-SES-CONFIGURATION-SET' in message.extra_headers: if 'X-SES-CONFIGURATION-SET' in message.extra_headers:
logger.debug( logger.info(
u"send_messages.sent from='{}' recipients='{}' message_id='{}' request_id='{}' " u"send_messages.sent from='{}' recipients='{}' message_id='{}' request_id='{}' "
u"ses-configuration-set='{}'".format( u"ses-configuration-set='{}'".format(
message.from_email, message.from_email,
@ -240,7 +290,7 @@ class SESBackend(BaseEmailBackend):
message.extra_headers['X-SES-CONFIGURATION-SET'] message.extra_headers['X-SES-CONFIGURATION-SET']
)) ))
else: else:
logger.debug(u"send_messages.sent from='{}' recipients='{}' message_id='{}' request_id='{}'".format( logger.info(u"send_messages.sent from='{}' recipients='{}' message_id='{}' request_id='{}'".format(
message.from_email, message.from_email,
", ".join(message.recipients()), ", ".join(message.recipients()),
message.extra_headers['message_id'], message.extra_headers['message_id'],
@ -255,26 +305,41 @@ class SESBackend(BaseEmailBackend):
message.extra_headers[key] = getattr(err, key, None) message.extra_headers[key] = getattr(err, key, None)
if not self.fail_silently: if not self.fail_silently:
raise raise
if not_sent_list:
sent_message.update({"Sent":"%s" % not_sent_list})
logger.info("new_conn_created: %s" % new_conn_created)
if new_conn_created: if new_conn_created:
logger.info("closing new connection after send")
self.close() self.close()
return num_sent logger.info("DEBUGING EMAILS --- list_of_response:%s" % (list_of_response))
logger.info("DEBUGING EMAILS --- return %s, %s" % (num_sent, sent_message))
return num_sent, sent_message
def get_rate_limit(self): def get_rate_limit(self):
logger.info("getting rate limit")
if self._access_key_id in cached_rate_limits: if self._access_key_id in cached_rate_limits:
logger.info("returning cached rate limit %s" % cached_rate_limits[self._access_key_id])
return cached_rate_limits[self._access_key_id] return cached_rate_limits[self._access_key_id]
logger.info("creating AWS connection")
new_conn_created = self.open() new_conn_created = self.open()
if not self.connection: if not self.connection:
logger.info("AWS connection creation failed")
raise Exception( raise Exception(
"No connection is available to check current SES rate limit.") "No connection is available to check current SES rate limit.")
try: try:
quota_dict = self.connection.get_send_quota() quota_dict = self.connection.get_send_quota()
logger.info("AWS quota dict %s" % quota_dict)
max_per_second = quota_dict['MaxSendRate'] max_per_second = quota_dict['MaxSendRate']
ret = float(max_per_second) ret = float(max_per_second)
cached_rate_limits[self._access_key_id] = ret cached_rate_limits[self._access_key_id] = ret
return ret return ret
finally: finally:
if new_conn_created: if new_conn_created:
self.close() self.close()

View File

@ -1,5 +1,6 @@
import hashlib import hashlib
import logging import logging
import traceback
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models
@ -25,9 +26,15 @@ class AwsSesSettings(models.Model):
@receiver(post_save, sender=Site) @receiver(post_save, sender=Site)
def update_awsses_settings(sender, instance, created, **kwargs): def update_awsses_settings(sender, instance, created, **kwargs):
if created: try:
AwsSesSettings.objects.create(Site=instance) if created:
instance.AwsSesSettings.save() AwsSesSettings.objects.create(site=instance)
instance.awssessettings.save()
except Exception as e:
print("Exception saving site error:%s" % e)
track = traceback.format_exc()
print("Exception saving site track: %s" % (track))
class AwsSesUserAddon(models.Model): class AwsSesUserAddon(models.Model):
user = models.OneToOneField(User, related_name='aws_ses', on_delete=models.CASCADE) user = models.OneToOneField(User, related_name='aws_ses', on_delete=models.CASCADE)
@ -87,9 +94,12 @@ class BounceRecord(models.Model):
status = models.CharField(max_length=255, blank=True, null=True,) status = models.CharField(max_length=255, blank=True, null=True,)
action = models.CharField(max_length=255, blank=True, null=True,) action = models.CharField(max_length=255, blank=True, null=True,)
feedback_id = models.TextField(max_length=255, blank=True, null=True,) feedback_id = models.TextField(max_length=255, blank=True, null=True,)
diagnostic_code = models.CharField(max_length=255, blank=True, null=True,) diagnostic_code = models.CharField(max_length=2048, blank=True, null=True,)
cleared = models.BooleanField(default=False) cleared = models.BooleanField(default=False)
class Meta:
indexes = [models.Index(fields=["email"]),]
def __str__(self): def __str__(self):
return "email: %s, type: %s, sub_type: %s, status: %s, date: %s" % (self.email, self.bounce_type, self.bounce_sub_type, self.status, self.timestamp) return "email: %s, type: %s, sub_type: %s, status: %s, date: %s" % (self.email, self.bounce_type, self.bounce_sub_type, self.status, self.timestamp)
@ -104,7 +114,6 @@ class ComplaintRecord(models.Model):
return "email: %s, sub_type: %s, feedback_type: %s, date: %s" % (self.email, self.bounce_sub_type, self.feedback_type, self.timestamp) return "email: %s, sub_type: %s, feedback_type: %s, date: %s" % (self.email, self.bounce_sub_type, self.feedback_type, self.timestamp)
class SendRecord(models.Model): class SendRecord(models.Model):
SEND = 'Send' SEND = 'Send'
DELIVERED = 'Delivery' DELIVERED = 'Delivery'
@ -121,6 +130,9 @@ class SendRecord(models.Model):
aws_process_time = models.IntegerField() aws_process_time = models.IntegerField()
smtp_response = models.CharField(max_length=255, blank=True, null=True,) smtp_response = models.CharField(max_length=255, blank=True, null=True,)
status = models.CharField(max_length=255, blank=True, null=True,) status = models.CharField(max_length=255, blank=True, null=True,)
class Meta:
indexes = [models.Index(fields=["destination"]),]
def __str__(self): def __str__(self):
return "source: %s, destination: %s, subject: %s, date: %s" % (self.source, self.destination, self.subject, self.timestamp) return "source: %s, destination: %s, subject: %s, date: %s" % (self.source, self.destination, self.subject, self.timestamp)
@ -133,4 +145,10 @@ class UnknownRecord(models.Model):
def __str__(self): def __str__(self):
return "eventType: %s, timestamp: %s" % (self.eventType, self.timestamp) return "eventType: %s, timestamp: %s" % (self.eventType, self.timestamp)
class BlackListedDomains(models.Model):
domain = models.CharField(max_length=255, unique=True)
timestamp = models.DateTimeField(auto_now_add=True)
def __str__(self):
return "%s, blocked: %s" % (self.domain, self.timestamp)

View File

@ -1,83 +1,106 @@
from django.conf import settings from django.conf import settings
from django.contrib.sites.models import Site
import logging import logging
from .models import ( from .models import (
AwsSesSettings AwsSesSettings
) )
__all__ = ('ACCESS_KEY', 'SECRET_KEY', 'AWS_SES_REGION_NAME',
'AWS_SES_REGION_ENDPOINT', 'AWS_SES_AUTO_THROTTLE',
'AWS_SES_RETURN_PATH', 'DKIM_DOMAIN', 'DKIM_PRIVATE_KEY',
'DKIM_SELECTOR', 'DKIM_HEADERS', 'TIME_ZONE', 'BASE_DIR',
'BOUNCE_LIMIT','SES_BACKEND_DEBUG','SES_BACKEND_DEBUG_LOGFILE_PATH',
'SES_BACKEND_DEBUG_LOGFILE_FORMATTER')
aws_ses_Settings = None
try: try:
aws_ses_Settings, c = AwsSesSettings.objects.get_or_create(site_id=settings.SITE_ID) aws_ses_Settings, c = AwsSesSettings.objects.get_or_create(site_id=settings.SITE_ID)
except Exception as e: except Exception as e:
print("AwsSesSettings does not exist: error: %s" % e) print("AwsSesSettings does not exist: error: %s" % e)
else:
__all__ = ('ACCESS_KEY', 'SECRET_KEY', 'AWS_SES_REGION_NAME',
'AWS_SES_REGION_ENDPOINT', 'AWS_SES_AUTO_THROTTLE',
'AWS_SES_RETURN_PATH', 'DKIM_DOMAIN', 'DKIM_PRIVATE_KEY',
'DKIM_SELECTOR', 'DKIM_HEADERS', 'TIME_ZONE', 'BASE_DIR',
'BOUNCE_LIMIT','SES_BACKEND_DEBUG','SES_BACKEND_DEBUG_LOGFILE_PATH',
'SES_BACKEND_DEBUG_LOGFILE_FORMATTER')
BASE_DIR = getattr(settings, 'BASE_DIR', None) ACCESS_KEY = aws_ses_Settings.access_key if aws_ses_Settings else None
if ACCESS_KEY is None:
ACCESS_KEY = getattr(settings, 'AWS_SES_ACCESS_KEY_ID',getattr(settings, 'AWS_ACCESS_KEY_ID', None))
if not BASE_DIR: SECRET_KEY = aws_ses_Settings.secret_key if aws_ses_Settings else None
raise RuntimeError('No BASE_DIR defined in project settings, django_aws_ses requires BASE_DIR to be defined and pointed at your root directory. i.e. BASE_DIR = os.path.dirname(os.path.abspath(__file__))') if SECRET_KEY is None:
SECRET_KEY = getattr(settings, 'AWS_SES_SECRET_ACCESS_KEY',getattr(settings, 'AWS_SECRET_ACCESS_KEY', None))
DEFAULT_FROM_EMAIL = getattr(settings, 'DEFAULT_FROM_EMAIL', 'no_reply@%s' % aws_ses_Settings.site.domain) AWS_SES_REGION_NAME = aws_ses_Settings.region_name if aws_ses_Settings else None
if AWS_SES_REGION_NAME is None:
AWS_SES_REGION_NAME = getattr(settings, 'AWS_SES_REGION_NAME',getattr(settings, 'AWS_DEFAULT_REGION', 'us-east-1'))
HOME_URL = getattr(settings, 'HOME_URL', '') AWS_SES_REGION_ENDPOINT = aws_ses_Settings.region_endpoint if aws_ses_Settings else None
if AWS_SES_REGION_ENDPOINT is None:
AWS_SES_REGION_ENDPOINT = getattr(settings, 'AWS_SES_REGION_ENDPOINT','email.us-east-1.amazonaws.com')
UNSUBSCRIBE_TEMPLET = getattr(settings, 'UNSUBSCRIBE_TEMPLET', 'django_aws_ses/unsebscribe.html') BASE_DIR = getattr(settings, 'BASE_DIR', None)
BASE_TEMPLET = getattr(settings, 'UNSUBSCRIBE_TEMPLET', 'django_aws_ses/base.html')
DEFAULT_FROM_EMAIL = ""
try:
site = Site.objects.get_current()
DEFAULT_FROM_EMAIL = getattr(settings, 'DEFAULT_FROM_EMAIL', 'no_reply@%s' % site.domain)
except Exception as e:
print("Site Doesn't Exist, please configure Django sites")
print("Error is: %s" % e)
ACCESS_KEY = aws_ses_Settings.access_key or getattr(settings, 'AWS_SES_ACCESS_KEY_ID',getattr(settings, 'AWS_ACCESS_KEY_ID', None)) HOME_URL = getattr(settings, 'HOME_URL', '')
if not BASE_DIR:
raise RuntimeError('No BASE_DIR defined in project settings, django_aws_ses requires BASE_DIR to be defined and pointed at your root directory. i.e. BASE_DIR = os.path.dirname(os.path.abspath(__file__))')
UNSUBSCRIBE_TEMPLET = getattr(settings, 'UNSUBSCRIBE_TEMPLET', 'django_aws_ses/unsebscribe.html')
BASE_TEMPLET = getattr(settings, 'UNSUBSCRIBE_TEMPLET', 'django_aws_ses/base.html')
AWS_SES_REGION_ENDPOINT_URL = getattr(settings, 'AWS_SES_REGION_ENDPOINT_URL','https://' + AWS_SES_REGION_ENDPOINT)
SECRET_KEY = aws_ses_Settings.secret_key or getattr(settings, 'AWS_SES_SECRET_ACCESS_KEY',getattr(settings, 'AWS_SECRET_ACCESS_KEY', None)) AWS_SES_AUTO_THROTTLE = getattr(settings, 'AWS_SES_AUTO_THROTTLE', 0.5)
AWS_SES_RETURN_PATH = getattr(settings, 'AWS_SES_RETURN_PATH', None)
# if AWS_SES_RETURN_PATH is None:
# AWS_SES_RETURN_PATH = "CF Doors <cdfbounced@zeeksgeeks.com>"
AWS_SES_REGION_NAME = aws_ses_Settings.region_name or getattr(settings, 'AWS_SES_REGION_NAME',getattr(settings, 'AWS_DEFAULT_REGION', 'us-east-1')) AWS_SES_CONFIGURATION_SET = getattr(settings, 'AWS_SES_CONFIGURATION_SET', None)
DKIM_DOMAIN = getattr(settings, "DKIM_DOMAIN", None)
DKIM_PRIVATE_KEY = getattr(settings, 'DKIM_PRIVATE_KEY', None)
DKIM_SELECTOR = getattr(settings, 'DKIM_SELECTOR', 'ses')
DKIM_HEADERS = getattr(settings, 'DKIM_HEADERS', ('From', 'To', 'Cc', 'Subject'))
TIME_ZONE = settings.TIME_ZONE
VERIFY_BOUNCE_SIGNATURES = getattr(settings, 'AWS_SES_VERIFY_BOUNCE_SIGNATURES', True)
# Domains that are trusted when retrieving the certificate
# used to sign bounce messages.
BOUNCE_CERT_DOMAINS = getattr(settings, 'AWS_SNS_BOUNCE_CERT_TRUSTED_DOMAINS', (
'amazonaws.com',
'amazon.com',
))
SES_BOUNCE_LIMIT = getattr(settings,'BOUNCE_LIMT', 1)
AWS_SES_REGION_ENDPOINT = aws_ses_Settings.region_endpoint or getattr(settings, 'AWS_SES_REGION_ENDPOINT','email.us-east-1.amazonaws.com') SES_BACKEND_DEBUG = getattr(settings,'SES_BACKEND_DEBUG', False)
AWS_SES_REGION_ENDPOINT_URL = getattr(settings, 'AWS_SES_REGION_ENDPOINT_URL','https://' + AWS_SES_REGION_ENDPOINT) SES_BACKEND_DEBUG_LOGFILE_PATH = getattr(settings,'SES_BACKEND_DEBUG_LOGFILE_PATH', '%s/aws_ses.log' % BASE_DIR)
AWS_SES_AUTO_THROTTLE = getattr(settings, 'AWS_SES_AUTO_THROTTLE', 0.5) SES_BACKEND_DEBUG_LOGFILE_FORMATTER = getattr(settings,'SES_BACKEND_DEBUG_LOGFILE_FORMATTER', '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
AWS_SES_RETURN_PATH = getattr(settings, 'AWS_SES_RETURN_PATH', None)
AWS_SES_CONFIGURATION_SET = getattr(settings, 'AWS_SES_CONFIGURATION_SET', None) logger = logging.getLogger('django_aws_ses')
# logger.setLevel(logging.WARNING)
DKIM_DOMAIN = getattr(settings, "DKIM_DOMAIN", None) if SES_BACKEND_DEBUG:
DKIM_PRIVATE_KEY = getattr(settings, 'DKIM_PRIVATE_KEY', None) logger.setLevel(logging.INFO)
DKIM_SELECTOR = getattr(settings, 'DKIM_SELECTOR', 'ses') # create a file handler
DKIM_HEADERS = getattr(settings, 'DKIM_HEADERS', ('From', 'To', 'Cc', 'Subject')) if SES_BACKEND_DEBUG_LOGFILE_PATH:
handler = logging.FileHandler(SES_BACKEND_DEBUG_LOGFILE_PATH)
TIME_ZONE = settings.TIME_ZONE handler.setLevel(logging.INFO)
# create a logging format
VERIFY_BOUNCE_SIGNATURES = getattr(settings, 'AWS_SES_VERIFY_BOUNCE_SIGNATURES', True) formatter = logging.Formatter(SES_BACKEND_DEBUG_LOGFILE_FORMATTER)
handler.setFormatter(formatter)
# Domains that are trusted when retrieving the certificate # add the handlers to the logger
# used to sign bounce messages. logger.addHandler(handler)
BOUNCE_CERT_DOMAINS = getattr(settings, 'AWS_SNS_BOUNCE_CERT_TRUSTED_DOMAINS', ( #logger.info('something we are logging')
'amazonaws.com',
'amazon.com',
))
SES_BOUNCE_LIMIT = getattr(settings,'BOUNCE_LIMT', 1)
SES_BACKEND_DEBUG = getattr(settings,'SES_BACKEND_DEBUG', False)
SES_BACKEND_DEBUG_LOGFILE_PATH = getattr(settings,'SES_BACKEND_DEBUG_LOGFILE_PATH', '%s/aws_ses.log' % BASE_DIR)
SES_BACKEND_DEBUG_LOGFILE_FORMATTER = getattr(settings,'SES_BACKEND_DEBUG_LOGFILE_FORMATTER', '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger('django_aws_ses')
# logger.setLevel(logging.WARNING)
if SES_BACKEND_DEBUG:
logger.setLevel(logging.INFO)
# create a file handler
if SES_BACKEND_DEBUG_LOGFILE_PATH:
handler = logging.FileHandler(SES_BACKEND_DEBUG_LOGFILE_PATH)
handler.setLevel(logging.INFO)
# create a logging format
formatter = logging.Formatter(SES_BACKEND_DEBUG_LOGFILE_FORMATTER)
handler.setFormatter(formatter)
# add the handlers to the logger
logger.addHandler(handler)
#logger.info('something we are logging')

View File

@ -1,5 +1,9 @@
import base64 import base64
import logging import logging
import time
import re
import dns.resolver
from telnetlib import Telnet
from builtins import str as text from builtins import str as text
from builtins import bytes from builtins import bytes
from io import StringIO from io import StringIO
@ -19,7 +23,9 @@ from . import settings
from . import signals from . import signals
from .models import ( from .models import (
BounceRecord, BounceRecord,
ComplaintRecord ComplaintRecord,
BlackListedDomains,
SendRecord,
) )
logger = settings.logger logger = settings.logger
@ -211,30 +217,40 @@ def verify_bounce_message(msg):
@receiver(signals.email_pre_send) @receiver(signals.email_pre_send)
def receiver_email_pre_send(sender, message=None, **kwargs): def receiver_email_pre_send(sender, message=None, **kwargs):
logger.info("receiver_email_pre_send received signal") #logger.info("receiver_email_pre_send received signal")
pass
def filter_recipiants(recipiant_list): def filter_recipiants(recipiant_list):
logger.info("Starting filter_recipiants: %s" % recipiant_list)
if type(recipiant_list) != type([]):
logger.info("putting emails into a list")
recipiant_list = [recipiant_list]
if len(recipiant_list) > 0: if len(recipiant_list) > 0:
recipiant_list = filter_recipiants_with_unsubscribe(recipiant_list) recipiant_list = filter_recipiants_with_unsubscribe(recipiant_list)
if len(recipiant_list) > 0: if len(recipiant_list) > 0:
recipiant_list = filter_recipiants_with_complaint_records(recipiant_list) recipiant_list = filter_recipiants_with_complaint_records(recipiant_list)
if len(recipiant_list) > 0: if len(recipiant_list) > 0:
recipiant_list = filter_recipiants_with_bounce_records(recipiant_list) recipiant_list = filter_recipiants_with_bounce_records(recipiant_list)
if len(recipiant_list) > 0:
recipiant_list = filter_recipiants_with_validater_email_domain(recipiant_list)
logger.info("recipiant list after filter_recipiants: %s" % recipiant_list)
return recipiant_list return recipiant_list
def filter_recipiants_with_unsubscribe(recipiant_list): def filter_recipiants_with_unsubscribe(recipiant_list):
""" """
filter message recipiants so we don't send emails to any email that have Unsubscribude filter message recipiants so we don't send emails to any email that have Unsubscribude
""" """
logger.info("unsubscribe filter running") #logger.info("unsubscribe filter running")
logger.info("message.recipients() befor blacklist_emails filter: %s" % recipiant_list) #logger.info("message.recipients() befor blacklist_emails filter: %s" % recipiant_list)
blacklist_emails = list(set([record.email for record in User.objects.filter(aws_ses__unsubscribe=True)])) blacklist_emails = list(set([record.email for record in User.objects.filter(aws_ses__unsubscribe=True)]))
if blacklist_emails: if blacklist_emails:
return filter_recipiants_with_blacklist(recipiant_list, blacklist_emails) return filter_recipiants_with_blacklist(recipiant_list, blacklist_emails)
else: else:
@ -244,9 +260,9 @@ def filter_recipiants_with_complaint_records(recipiant_list):
""" """
filter message recipiants so we don't send emails to any email that have a ComplaintRecord filter message recipiants so we don't send emails to any email that have a ComplaintRecord
""" """
logger.info("complaint_records filter running") #logger.info("complaint_records filter running")
logger.info("message.recipients() befor blacklist_emails filter: %s" % recipiant_list) #logger.info("message.recipients() befor blacklist_emails filter: %s" % recipiant_list)
blacklist_emails = list(set([record.email for record in ComplaintRecord.objects.filter(email__isnull=False)])) blacklist_emails = list(set([record.email for record in ComplaintRecord.objects.filter(email__isnull=False)]))
if blacklist_emails: if blacklist_emails:
@ -259,21 +275,87 @@ def filter_recipiants_with_bounce_records(recipiant_list):
filter message recipiants so we dont send emails to any email that has more BounceRecord filter message recipiants so we dont send emails to any email that has more BounceRecord
the SES_BOUNCE_LIMIT the SES_BOUNCE_LIMIT
""" """
logger.info("bounce_records filter running") #logger.info("bounce_records filter running")
#logger.info("message.recipients() befor blacklist_emails filter: %s" % recipiant_list)
blacklist_emails = list(set([record.email for record in BounceRecord.objects.filter(email__isnull=False).annotate(total=Count('email')).filter(total__gte=settings.SES_BOUNCE_LIMIT)]))
logger.info("message.recipients() befor blacklist_emails filter: %s" % recipiant_list)
blacklist_emails = list(set([record.email for record in BounceRecord.objects.filter(email__isnull=False).annotate(total=Count('email')).filter(total__gte=settings.SES_BOUNCE_LIMIT)]))
if blacklist_emails: if blacklist_emails:
return filter_recipiants_with_blacklist(recipiant_list, blacklist_emails) return filter_recipiants_with_blacklist(recipiant_list, blacklist_emails)
else: else:
return recipiant_list return recipiant_list
def filter_recipiants_with_blacklist(recipiant_list, blacklist_emails): def filter_recipiants_with_blacklist(recipiant_list, blacklist_emails):
""" """
filter message recipiants with a list of email you dont want to email filter message recipiants with a list of emails you dont want to email
""" """
logger.info("blacklist_emails filter list: %s" % blacklist_emails) filtered_recipiant_list = [email for email in recipiant_list if email not in blacklist_emails]
filtered_recipiant_list = [email for email in recipiant_list if email not in blacklist_emails]
logger.info("filtered_recipiant_list: %s" % filtered_recipiant_list)
return filtered_recipiant_list return filtered_recipiant_list
def filter_recipiants_with_validater_email_domain(recipiant_list):
debug_flag = True
sent_list = [e.destination for e in SendRecord.objects.filter(destination__in=recipiant_list).distinct("destination")]
test_list = [e for e in recipiant_list if e not in sent_list]
for e in test_list:
if not validater_email_domain(e):
recipiant_list.remove(e)
return recipiant_list
def validater_email_domain(email):
if email.find("@") < 1:
return False
domain = email.split("@")[-1]
if BlackListedDomains.objects.filter(domain=domain).count() > 0:
return False
records = []
try:
records = dns.resolver.query(domain, 'MX')
except dns.resolver.NoNameservers as e:
return False
except dns.resolver.NoAnswer as e:
return False
except dns.resolver.NXDOMAIN as e:
return False
except dns.resolver.LifetimeTimeout as e:
return False
if len(records) < 1:
return False
return True
def emailIsValid(email):
resp = False
regex = re.compile(r'([A-Za-z0-9]+[.\-_])*[A-Za-z0-9]+@[A-Za-z0-9-]+(.[A-Z|a-z]{2,})+')
if re.fullmatch(regex, email):
resp = True
return resp
def validate_email(email):
if not emailIsValid(email):
return False
if BounceRecord.objects.filter(email=email).count() >= settings.SES_BOUNCE_LIMIT:
return False
if ComplaintRecord.objects.filter(email=email).count() > 0:
return False
return validater_email_domain(email)

View File

@ -249,6 +249,13 @@ def handle_bounce(request):
bounce_type = bounce_obj.get('bounceType') bounce_type = bounce_obj.get('bounceType')
bounce_subtype = bounce_obj.get('bounceSubType') bounce_subtype = bounce_obj.get('bounceSubType')
bounce_recipients = bounce_obj.get('bouncedRecipients', []) bounce_recipients = bounce_obj.get('bouncedRecipients', [])
logger.info(
u'Received bounce notification: feedbackId: %s, bounceType: %s, bounceSubType: %s',
feedback_id, bounce_type, bounce_subtype,
extra={
'notification': notification,
},
)
# create a BounceRecord so we can keep from sending to bad emails. # create a BounceRecord so we can keep from sending to bad emails.
logger.info('create records') logger.info('create records')
@ -265,13 +272,6 @@ def handle_bounce(request):
reporting_mta = bounce_obj.get('reportingMTA', None), reporting_mta = bounce_obj.get('reportingMTA', None),
) )
logger.info(
u'Received bounce notification: feedbackId: %s, bounceType: %s, bounceSubType: %s',
feedback_id, bounce_type, bounce_subtype,
extra={
'notification': notification,
},
)
signals.bounce_received.send( signals.bounce_received.send(
sender=handle_bounce, sender=handle_bounce,