class AmazonTransport(object): # pragma: no cover __slots__ = ('ephemeral', 'id', 'key', 'host', 'connection') def __init__(self, config): self.id = config.get('id') self.key = config.get('key') self.host = config.get('host', "email.us-east-1.amazonaws.com") self.connection = None def startup(self): self.connection = SESConnection(aws_access_key_id=self.id, aws_secret_access_key=self.key, host=self.host) def deliver(self, message): try: response = self.connection.send_raw_email( source=message.author.encode(), destinations=message.recipients.encode(), raw_message=str(message)) return (response['SendRawEmailResponse']['SendRawEmailResult'] ['MessageId'], response['SendRawEmailResponse'] ['ResponseMetadata']['RequestId']) except SESConnection.ResponseError: raise # TODO: Raise appropriate internal exception. # ['status', 'reason', 'body', 'request_id', 'error_code', 'error_message'] def shutdown(self): if self.connection: self.connection.close() self.connection = None
def send_mail(msg): global access_key global secret_key client = SESConnection( aws_access_key_id=access_key, aws_secret_access_key=secret_key) senders = msg["From"].split(",") recipients = msg["To"].split(',') for sender in senders: client.send_raw_email(msg.as_string(), sender, recipients) client.close()
class AmazonTransport(object): # pragma: no cover __slots__ = ('ephemeral', 'id', 'key', 'host', 'connection') def __init__(self, config): self.id = config.get('id') self.key = config.get('key') self.host = config.get('host', "email.us-east-1.amazonaws.com") self.connection = None def startup(self): self.connection = SESConnection( aws_access_key_id = self.id, aws_secret_access_key = self.key, host = self.host ) def deliver(self, message): try: response = self.connection.send_raw_email( source = message.author.encode(), destinations = message.recipients.encode(), raw_message = str(message) ) return ( response['SendRawEmailResponse']['SendRawEmailResult']['MessageId'], response['SendRawEmailResponse']['ResponseMetadata']['RequestId'] ) except SESConnection.ResponseError: raise # TODO: Raise appropriate internal exception. # ['status', 'reason', 'body', 'request_id', 'error_code', 'error_message'] def shutdown(self): if self.connection: self.connection.close() self.connection = None
class SESBackend(BaseEmailBackend): """A Django Email backend that uses Amazon's Simple Email Service. """ def __init__(self, fail_silently=False, aws_access_key=None, aws_secret_key=None, aws_region_name=None, aws_region_endpoint=None, aws_auto_throttle=None, dkim_domain=None, dkim_key=None, dkim_selector=None, dkim_headers=None, proxy=None, proxy_port=None, proxy_user=None, proxy_pass=None, **kwargs): super(SESBackend, self).__init__(fail_silently=fail_silently, **kwargs) self._access_key_id = aws_access_key or settings.ACCESS_KEY self._access_key = aws_secret_key or settings.SECRET_KEY self._region = RegionInfo(name=aws_region_name or settings.AWS_SES_REGION_NAME, endpoint=aws_region_endpoint or settings.AWS_SES_REGION_ENDPOINT) self._throttle = aws_auto_throttle or settings.AWS_SES_AUTO_THROTTLE self._proxy = proxy or settings.AWS_SES_PROXY self._proxy_port = proxy_port or settings.AWS_SES_PROXY_PORT self._proxy_user = proxy_user or settings.AWS_SES_PROXY_USER self._proxy_pass = proxy_pass or settings.AWS_SES_PROXY_PASS self.dkim_domain = dkim_domain or settings.DKIM_DOMAIN self.dkim_key = dkim_key or settings.DKIM_PRIVATE_KEY self.dkim_selector = dkim_selector or settings.DKIM_SELECTOR self.dkim_headers = dkim_headers or settings.DKIM_HEADERS self.connection = None def open(self): """Create a connection to the AWS API server. This can be reused for sending multiple emails. """ if self.connection: return False try: self.connection = SESConnection( aws_access_key_id=self._access_key_id, aws_secret_access_key=self._access_key, region=self._region, proxy=self._proxy, proxy_port=self._proxy_port, proxy_user=self._proxy_user, proxy_pass=self._proxy_pass, ) except Exception: if not self.fail_silently: raise def close(self): """Close any open HTTP connections to the API server. """ try: self.connection.close() self.connection = None except Exception: if not self.fail_silently: raise def send_messages(self, email_messages): """Sends one or more EmailMessage objects and returns the number of email messages sent. """ if not email_messages: return new_conn_created = self.open() if not self.connection: # Failed silently return num_sent = 0 source = settings.AWS_SES_RETURN_PATH for message in email_messages: # SES Configuration sets. If the AWS_SES_CONFIGURATION_SET setting # is not None, append the appropriate header to the message so that # SES knows which configuration set it belongs to. # # If settings.AWS_SES_CONFIGURATION_SET is a callable, pass it the # message object and dkim settings and expect it to return a string # containing the SES Configuration Set name. if (settings.AWS_SES_CONFIGURATION_SET and 'X-SES-CONFIGURATION-SET' not in message.extra_headers): if callable(settings.AWS_SES_CONFIGURATION_SET): message.extra_headers[ 'X-SES-CONFIGURATION-SET'] = settings.AWS_SES_CONFIGURATION_SET( message, dkim_domain=self.dkim_domain, dkim_key=self.dkim_key, dkim_selector=self.dkim_selector, dkim_headers=self.dkim_headers) else: message.extra_headers[ 'X-SES-CONFIGURATION-SET'] = settings.AWS_SES_CONFIGURATION_SET # Automatic throttling. Assumes that this is the only SES client # currently operating. The AWS_SES_AUTO_THROTTLE setting is a # factor to apply to the rate limit, with a default of 0.5 to stay # well below the actual SES throttle. # Set the setting to 0 or None to disable throttling. if self._throttle: global recent_send_times now = datetime.now() # Get and cache the current SES max-per-second rate limit # returned by the SES API. rate_limit = self.get_rate_limit() logger.debug(u"send_messages.throttle rate_limit='{}'".format( rate_limit)) # Prune from recent_send_times anything more than a few seconds # ago. Even though SES reports a maximum per-second, the way # they enforce the limit may not be on a one-second window. # To be safe, we use a two-second window (but allow 2 times the # rate limit) and then also have a default rate limit factor of # 0.5 so that we really limit the one-second amount in two # seconds. window = 2.0 # seconds window_start = now - timedelta(seconds=window) new_send_times = [] for time in recent_send_times: if time > window_start: new_send_times.append(time) recent_send_times = new_send_times # If the number of recent send times in the last 1/_throttle # seconds exceeds the rate limit, add a delay. # Since I'm not sure how Amazon determines at exactly what # point to throttle, better be safe than sorry and let in, say, # half of the allowed rate. if len(new_send_times) > rate_limit * window * self._throttle: # Sleep the remainder of the window period. delta = now - new_send_times[0] total_seconds = (delta.microseconds + (delta.seconds + delta.days * 24 * 3600) * 10**6) / 10**6 delay = window - total_seconds if delay > 0: sleep(delay) recent_send_times.append(now) # end of throttling try: response = self.connection.send_raw_email( source=source or message.from_email, destinations=message.recipients(), raw_message=dkim_sign(message.message().as_string(), dkim_key=self.dkim_key, dkim_domain=self.dkim_domain, dkim_selector=self.dkim_selector, dkim_headers=self.dkim_headers)) message.extra_headers['status'] = 200 message.extra_headers['message_id'] = response[ 'SendRawEmailResponse']['SendRawEmailResult']['MessageId'] message.extra_headers['request_id'] = response[ 'SendRawEmailResponse']['ResponseMetadata']['RequestId'] num_sent += 1 if 'X-SES-CONFIGURATION-SET' in message.extra_headers: logger.debug( u"send_messages.sent from='{}' recipients='{}' message_id='{}' request_id='{}' ses-configuration-set='{}'" .format( message.from_email, ", ".join(message.recipients()), message.extra_headers['message_id'], message.extra_headers['request_id'], message.extra_headers['X-SES-CONFIGURATION-SET'])) else: logger.debug( u"send_messages.sent from='{}' recipients='{}' message_id='{}' request_id='{}'" .format(message.from_email, ", ".join(message.recipients()), message.extra_headers['message_id'], message.extra_headers['request_id'])) except SESConnection.ResponseError as err: # Store failure information so to post process it if required error_keys = [ 'status', 'reason', 'body', 'request_id', 'error_code', 'error_message' ] for key in error_keys: message.extra_headers[key] = getattr(err, key, None) if not self.fail_silently: raise if new_conn_created: self.close() return num_sent def get_rate_limit(self): if self._access_key_id in cached_rate_limits: return cached_rate_limits[self._access_key_id] new_conn_created = self.open() if not self.connection: raise Exception( "No connection is available to check current SES rate limit.") try: quota_dict = self.connection.get_send_quota() max_per_second = quota_dict['GetSendQuotaResponse'][ 'GetSendQuotaResult']['MaxSendRate'] ret = float(max_per_second) cached_rate_limits[self._access_key_id] = ret return ret finally: if new_conn_created: self.close()
class SESBackend(BaseEmailBackend): """A Django Email backend that uses Amazon's Simple Email Service. """ def __init__(self, fail_silently=False, aws_access_key=None, aws_secret_key=None, aws_region_name=None, aws_region_endpoint=None, aws_auto_throttle=None, dkim_domain=None, dkim_key=None, dkim_selector=None, dkim_headers=None, **kwargs): super(SESBackend, self).__init__(fail_silently=fail_silently, **kwargs) self._access_key_id = aws_access_key or settings.ACCESS_KEY self._access_key = aws_secret_key or settings.SECRET_KEY self._region = RegionInfo( name=aws_region_name or settings.AWS_SES_REGION_NAME, endpoint=aws_region_endpoint or settings.AWS_SES_REGION_ENDPOINT) self._throttle = aws_auto_throttle or settings.AWS_SES_AUTO_THROTTLE self.dkim_domain = dkim_domain or settings.DKIM_DOMAIN self.dkim_key = dkim_key or settings.DKIM_PRIVATE_KEY self.dkim_selector = dkim_selector or settings.DKIM_SELECTOR self.dkim_headers = dkim_headers or settings.DKIM_HEADERS self.connection = None def open(self): """Create a connection to the AWS API server. This can be reused for sending multiple emails. """ if self.connection: return False try: self.connection = SESConnection( aws_access_key_id=self._access_key_id, aws_secret_access_key=self._access_key, region=self._region, ) except: if not self.fail_silently: raise def close(self): """Close any open HTTP connections to the API server. """ try: self.connection.close() self.connection = None except: if not self.fail_silently: raise def send_messages(self, email_messages): """Sends one or more EmailMessage objects and returns the number of email messages sent. """ if not email_messages: return new_conn_created = self.open() if not self.connection: # Failed silently return num_sent = 0 source = settings.AWS_SES_RETURN_PATH for message in email_messages: # Automatic throttling. Assumes that this is the only SES client # currently operating. The AWS_SES_AUTO_THROTTLE setting is a # factor to apply to the rate limit, with a default of 0.5 to stay # well below the actual SES throttle. # Set the setting to 0 or None to disable throttling. if self._throttle: global recent_send_times now = datetime.now() # Get and cache the current SES max-per-second rate limit # returned by the SES API. rate_limit = self.get_rate_limit() # Prune from recent_send_times anything more than a few seconds # ago. Even though SES reports a maximum per-second, the way # they enforce the limit may not be on a one-second window. # To be safe, we use a two-second window (but allow 2 times the # rate limit) and then also have a default rate limit factor of # 0.5 so that we really limit the one-second amount in two # seconds. window = 2.0 # seconds window_start = now - timedelta(seconds=window) new_send_times = [] for time in recent_send_times: if time > window_start: new_send_times.append(time) recent_send_times = new_send_times # If the number of recent send times in the last 1/_throttle # seconds exceeds the rate limit, add a delay. # Since I'm not sure how Amazon determines at exactly what # point to throttle, better be safe than sorry and let in, say, # half of the allowed rate. if len(new_send_times) > rate_limit * window * self._throttle: # Sleep the remainder of the window period. delta = now - new_send_times[0] total_seconds = (delta.microseconds + (delta.seconds + delta.days * 24 * 3600) * 10**6) / 10**6 delay = window - total_seconds if delay > 0: sleep(delay) recent_send_times.append(now) # end of throttling try: response = self.connection.send_raw_email( source=source or message.from_email, destinations=message.recipients(), raw_message=unicode(dkim_sign(message.message().as_string(), dkim_key=self.dkim_key, dkim_domain=self.dkim_domain, dkim_selector=self.dkim_selector, dkim_headers=self.dkim_headers, ), 'utf-8') ) message.extra_headers['status'] = 200 message.extra_headers['message_id'] = response[ 'SendRawEmailResponse']['SendRawEmailResult']['MessageId'] message.extra_headers['request_id'] = response[ 'SendRawEmailResponse']['ResponseMetadata']['RequestId'] num_sent += 1 except SESConnection.ResponseError, err: # Store failure information so to post process it if required error_keys = ['status', 'reason', 'body', 'request_id', 'error_code', 'error_message'] for key in error_keys: message.extra_headers[key] = getattr(err, key, None) if not self.fail_silently: raise if new_conn_created: self.close() return num_sent
class SESBackend(BaseEmailBackend): """A Django Email backend that uses Amazon's Simple Email Service. """ def __init__(self, fail_silently=False, *args, **kwargs): super(SESBackend, self).__init__(fail_silently=fail_silently, *args, **kwargs) self._access_key_id = getattr(settings, "AWS_ACCESS_KEY_ID", None) self._access_key = getattr(settings, "AWS_SECRET_ACCESS_KEY", None) self._api_endpoint = getattr(settings, "AWS_SES_API_HOST", SESConnection.DefaultHost) self.connection = None def open(self): """Create a connection to the AWS API server. This can be reused for sending multiple emails. """ if self.connection: return False try: self.connection = SESConnection( aws_access_key_id=self._access_key_id, aws_secret_access_key=self._access_key, host=self._api_endpoint ) except: if not self.fail_silently: raise def close(self): """Close any open HTTP connections to the API server. """ try: self.connection.close() self.connection = None except: if not self.fail_silently: raise def send_messages(self, email_messages): """Sends one or more EmailMessage objects and returns the number of email messages sent. """ if not email_messages: return new_conn_created = self.open() if not self.connection: # Failed silently return num_sent = 0 for message in email_messages: try: response = self.connection.send_raw_email( source=message.from_email, destinations=message.recipients(), raw_message=message.message().as_string(), ) message.extra_headers["status"] = 200 message.extra_headers["message_id"] = response["SendRawEmailResponse"]["SendRawEmailResult"][ "MessageId" ] message.extra_headers["request_id"] = response["SendRawEmailResponse"]["ResponseMetadata"]["RequestId"] num_sent += 1 except SESConnection.ResponseError, err: # Store failure information so you can post process it if required error_keys = ["status", "reason", "body", "request_id", "error_code", "error_message"] for key in error_keys: message.extra_headers[key] = getattr(err, key, None) if not self.fail_silently: raise if new_conn_created: self.close() return num_sent
class SESBackend(BaseEmailBackend): """A Django Email backend that uses Amazon's Simple Email Service. """ def __init__(self, fail_silently=False, aws_access_key=None, aws_secret_key=None, aws_region_name=None, aws_region_endpoint=None, aws_auto_throttle=None, dkim_domain=None, dkim_key=None, dkim_selector=None, dkim_headers=None, **kwargs): super(SESBackend, self).__init__(fail_silently=fail_silently, **kwargs) self._access_key_id = aws_access_key or settings.ACCESS_KEY self._access_key = aws_secret_key or settings.SECRET_KEY self._region = RegionInfo(name=aws_region_name or settings.AWS_SES_REGION_NAME, endpoint=aws_region_endpoint or settings.AWS_SES_REGION_ENDPOINT) self._throttle = aws_auto_throttle or settings.AWS_SES_AUTO_THROTTLE self.dkim_domain = dkim_domain or settings.DKIM_DOMAIN self.dkim_key = dkim_key or settings.DKIM_PRIVATE_KEY self.dkim_selector = dkim_selector or settings.DKIM_SELECTOR self.dkim_headers = dkim_headers or settings.DKIM_HEADERS self.connection = None def open(self): """Create a connection to the AWS API server. This can be reused for sending multiple emails. """ if self.connection: return False try: self.connection = SESConnection( aws_access_key_id=self._access_key_id, aws_secret_access_key=self._access_key, region=self._region, ) except: if not self.fail_silently: raise def close(self): """Close any open HTTP connections to the API server. """ try: self.connection.close() self.connection = None except: if not self.fail_silently: raise def send_messages(self, email_messages): """Sends one or more EmailMessage objects and returns the number of email messages sent. """ if not email_messages: return new_conn_created = self.open() if not self.connection: # Failed silently return num_sent = 0 source = settings.AWS_SES_RETURN_PATH for message in email_messages: # Automatic throttling. Assumes that this is the only SES client # currently operating. The AWS_SES_AUTO_THROTTLE setting is a # factor to apply to the rate limit, with a default of 0.5 to stay # well below the actual SES throttle. # Set the setting to 0 or None to disable throttling. if self._throttle: global recent_send_times now = datetime.now() # Get and cache the current SES max-per-second rate limit # returned by the SES API. rate_limit = self.get_rate_limit() # Prune from recent_send_times anything more than a few seconds # ago. Even though SES reports a maximum per-second, the way # they enforce the limit may not be on a one-second window. # To be safe, we use a two-second window (but allow 2 times the # rate limit) and then also have a default rate limit factor of # 0.5 so that we really limit the one-second amount in two # seconds. window = 2.0 # seconds window_start = now - timedelta(seconds=window) new_send_times = [] for time in recent_send_times: if time > window_start: new_send_times.append(time) recent_send_times = new_send_times # If the number of recent send times in the last 1/_throttle # seconds exceeds the rate limit, add a delay. # Since I'm not sure how Amazon determines at exactly what # point to throttle, better be safe than sorry and let in, say, # half of the allowed rate. if len(new_send_times) > rate_limit * window * self._throttle: # Sleep the remainder of the window period. delta = now - new_send_times[0] total_seconds = (delta.microseconds + (delta.seconds + delta.days * 24 * 3600) * 10**6) / 10**6 delay = window - total_seconds if delay > 0: sleep(delay) recent_send_times.append(now) # end of throttling try: response = self.connection.send_raw_email( source=source or message.from_email, destinations=message.recipients(), raw_message=unicode( dkim_sign( message.message().as_string(), dkim_key=self.dkim_key, dkim_domain=self.dkim_domain, dkim_selector=self.dkim_selector, dkim_headers=self.dkim_headers, ), 'utf-8')) message.extra_headers['status'] = 200 message.extra_headers['message_id'] = response[ 'SendRawEmailResponse']['SendRawEmailResult']['MessageId'] message.extra_headers['request_id'] = response[ 'SendRawEmailResponse']['ResponseMetadata']['RequestId'] num_sent += 1 except SESConnection.ResponseError, err: # Store failure information so to post process it if required error_keys = [ 'status', 'reason', 'body', 'request_id', 'error_code', 'error_message' ] for key in error_keys: message.extra_headers[key] = getattr(err, key, None) if not self.fail_silently: raise if new_conn_created: self.close() return num_sent
class SESBackend(BaseEmailBackend): """A Django Email backend that uses Amazon's Simple Email Service. """ def __init__(self, fail_silently=False, *args, **kwargs): super(SESBackend, self).__init__(fail_silently=fail_silently, *args, **kwargs) self._access_key_id = getattr(settings, 'AWS_ACCESS_KEY_ID', None) self._access_key = getattr(settings, 'AWS_SECRET_ACCESS_KEY', None) self._api_endpoint = getattr(settings, 'AWS_SES_API_HOST', SESConnection.DefaultHost) self.connection = None def open(self): """Create a connection to the AWS API server. This can be reused for sending multiple emails. """ if self.connection: return False try: self.connection = SESConnection( aws_access_key_id=self._access_key_id, aws_secret_access_key=self._access_key, host=self._api_endpoint, ) except: if not self.fail_silently: raise def close(self): """Close any open HTTP connections to the API server. """ try: self.connection.close() self.connection = None except: if not self.fail_silently: raise def send_messages(self, email_messages): """Sends one or more EmailMessage objects and returns the number of email messages sent. """ if not email_messages: return new_conn_created = self.open() if not self.connection: # Failed silently return num_sent = 0 for message in email_messages: try: response = self.connection.send_raw_email( source=message.from_email, destinations=message.recipients(), raw_message=message.message().as_string(), ) message.extra_headers['status'] = 200 message.extra_headers['message_id'] = response[ 'SendRawEmailResponse']['SendRawEmailResult']['MessageId'] message.extra_headers['request_id'] = response[ 'SendRawEmailResponse']['ResponseMetadata']['RequestId'] num_sent += 1 except SESConnection.ResponseError, err: # Store failure information so you can post process it if required error_keys = [ 'status', 'reason', 'body', 'request_id', 'error_code', 'error_message' ] for key in error_keys: message.extra_headers[key] = getattr(err, key, None) if not self.fail_silently: raise if new_conn_created: self.close() return num_sent
class SESBackend(BaseEmailBackend): """A Django Email backend that uses Amazon's Simple Email Service. """ def __init__(self, fail_silently=False, aws_access_key=None, aws_secret_key=None, aws_region_name=None, aws_region_endpoint=None, aws_auto_throttle=None, dkim_domain=None, dkim_key=None, dkim_selector=None, dkim_headers=None, proxy=None, proxy_port=None, proxy_user=None, proxy_pass=None, **kwargs): super(SESBackend, self).__init__(fail_silently=fail_silently, **kwargs) self._access_key_id = aws_access_key or settings.ACCESS_KEY self._access_key = aws_secret_key or settings.SECRET_KEY self._region = RegionInfo( name=aws_region_name or settings.AWS_SES_REGION_NAME, endpoint=aws_region_endpoint or settings.AWS_SES_REGION_ENDPOINT) self._throttle = aws_auto_throttle or settings.AWS_SES_AUTO_THROTTLE self._proxy = proxy or settings.AWS_SES_PROXY self._proxy_port = proxy_port or settings.AWS_SES_PROXY_PORT self._proxy_user = proxy_user or settings.AWS_SES_PROXY_USER self._proxy_pass = proxy_pass or settings.AWS_SES_PROXY_PASS self.dkim_domain = dkim_domain or settings.DKIM_DOMAIN self.dkim_key = dkim_key or settings.DKIM_PRIVATE_KEY self.dkim_selector = dkim_selector or settings.DKIM_SELECTOR self.dkim_headers = dkim_headers or settings.DKIM_HEADERS self.connection = None def open(self): """Create a connection to the AWS API server. This can be reused for sending multiple emails. """ if self.connection: return False try: self.connection = SESConnection( aws_access_key_id=self._access_key_id, aws_secret_access_key=self._access_key, region=self._region, proxy=self._proxy, proxy_port=self._proxy_port, proxy_user=self._proxy_user, proxy_pass=self._proxy_pass, ) except Exception: if not self.fail_silently: raise def close(self): """Close any open HTTP connections to the API server. """ try: self.connection.close() self.connection = None except Exception: if not self.fail_silently: raise def send_messages(self, email_messages): """Sends one or more EmailMessage objects and returns the number of email messages sent. """ if not email_messages: return new_conn_created = self.open() if not self.connection: # Failed silently return num_sent = 0 source = settings.AWS_SES_RETURN_PATH for message in email_messages: # SES Configuration sets. If the AWS_SES_CONFIGURATION_SET setting # is not None, append the appropriate header to the message so that # SES knows which configuration set it belongs to. # # If settings.AWS_SES_CONFIGURATION_SET is a callable, pass it the # message object and dkim settings and expect it to return a string # containing the SES Configuration Set name. if (settings.AWS_SES_CONFIGURATION_SET and 'X-SES-CONFIGURATION-SET' not in message.extra_headers): if callable(settings.AWS_SES_CONFIGURATION_SET): message.extra_headers[ 'X-SES-CONFIGURATION-SET'] = settings.AWS_SES_CONFIGURATION_SET( message, dkim_domain=self.dkim_domain, dkim_key=self.dkim_key, dkim_selector=self.dkim_selector, dkim_headers=self.dkim_headers ) else: message.extra_headers[ 'X-SES-CONFIGURATION-SET'] = settings.AWS_SES_CONFIGURATION_SET # Automatic throttling. Assumes that this is the only SES client # currently operating. The AWS_SES_AUTO_THROTTLE setting is a # factor to apply to the rate limit, with a default of 0.5 to stay # well below the actual SES throttle. # Set the setting to 0 or None to disable throttling. if self._throttle: global recent_send_times now = datetime.now() # Get and cache the current SES max-per-second rate limit # returned by the SES API. rate_limit = self.get_rate_limit() logger.debug(u"send_messages.throttle rate_limit='{}'".format(rate_limit)) # Prune from recent_send_times anything more than a few seconds # ago. Even though SES reports a maximum per-second, the way # they enforce the limit may not be on a one-second window. # To be safe, we use a two-second window (but allow 2 times the # rate limit) and then also have a default rate limit factor of # 0.5 so that we really limit the one-second amount in two # seconds. window = 2.0 # seconds window_start = now - timedelta(seconds=window) new_send_times = [] for time in recent_send_times: if time > window_start: new_send_times.append(time) recent_send_times = new_send_times # If the number of recent send times in the last 1/_throttle # seconds exceeds the rate limit, add a delay. # Since I'm not sure how Amazon determines at exactly what # point to throttle, better be safe than sorry and let in, say, # half of the allowed rate. if len(new_send_times) > rate_limit * window * self._throttle: # Sleep the remainder of the window period. delta = now - new_send_times[0] total_seconds = (delta.microseconds + (delta.seconds + delta.days * 24 * 3600) * 10**6) / 10**6 delay = window - total_seconds if delay > 0: sleep(delay) recent_send_times.append(now) # end of throttling try: response = self.connection.send_raw_email( source=source or message.from_email, destinations=message.recipients(), raw_message=dkim_sign(message.message().as_string(), dkim_key=self.dkim_key, dkim_domain=self.dkim_domain, dkim_selector=self.dkim_selector, dkim_headers=self.dkim_headers) ) message.extra_headers['status'] = 200 message.extra_headers['message_id'] = response[ 'SendRawEmailResponse']['SendRawEmailResult']['MessageId'] message.extra_headers['request_id'] = response[ 'SendRawEmailResponse']['ResponseMetadata']['RequestId'] num_sent += 1 if 'X-SES-CONFIGURATION-SET' in message.extra_headers: logger.debug(u"send_messages.sent from='{}' recipients='{}' message_id='{}' request_id='{}' ses-configuration-set='{}'".format( message.from_email, ", ".join(message.recipients()), message.extra_headers['message_id'], message.extra_headers['request_id'], message.extra_headers['X-SES-CONFIGURATION-SET'] )) else: logger.debug(u"send_messages.sent from='{}' recipients='{}' message_id='{}' request_id='{}'".format( message.from_email, ", ".join(message.recipients()), message.extra_headers['message_id'], message.extra_headers['request_id'] )) except SESConnection.ResponseError as err: # Store failure information so to post process it if required error_keys = ['status', 'reason', 'body', 'request_id', 'error_code', 'error_message'] for key in error_keys: message.extra_headers[key] = getattr(err, key, None) if not self.fail_silently: raise if new_conn_created: self.close() return num_sent def get_rate_limit(self): if self._access_key_id in cached_rate_limits: return cached_rate_limits[self._access_key_id] new_conn_created = self.open() if not self.connection: raise Exception( "No connection is available to check current SES rate limit.") try: quota_dict = self.connection.get_send_quota() max_per_second = quota_dict['GetSendQuotaResponse'][ 'GetSendQuotaResult']['MaxSendRate'] ret = float(max_per_second) cached_rate_limits[self._access_key_id] = ret return ret finally: if new_conn_created: self.close()
class SESBackend(BaseEmailBackend): """A Django Email backend that uses Amazon's Simple Email Service. """ def __init__(self, fail_silently=False, *args, **kwargs): super(SESBackend, self).__init__(fail_silently=fail_silently, *args, **kwargs) self._access_key_id = getattr(settings, 'AWS_ACCESS_KEY_ID', None) self._access_key = getattr(settings, 'AWS_SECRET_ACCESS_KEY', None) self._api_endpoint = getattr(settings, 'AWS_SES_API_HOST', SESConnection.DefaultHost) self.connection = None self._lock = threading.RLock() def open(self): """Create a connection to the AWS API server. This can be reused for sending multiple emails. """ if self.connection: return False try: self.connection = SESConnection( aws_access_key_id=self._access_key_id, aws_secret_access_key=self._access_key, host=self._api_endpoint, ) except: if not self.fail_silently: raise def close(self): """Close any open HTTP connections to the API server. """ try: self.connection.close() self.connection = None except: if not self.fail_silently: raise def send_messages(self, email_messages): """Sends one or more EmailMessage objects and returns the number of email messages sent. """ if not email_messages: return self._lock.acquire() try: new_conn_created = self.open() if not self.connection: # Failed silently return num_sent = 0 for message in email_messages: try: self.connection.send_raw_email( source=message.from_email, destinations=message.recipients(), raw_message=message.message().as_string(), ) num_sent += 1 except SESConnection.ResponseError: if not self.fail_silently: raise pass if new_conn_created: self.close() finally: self._lock.release() return num_sent
class SESBackend(BaseEmailBackend): """A Django Email backend that uses Amazon's Simple Email Service. """ def __init__(self, fail_silently=False, *args, **kwargs): super(SESBackend, self).__init__(fail_silently=fail_silently, *args, **kwargs) self._access_key_id = getattr(settings, 'AWS_ACCESS_KEY_ID', None) self._access_key = getattr(settings, 'AWS_SECRET_ACCESS_KEY', None) self._api_endpoint = getattr(settings, 'AWS_SES_API_HOST', SESConnection.DefaultHost) self.connection = None self._lock = threading.RLock() def open(self): """Create a connection to the AWS API server. This can be reused for sending multiple emails. """ if self.connection: return False try: self.connection = SESConnection( aws_access_key_id=self._access_key_id, aws_secret_access_key=self._access_key, host=self._api_endpoint, ) except: if not self.fail_silently: raise def close(self): """Close any open HTTP connections to the API server. """ try: self.connection.close() self.connection = None except: if not self.fail_silently: raise def dkim_sign(self, message): # Courtesy of http://djangosnippets.org/snippets/1995/ raw_message = message.message().as_string() if settings.DKIM_PRIVATE_KEY: included_headers = [ "Content-Type", "MIME-Version", "Content-Transfer-Encoding", "Subject", "From", "To" ] dkim_header = dkim.sign(raw_message, settings.DKIM_SELECTOR, settings.DKIM_DOMAIN, settings.DKIM_PRIVATE_KEY, include_headers=included_headers) raw_message = dkim_header + raw_message return raw_message def send_messages(self, email_messages): """Sends one or more EmailMessage objects and returns the number of email messages sent. """ if not email_messages: return self._lock.acquire() try: new_conn_created = self.open() if not self.connection: # Failed silently return num_sent = 0 for message in email_messages: raw_message = self.dkim_sign(message) try: self.connection.send_raw_email( source=message.from_email, destinations=message.recipients(), raw_message=raw_message, ) num_sent += 1 except SESConnection.ResponseError: if not self.fail_silently: raise pass if new_conn_created: self.close() finally: self._lock.release() return num_sent
class SESBackend(BaseEmailBackend): """A Django Email backend that uses Amazon's Simple Email Service. """ def __init__(self, fail_silently=False, *args, **kwargs): super(SESBackend, self).__init__(fail_silently=fail_silently, *args, **kwargs) self._access_key_id = getattr(settings, 'AWS_ACCESS_KEY_ID', None) self._access_key = getattr(settings, 'AWS_SECRET_ACCESS_KEY', None) self._api_endpoint = getattr(settings, 'AWS_SES_API_HOST', SESConnection.DefaultHost) self.connection = None self._lock = threading.RLock() def open(self): """Create a connection to the AWS API server. This can be reused for sending multiple emails. """ if self.connection: return False try: self.connection = SESConnection( aws_access_key_id=self._access_key_id, aws_secret_access_key=self._access_key, host=self._api_endpoint, ) except: if not self.fail_silently: raise def close(self): """Close any open HTTP connections to the API server. """ try: self.connection.close() self.connection = None except: if not self.fail_silently: raise def send_messages(self, email_messages): """Sends one or more EmailMessage objects and returns the number of email messages sent. """ if not email_messages: return self._lock.acquire() try: new_conn_created = self.open() if not self.connection: # Failed silently return num_sent = 0 for message in email_messages: try: self.connection.send_raw_email( source=message.from_email, destinations=message.recipients(), raw_message=message.message().as_string(), ) num_sent += 1 except SESConnection.ResponseError: if not self.fail_silently: raise pass if new_conn_created: self.close() finally: self._lock.release() return num_sent
class SESBackend(BaseEmailBackend): """A Django Email backend that uses Amazon's Simple Email Service. """ def __init__(self, fail_silently=False, *args, **kwargs): super(SESBackend, self).__init__(fail_silently=fail_silently, *args, **kwargs) self._access_key_id = getattr(settings, 'AWS_ACCESS_KEY_ID', None) self._access_key = getattr(settings, 'AWS_SECRET_ACCESS_KEY', None) self._api_endpoint = getattr(settings, 'AWS_SES_API_HOST', SESConnection.DefaultHost) self.connection = None self._lock = threading.RLock() def open(self): """Create a connection to the AWS API server. This can be reused for sending multiple emails. """ if self.connection: return False try: self.connection = SESConnection( aws_access_key_id=self._access_key_id, aws_secret_access_key=self._access_key, host=self._api_endpoint, ) except: if not self.fail_silently: raise def close(self): """Close any open HTTP connections to the API server. """ try: self.connection.close() self.connection = None except: if not self.fail_silently: raise def dkim_sign(self, message): # Courtesy of http://djangosnippets.org/snippets/1995/ raw_message = message.message().as_string() if settings.DKIM_PRIVATE_KEY: included_headers = ["Content-Type", "MIME-Version", "Content-Transfer-Encoding", "Subject", "From", "To"] dkim_header = dkim.sign(raw_message, settings.DKIM_SELECTOR, settings.DKIM_DOMAIN, settings.DKIM_PRIVATE_KEY, include_headers=included_headers) raw_message = dkim_header + raw_message return raw_message def send_messages(self, email_messages): """Sends one or more EmailMessage objects and returns the number of email messages sent. """ if not email_messages: return self._lock.acquire() try: new_conn_created = self.open() if not self.connection: # Failed silently return num_sent = 0 for message in email_messages: raw_message = self.dkim_sign(message) try: self.connection.send_raw_email( source=message.from_email, destinations=message.recipients(), raw_message=raw_message, ) num_sent += 1 except SESConnection.ResponseError: if not self.fail_silently: raise pass if new_conn_created: self.close() finally: self._lock.release() return num_sent