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:
parent
54b5c97ce9
commit
823d6c1b2d
|
@ -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)
|
|
@ -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'
|
||||||
|
|
|
@ -14,6 +14,7 @@ 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
|
||||||
|
|
||||||
|
@ -83,6 +84,8 @@ class SESBackend(BaseEmailBackend):
|
||||||
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)")
|
||||||
|
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)
|
signals.email_pre_send.send_robust(self.__class__, message=message)
|
||||||
|
|
||||||
message.to = utils.filter_recipiants(message.recipients())
|
# 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.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,22 +305,35 @@ 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
|
||||||
|
@ -278,3 +341,5 @@ class SESBackend(BaseEmailBackend):
|
||||||
finally:
|
finally:
|
||||||
if new_conn_created:
|
if new_conn_created:
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
@ -105,7 +115,6 @@ class ComplaintRecord(models.Model):
|
||||||
|
|
||||||
class SendRecord(models.Model):
|
class SendRecord(models.Model):
|
||||||
|
|
||||||
|
|
||||||
SEND = 'Send'
|
SEND = 'Send'
|
||||||
DELIVERED = 'Delivery'
|
DELIVERED = 'Delivery'
|
||||||
STATUS_CHOICE = (
|
STATUS_CHOICE = (
|
||||||
|
@ -122,6 +131,9 @@ class SendRecord(models.Model):
|
||||||
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)
|
||||||
|
|
||||||
|
@ -134,3 +146,9 @@ 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)
|
|
@ -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')
|
|
||||||
|
|
||||||
ACCESS_KEY = aws_ses_Settings.access_key or getattr(settings, 'AWS_SES_ACCESS_KEY_ID',getattr(settings, 'AWS_ACCESS_KEY_ID', None))
|
DEFAULT_FROM_EMAIL = ""
|
||||||
|
try:
|
||||||
|
site = Site.objects.get_current()
|
||||||
|
|
||||||
SECRET_KEY = aws_ses_Settings.secret_key or 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' % site.domain)
|
||||||
|
except Exception as e:
|
||||||
|
print("Site Doesn't Exist, please configure Django sites")
|
||||||
|
print("Error is: %s" % e)
|
||||||
|
|
||||||
AWS_SES_REGION_NAME = aws_ses_Settings.region_name or 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 or getattr(settings, 'AWS_SES_REGION_ENDPOINT','email.us-east-1.amazonaws.com')
|
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__))')
|
||||||
|
|
||||||
AWS_SES_REGION_ENDPOINT_URL = getattr(settings, 'AWS_SES_REGION_ENDPOINT_URL','https://' + AWS_SES_REGION_ENDPOINT)
|
UNSUBSCRIBE_TEMPLET = getattr(settings, 'UNSUBSCRIBE_TEMPLET', 'django_aws_ses/unsebscribe.html')
|
||||||
|
BASE_TEMPLET = getattr(settings, 'UNSUBSCRIBE_TEMPLET', 'django_aws_ses/base.html')
|
||||||
|
|
||||||
AWS_SES_AUTO_THROTTLE = getattr(settings, 'AWS_SES_AUTO_THROTTLE', 0.5)
|
AWS_SES_REGION_ENDPOINT_URL = getattr(settings, 'AWS_SES_REGION_ENDPOINT_URL','https://' + AWS_SES_REGION_ENDPOINT)
|
||||||
AWS_SES_RETURN_PATH = getattr(settings, 'AWS_SES_RETURN_PATH', None)
|
|
||||||
AWS_SES_CONFIGURATION_SET = getattr(settings, 'AWS_SES_CONFIGURATION_SET', None)
|
|
||||||
|
|
||||||
DKIM_DOMAIN = getattr(settings, "DKIM_DOMAIN", None)
|
AWS_SES_AUTO_THROTTLE = getattr(settings, 'AWS_SES_AUTO_THROTTLE', 0.5)
|
||||||
DKIM_PRIVATE_KEY = getattr(settings, 'DKIM_PRIVATE_KEY', None)
|
AWS_SES_RETURN_PATH = getattr(settings, 'AWS_SES_RETURN_PATH', None)
|
||||||
DKIM_SELECTOR = getattr(settings, 'DKIM_SELECTOR', 'ses')
|
|
||||||
DKIM_HEADERS = getattr(settings, 'DKIM_HEADERS', ('From', 'To', 'Cc', 'Subject'))
|
|
||||||
|
|
||||||
TIME_ZONE = settings.TIME_ZONE
|
# if AWS_SES_RETURN_PATH is None:
|
||||||
|
# AWS_SES_RETURN_PATH = "CF Doors <cdfbounced@zeeksgeeks.com>"
|
||||||
|
|
||||||
VERIFY_BOUNCE_SIGNATURES = getattr(settings, 'AWS_SES_VERIFY_BOUNCE_SIGNATURES', True)
|
AWS_SES_CONFIGURATION_SET = getattr(settings, 'AWS_SES_CONFIGURATION_SET', None)
|
||||||
|
|
||||||
# Domains that are trusted when retrieving the certificate
|
DKIM_DOMAIN = getattr(settings, "DKIM_DOMAIN", None)
|
||||||
# used to sign bounce messages.
|
DKIM_PRIVATE_KEY = getattr(settings, 'DKIM_PRIVATE_KEY', None)
|
||||||
BOUNCE_CERT_DOMAINS = getattr(settings, 'AWS_SNS_BOUNCE_CERT_TRUSTED_DOMAINS', (
|
DKIM_SELECTOR = getattr(settings, 'DKIM_SELECTOR', 'ses')
|
||||||
'amazonaws.com',
|
DKIM_HEADERS = getattr(settings, 'DKIM_HEADERS', ('From', 'To', 'Cc', 'Subject'))
|
||||||
'amazon.com',
|
|
||||||
))
|
|
||||||
|
|
||||||
SES_BOUNCE_LIMIT = getattr(settings,'BOUNCE_LIMT', 1)
|
TIME_ZONE = settings.TIME_ZONE
|
||||||
|
|
||||||
SES_BACKEND_DEBUG = getattr(settings,'SES_BACKEND_DEBUG', False)
|
VERIFY_BOUNCE_SIGNATURES = getattr(settings, 'AWS_SES_VERIFY_BOUNCE_SIGNATURES', True)
|
||||||
|
|
||||||
SES_BACKEND_DEBUG_LOGFILE_PATH = getattr(settings,'SES_BACKEND_DEBUG_LOGFILE_PATH', '%s/aws_ses.log' % BASE_DIR)
|
# 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_BACKEND_DEBUG_LOGFILE_FORMATTER = getattr(settings,'SES_BACKEND_DEBUG_LOGFILE_FORMATTER', '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
SES_BOUNCE_LIMIT = getattr(settings,'BOUNCE_LIMT', 1)
|
||||||
|
|
||||||
logger = logging.getLogger('django_aws_ses')
|
SES_BACKEND_DEBUG = getattr(settings,'SES_BACKEND_DEBUG', False)
|
||||||
# logger.setLevel(logging.WARNING)
|
|
||||||
if SES_BACKEND_DEBUG:
|
SES_BACKEND_DEBUG_LOGFILE_PATH = getattr(settings,'SES_BACKEND_DEBUG_LOGFILE_PATH', '%s/aws_ses.log' % BASE_DIR)
|
||||||
logger.setLevel(logging.INFO)
|
|
||||||
# create a file handler
|
SES_BACKEND_DEBUG_LOGFILE_FORMATTER = getattr(settings,'SES_BACKEND_DEBUG_LOGFILE_FORMATTER', '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||||
if SES_BACKEND_DEBUG_LOGFILE_PATH:
|
|
||||||
handler = logging.FileHandler(SES_BACKEND_DEBUG_LOGFILE_PATH)
|
logger = logging.getLogger('django_aws_ses')
|
||||||
handler.setLevel(logging.INFO)
|
# logger.setLevel(logging.WARNING)
|
||||||
# create a logging format
|
if SES_BACKEND_DEBUG:
|
||||||
formatter = logging.Formatter(SES_BACKEND_DEBUG_LOGFILE_FORMATTER)
|
logger.setLevel(logging.INFO)
|
||||||
handler.setFormatter(formatter)
|
# create a file handler
|
||||||
# add the handlers to the logger
|
if SES_BACKEND_DEBUG_LOGFILE_PATH:
|
||||||
logger.addHandler(handler)
|
handler = logging.FileHandler(SES_BACKEND_DEBUG_LOGFILE_PATH)
|
||||||
#logger.info('something we are logging')
|
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')
|
||||||
|
|
|
@ -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,9 +217,15 @@ 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)
|
||||||
|
@ -224,15 +236,19 @@ def filter_recipiants(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:
|
||||||
|
@ -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)
|
||||||
|
|
||||||
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)]))
|
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)
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue