Question:
The Amazon SES documentation is not clear for me. So if you can help me with an example tot send raw text e-mails including a PDF attachment, this will help me a lot. I use Python 2.5 and Google App engine.
I have to use “raw”, because that is the only SES option if I include attachments.
My problems at the moment :
- What is the content of the post request.
- Which message fields should I put in the header.
- How to handle the “returnPath”.
- How to handle the text body. It should be : Content-Type: text/plain; charset=UTF-8; format=flowed; delsp=yes.
Content-Transfer-Encoding: base64 - How do I construct the HMAC signature for this post. I know how to make the signature, but how does the raw string looks for the signature function.
Answer:
Example code here to send SES raw e-mail using a multipart body (plain / html / attachment) :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
from email.mime.text import MIMEText from email.mime.application import MIMEApplication from email.mime.multipart import MIMEMultipart from google.appengine.api import urlfetch from google.appengine.runtime import DeadlineExceededError import urllib import hmac import base64 import hashlib from datetime import datetime import logging def ses_multi_part(msg_subject, msg_to, msg_body='', msg_cc=None, msg_bcc=None, file_name=None, file_reader=None, msg_type='plain', msg_from='noreply@....', msg_reply_to='contact@....'): """ send an html or plain e-mail. Use file_name and file_reader to pass an attachment inspiration: https://codeadict.wordpress.com/2010/02/11/send-e-mails-with-attachment-in-python/ """ msg = MIMEMultipart() msg.set_charset("utf-8") msg['Subject'] = msg_subject msg['From'] = msg_from msg['Reply-to'] = msg_reply_to msg['To'] = msg_to if msg_cc: msg['Cc'] = msg_cc if msg_bcc: msg['Bcc'] = msg_bcc logging.debug(msg) msg.preamble = 'Multipart massage.\n' part = MIMEText(msg_body, msg_type, "utf-8") msg.attach(part) if file_name: part = MIMEApplication(file_reader.read()) part.add_header('Content-Disposition', 'attachment', filename=file_name) msg.attach(part) return msg.as_string() class SES(object): """ SES send RAW e-mail inspiration: https://github.com/richieforeman/python-amazon-ses-api/blob/master/amazon_ses.py """ def __init__(self, accessKeyID, secretAccessKey, return_path='contact@....'): self._accessKeyID = accessKeyID self._secretAccessKey = secretAccessKey self.ses_return_path = return_path def _getSignature(self, dateValue): h = hmac.new(key=self._secretAccessKey, msg=dateValue, digestmod=hashlib.sha256) return base64.b64encode(h.digest()).decode() def _getHeaders(self): headers = {'Content-type': 'application/x-www-form-urlencoded', 'Return-Path': self.ses_return_path} d = datetime.utcnow() dateValue = d.strftime('%a, %d %b %Y %H:%M:%S GMT') headers['Date'] = dateValue signature = self._getSignature(dateValue) headers['X-Amzn-Authorization'] = 'AWS3-HTTPS AWSAccessKeyId=%s, Algorithm=HMACSHA256, Signature=%s' % (self._accessKeyID, signature) return headers def _performAction(self, actionName, params=None): if not params: params = {} params['Action'] = actionName response = None #https://email.us-east-1.amazonaws.com/ retry = 0 # download error retry while retry <= 1: # dan een eenmalige retry try: url = 'https://email.us-east-1.amazonaws.com' response = urlfetch.fetch(url=url, payload=urllib.urlencode(params), method=urlfetch.POST, headers=self._getHeaders()) break except (urlfetch.DownloadError, DeadlineExceededError), e: logging.debug('Amazon SES download or deadline error : %d' % (retry + 1)) if retry == 0: retry += 1 continue # retry else: logging.warning('fetcherror' + str(e)) raise # bij een dubbele fout stoppen if response.status_code != 200: logging.warning(response.headers) logging.warning(response.content) raise ValueError('status_code : %s' % (str(response.status_code))) logging.debug(response.content) return response.content def sendRawEmail(self, raw_msg_data): return self._performAction("SendRawEmail", params={"RawMessage.Data": base64.b64encode(raw_msg_data)}) |
Example usage:
1 2 3 4 5 |
ses = SES(settings.AMAZON_ACCESS_KEY_ID, settings.AMAZON_SECRET_ACCESS_KEY, settings.SES_RETURN_PATH[country]) raw_msg_data = ses_multi_part(msg_subject=subject.encode('utf-8'), msg_to=mail_to, msg_body=body_text.encode('utf-8'), msg_bcc=settings.MAIL_BCC, msg_reply_to=reply_to, msg_from=sender, msg_type=msg_type) ses.sendRawEmail(raw_msg_data) |