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 AWSSESSender: def __init__(self, aws_access_key, aws_secret_key): try: self.ses_conn = SESConnection(aws_access_key_id=aws_access_key, aws_secret_access_key=aws_secret_key) except: print "SES Connection was failed !!" exit() def send_email(self, sender, subject, body, recipient_list): if self.ses_conn != None: self.ses_conn.send_email(sender, subject, body, recipient_list) else: print "Connection object is null!!" def send_raw_email(self, sender, subject, body, recipient_list, attachment_path): msg = MIMEMultipart() msg['Subject'] = subject msg['From'] = sender to_list = "" for idx, data in enumerate(recipient_list): if idx < len(recipient_list) - 1: to_list += data + "," else: to_list += data msg['To'] = to_list msg.premable = 'Multipart Message.\n' # Assembling Mail body part = MIMEText(body) msg.attach(part) # Assembling Attachment part = MIMEApplication(open(attachment_path, 'rb').read()) part.add_header('Content-Disposition', 'attachment', filename=os.path.basename(attachment_path)) msg.attach(part) # Send RAW email if self.ses_conn != None: self.ses_conn.send_raw_email(msg.as_string(), source=msg['From'], destinations=recipient_list) else: print "Connection object is null!!"
class AmazonTransport(object): __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, err: raise # TODO: Raise appropriate internal exception.
def emit(self, record): """ Emit a record. Format the record and send it to the specified addressees. """ client = SESConnection(self.aws_key, self.aws_secret) message = MIMEMultipart('alternative') message.set_charset('UTF-8') message['Subject'] = self._encode_str(self.getSubject(record)) message['From'] = self._encode_str(self.fromaddr) message['To'] = self._convert_to_strings(self.toaddrs) from email.utils import formatdate body = self.format(record) body = "From: %s\r\n" \ "To: %s\r\n" \ "Subject: %s\r\n" \ "Date: %s\r\n\r\n" \ "%s" % ( self.fromaddr, ",".join(self.toaddrs), self.getSubject(record), formatdate(), body) message.attach(MIMEText(self._encode_str(body), 'plain')) return client.send_raw_email(message.as_string(), self.fromaddr, destinations=self.toaddrs)
def emit(self, record): """ Emit a record. Format the record and send it to the specified addressees. """ client = SESConnection(self.aws_key, self.aws_secret) message = MIMEMultipart('alternative') message.set_charset('UTF-8') message['Subject'] = self._encode_str(self.getSubject(record)) message['From'] = self._encode_str(self.fromaddr) message['To'] = self._convert_to_strings(self.toaddrs) from email.utils import formatdate body = self.format(record) body = "Date: {0}\r\n\r\n {1}".format(formatdate(), body) message.attach(MIMEText(self._encode_str(body), 'plain')) return client.send_raw_email(message.as_string(), self.fromaddr, destinations=self.toaddrs)
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_ses(self, sender_from, recipients, body): # SES Connection create aws_access_key = EXTERNAL_CONFIG['aws_access_key'] aws_secret_key = EXTERNAL_CONFIG['aws_secret_key'] from boto.ses import SESConnection ses_conn = SESConnection(aws_access_key, aws_secret_key) ret = ses_conn.send_raw_email(sender_from, body, destinations=[]) current_app.logger.debug('sent mail to %s by SES' % (str(recipients),)) return ret
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 SesSender(object): logger = None def run(self): self.init_log() try: self.log("Args: %r" % argv) self.log("Env: %r" % dict(environ)) self.get_parties() self.get_credentials() self.make_connection() self.process_message() self.send_message() except Exception: self.abort('Unspecified error',1) def get_parties(self): try: self.sender = argv[1] self.recipients = argv[2:] except Exception: self.abort('Missing Sender / Recipients',2) def get_credentials(self): try: self.aws_id = environ['AWS_ACCESS_KEY_ID'] self.aws_key = environ['AWS_SECRET_KEY'] assert self.aws_id is not None assert self.aws_key is not None except Exception: self.abort('Missing AWS Credentials',3) def make_connection(self): try: self.conn = SESConnection(self.aws_id,self.aws_key) except Exception: self.abort('Failed to establish connection',4) def process_message(self): try: msg = stdin.read() assert msg[:4] == 'From' envelope,msg = msg.split('\n',1) self.msg = msg self.log('Sender: %r' % self.sender) self.log('Recipients: %r' % self.recipients) self.log('Message:\n' + msg) except Exception: self.abort('Failed to process message text',5) def send_message(self): try: self.conn.send_raw_email( source=self.sender, raw_message=self.msg, destinations=self.recipients ) except BotoServerError, bse: if 'InvalidTokenId' in bse.body: self.abort('Bad AWS Credentials (token)',6) if 'SignatureDoesNotMatch' in bse.body: self.abort('Bad AWS Credentials (signature)',6) self.abort('Failed to actually deliver message',7) except Exception: self.abort('Failed to actually deliver message',7)
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, *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
def mailsender(request): mail = request.FILES[u"file"].read() to = request.GET["to"] # split headers i = mail.find("\n\n") headers, mail = mail[:i], mail[i:] headers = headers.split("\n") allowed = { "MIME-Version", "Message-ID", "In-Reply-To", "Content-Type", "Date", "Subject", "From", "To", "Bcc", "Cc", "References", } from_ = None h = [] i = 0 while i < len(headers): add = False header = headers[i][: headers[i].find(":")] if header in allowed: add = True if header == "From": from_ = headers[i][headers[i].find(":") + 1 :].strip() while True: if add: h.append(headers[i]) i += 1 if i >= len(headers) or headers[i][0] not in " \t": break mail = "\n".join(h) + mail # find from email i = from_.find("<") if i != -1: from_ = from_[i + 1 :] from_ = from_[: from_.find(">")] # logger.info("headers: %s", str(headers)) # logger.info("h: %s", str(h)) logger.info("Mail from %s to %s recieved", from_, to) # Only allow sending from altekamereren domains and registered users. if ( not from_.endswith("@altekamereren.org") and not from_.endswith("@altekamereren.com") and User.objects.filter(email=from_).exists() ): logger.info("Sender not accepted.") return HttpResponse(status=403) to = to.replace(u"flojt", u"flöjt") reciever = to.split("@")[0] if reciever in ak.sections: user_emails = [ user.email for user in User.objects.filter(instrument__in=ak.section_to_short_instruments[reciever], is_active=True) ] logger.info("Sending to section %s: %s", to, str(user_emails)) elif reciever == u"infolistan": reciever = [user.email for user in User.objects.filter(is_active=True)] logger.info("Sending to infolistan: %s", str(reciever)) else: logger.info("List not accepted.") return HttpResponse(status=400) from django.conf import settings from boto.ses import SESConnection from boto.exception import BotoServerError access_key_id = getattr(settings, "AWS_ACCESS_KEY_ID", None) access_key = getattr(settings, "AWS_SECRET_ACCESS_KEY", None) api_endpoint = getattr(settings, "AWS_SES_API_HOST", SESConnection.DefaultHost) connection = SESConnection(aws_access_key_id=access_key_id, aws_secret_access_key=access_key, host=api_endpoint) if not user_emails: return HttpResponse(status=400) try: connection.send_raw_email(mail, settings.ADMINS[0][1], user_emails) except BotoServerError as e: i = e.body.find("<Message>") message = e.body[i + len("<Message>") :] message = message[: message.find("</Message>")] if message == "Email address is not verified.": if MailVerificationSent.objects.filter( email=from_, sent__gte=datetime.datetime.now() - datetime.timedelta(days=1) ).exists(): connection.verify_email_address(from_) logger.error("Sending verify mail to: %s", from_) MailVerificationSent(email=from_).save() else: logger.error("Verify mail already sent today: %s", from_) return HttpResponse(status=444) else: raise return HttpResponse()
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
def mailsender(request): mail = request.FILES[u"file"].read() to = request.GET["to"] #split headers i = mail.find("\n\n") headers, mail = mail[:i], mail[i:] headers = headers.split("\n") allowed = {"MIME-Version", "Message-ID", "In-Reply-To", "Content-Type", "Date", "Subject", "From", "To", "Bcc", "Cc", "References"} from_ = None h = [] i=0 while i<len(headers): add = False header = headers[i][:headers[i].find(":")] if header in allowed: add = True if header == "From": from_ = headers[i][headers[i].find(":")+1:].strip() while True: if add: h.append(headers[i]) i += 1 if i>=len(headers) or headers[i][0] not in " \t": break mail = "\n".join(h) + mail #find from email i = from_.find("<") if i != -1: from_ = from_[i+1:] from_ = from_[:from_.find(">")] #logger.info("headers: %s", str(headers)) #logger.info("h: %s", str(h)) logger.info("Mail from %s to %s recieved", from_, to) # Only allow sending from altekamereren domains and registered users. if not from_.endswith("@altekamereren.org") \ and not from_.endswith("@altekamereren.com") \ and User.objects.filter(email=from_).exists(): logger.info("Sender not accepted.") return HttpResponse(status=403) to = to.replace(u"flojt", u"flöjt") reciever = to.split("@")[0] if reciever in ak.sections: user_emails = [user.email for user in User.objects.filter( instrument__in=ak.section_to_short_instruments[reciever], is_active=True)] logger.info("Sending to section %s: %s", to, str(user_emails)) elif reciever == u"infolistan": reciever = [user.email for user in User.objects.filter(is_active=True)] logger.info("Sending to infolistan: %s", str(reciever)) else: logger.info("List not accepted.") return HttpResponse(status=400) from django.conf import settings from boto.ses import SESConnection from boto.exception import BotoServerError access_key_id = getattr(settings, 'AWS_ACCESS_KEY_ID', None) access_key = getattr(settings, 'AWS_SECRET_ACCESS_KEY', None) api_endpoint = getattr(settings, 'AWS_SES_API_HOST', SESConnection.DefaultHost) connection = SESConnection( aws_access_key_id=access_key_id, aws_secret_access_key=access_key, host=api_endpoint, ) if not user_emails: return HttpResponse(status=400) try: connection.send_raw_email(mail, settings.ADMINS[0][1], user_emails) except BotoServerError as e: i = e.body.find("<Message>") message = e.body[i+len("<Message>"):] message = message[:message.find("</Message>")] if message == "Email address is not verified.": if MailVerificationSent.objects.filter(email=from_, sent__gte=datetime.datetime.now() - datetime.timedelta(days=1) ).exists(): connection.verify_email_address(from_) logger.error("Sending verify mail to: %s", from_) MailVerificationSent(email=from_).save() else: logger.error("Verify mail already sent today: %s", from_) return HttpResponse(status=444) else: raise return HttpResponse()
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, 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 SESMessage(object): """ Usage: msg = SESMessage('*****@*****.**', '*****@*****.**', 'The subject') msg.text = 'Text body' msg.html = 'HTML body' msg.send() """ def __init__(self, source, to_addresses, subject, **kw): self.ses = SESConnection() self._source = source self._to_addresses = to_addresses self._cc_addresses = None self._bcc_addresses = None self.subject = subject self.text = None self.html = None self.attachments = [] def send(self): if not self.ses: raise Exception, 'No connection found' if (self.text and not self.html and not self.attachments) or \ (self.html and not self.text and not self.attachments): return self.ses.send_email(self._source, self.subject, self.text or self.html, self._to_addresses, self._cc_addresses, self._bcc_addresses, format='text' if self.text else 'html') else: if not self.attachments: message = MIMEMultipart('alternative') message['Subject'] = self.subject message['From'] = self._source if isinstance(self._to_addresses, (list, tuple)): message['To'] = COMMASPACE.join(self._to_addresses) else: message['To'] = self._to_addresses message.attach(MIMEText(self.text, 'plain')) message.attach(MIMEText(self.html, 'html')) else: raise NotImplementedError, 'SES does not currently allow ' + \ 'messages with attachments.' # message = MIMEMultipart() # # message_alt = MIMEMultipart('alternative') # # if self.text: # message_alt.attach(MIMEText(self.text, 'plain')) # if self.html: # message_alt.attach(MIMEText(self.html, 'html')) # # message.attach(message_alt) # # message['Subject'] = self.subject # message['From'] = self._source # if isinstance(self._to_addresses, (list, tuple)): # message['To'] = COMMASPACE.join(self._to_addresses) # else: # message['To'] = self._to_addresses # message.preamble = 'You will not see this in a MIME-aware mail reader.\n' # print 'message: ', message.as_string() # for attachment in self.attachments: # # Guess the content type based on the file's extension. Encoding # # will be ignored, although we should check for simple things like # # gzip'd or compressed files. # ctype, encoding = mimetypes.guess_type(attachment) # if ctype is None or encoding is not None: # # No guess could be made, or the file is encoded (compressed), so # # use a generic bag-of-bits type. # ctype = 'application/octet-stream' # maintype, subtype = ctype.split('/', 1) # if maintype == 'text': # fp = open(attachment) # # Note: we should handle calculating the charset # part = MIMEText(fp.read(), _subtype=subtype) # fp.close() # elif maintype == 'image': # fp = open(attachment, 'rb') # part = MIMEImage(fp.read(), _subtype=subtype) # fp.close() # elif maintype == 'audio': # fp = open(attachment, 'rb') # part = MIMEAudio(fp.read(), _subtype=subtype) # fp.close() # else: # fp = open(attachment, 'rb') # part = MIMEBase(maintype, subtype) # part.set_payload(fp.read()) # fp.close() # # Encode the payload using Base64 # encoders.encode_base64(part) # # Set the filename parameter # part.add_header('Content-Disposition', 'attachment', filename=attachment) # message.attach(part) return self.ses.send_raw_email(self._source, message.as_string(), destinations=self._to_addresses)
class SesSender(object): logger = None def run(self): self.init_log() self.init_signer() try: self.log("Args: %r" % argv) self.log("Env: %r" % dict(environ)) self.get_parties() self.get_credentials() self.make_connection() self.process_message() self.send_message() except Exception: self.abort('Unspecified error',1) def get_parties(self): try: self.sender = argv[1] self.recipients = argv[2:] except Exception: self.abort('Missing Sender / Recipients',2) def get_credentials(self): try: self.aws_id = environ['AWS_ACCESS_KEY_ID'] self.aws_key = environ['AWS_SECRET_KEY'] assert self.aws_id is not None assert self.aws_key is not None except Exception: self.abort('Missing AWS Credentials',3) def make_connection(self): try: self.conn = SESConnection(self.aws_id,self.aws_key) except Exception: self.abort('Failed to establish connection',4) def process_message(self): try: msg = stdin.read() assert msg[:4] == 'From' msg = self.sanitize_headers(msg) envelope,msg = msg.split('\n',1) self.msg = self.sign_message(msg) self.log('Sender: %r' % self.sender) self.log('Recipients: %r' % self.recipients) self.log('Message:\n' + msg) except Exception: self.abort('Failed to process message text',5) def sanitize_headers(self, msg): msg_obj = Parser().parsestr(msg) for hdr in msg_obj.keys(): if hdr not in aws_allowed_headers and not hdr.startswith('X-'): del msg_obj[hdr] return str(msg_obj) def sign_message(self, msg): if self.dkim: return DKIM(msg).sign(self.dkim_selector, self.dkim_domain, self.dkim_private_key, canonicalize=('relaxed', 'simple'), include_headers=dkim_include_headers) + msg else: return msg def send_message(self): try: self.conn.send_raw_email( source=self.sender, raw_message=self.msg, destinations=self.recipients ) except BotoServerError, bse: if 'InvalidTokenId' in bse.body: self.abort('Bad AWS Credentials (token)',6) if 'SignatureDoesNotMatch' in bse.body: self.abort('Bad AWS Credentials (signature)',6) if bse.error_code == 'Throttling': self.abort('Failed to actually deliver message: quota exceeded',9) self.abort('Failed to actually deliver message:',7) except Exception: self.abort('Failed to actually deliver message:',7)
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 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