Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Looking for an Amazon SES example sending a raw mail including attachment

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.
like image 416
voscausa Avatar asked Nov 25 '11 14:11

voscausa


People also ask

Can SES send email with attachment?

You can send messages with attachments through Amazon SES by using the Multipurpose Internet Mail Extensions (MIME) standard. Amazon SES accepts all file attachment types except for attachments with the file extensions in the following list.

What is a raw email?

The raw message format sends an entire message as a single field within a multipart/form-data POST request. The parameter message will contain the entire unparsed email message in addition to the envelope.

Which of the following methods through which Amazon SES delivers mail?

Amazon SES supports two methods of authentication: Sender Policy Framework (SPF) and DomainKeys Identified Mail (DKIM).


2 Answers

Example code here to send SES raw e-mail using a multipart body (plain / html / attachment) :

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:

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)
like image 182
voscausa Avatar answered Sep 27 '22 21:09

voscausa


The procedure I am using is outlined in this gist. It mostly comes from following the example given here.

like image 20
Matt Avatar answered Sep 27 '22 21:09

Matt