How can I send an image attachment using boto3 SES send_email
client?
I know that I can use send_raw_email
to send an attachment but I need to send the message body with html data
. If this is not possible, how can I send an email with html data using boto3.ses.send_raw_email() ?
After going through several sources, including other SO questions, blogs and Python documentation, I came up with the code below.
Allows for text and/or html emails and attachments.
Separated the MIME and boto3 parts, in case you want to re-use MIME for other purposes, like sending an email with a SMTP client, instead of boto3.
import os
import boto3
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
def create_multipart_message(
sender: str, recipients: list, title: str, text: str=None, html: str=None, attachments: list=None)\
-> MIMEMultipart:
"""
Creates a MIME multipart message object.
Uses only the Python `email` standard library.
Emails, both sender and recipients, can be just the email string or have the format 'The Name <[email protected]>'.
:param sender: The sender.
:param recipients: List of recipients. Needs to be a list, even if only one recipient.
:param title: The title of the email.
:param text: The text version of the email body (optional).
:param html: The html version of the email body (optional).
:param attachments: List of files to attach in the email.
:return: A `MIMEMultipart` to be used to send the email.
"""
multipart_content_subtype = 'alternative' if text and html else 'mixed'
msg = MIMEMultipart(multipart_content_subtype)
msg['Subject'] = title
msg['From'] = sender
msg['To'] = ', '.join(recipients)
# Record the MIME types of both parts - text/plain and text/html.
# According to RFC 2046, the last part of a multipart message, in this case the HTML message, is best and preferred.
if text:
part = MIMEText(text, 'plain')
msg.attach(part)
if html:
part = MIMEText(html, 'html')
msg.attach(part)
# Add attachments
for attachment in attachments or []:
with open(attachment, 'rb') as f:
part = MIMEApplication(f.read())
part.add_header('Content-Disposition', 'attachment', filename=os.path.basename(attachment))
msg.attach(part)
return msg
def send_mail(
sender: str, recipients: list, title: str, text: str=None, html: str=None, attachments: list=None) -> dict:
"""
Send email to recipients. Sends one mail to all recipients.
The sender needs to be a verified email in SES.
"""
msg = create_multipart_message(sender, recipients, title, text, html, attachments)
ses_client = boto3.client('ses') # Use your settings here
return ses_client.send_raw_email(
Source=sender,
Destinations=recipients,
RawMessage={'Data': msg.as_string()}
)
if __name__ == '__main__':
sender_ = 'The Sender <[email protected]>'
recipients_ = ['Recipient One <[email protected]>', '[email protected]']
title_ = 'Email title here'
text_ = 'The text version\nwith multiple lines.'
body_ = """<html><head></head><body><h1>A header 1</h1><br>Some text."""
attachments_ = ['/path/to/file1/filename1.txt', '/path/to/file2/filename2.txt']
response_ = send_mail(sender_, recipients_, title_, text_, body_, attachments_)
print(response_)
Shameless copy example from "HOW TO SEND HTML MAILS USING AMAZON SES " This is how a typical email data content looks like.
message_dict = { 'Data':
'From: ' + mail_sender + '\n'
'To: ' + mail_receivers_list + '\n'
'Subject: ' + mail_subject + '\n'
'MIME-Version: 1.0\n'
'Content-Type: text/html;\n\n' +
mail_content}
If you want to send attachment and HTML text using boto3.ses.send_raw_email, you just need use above message dict and pass. (just put your html text under the mail_content)
response = client.send_raw_email(
Destinations=[
],
FromArn='',
RawMessage=message_dict,
ReturnPathArn='',
Source='',
SourceArn='',
)
In fact, the raw attachment header should works in both send_email() and send_raw_email() . Except in send_mail, you should put the attachment inside Text
, not html
.
March 2019
Here is a copy-pasted solution from the UPDATED official documentation (https://docs.aws.amazon.com/ses/latest/DeveloperGuide/send-email-raw.html):
import os
import boto3
from botocore.exceptions import ClientError
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
# Replace [email protected] with your "From" address.
# This address must be verified with Amazon SES.
SENDER = "Sender Name <[email protected]>"
# Replace [email protected] with a "To" address. If your account
# is still in the sandbox, this address must be verified.
RECIPIENT = "[email protected]"
# Specify a configuration set. If you do not want to use a configuration
# set, comment the following variable, and the
# ConfigurationSetName=CONFIGURATION_SET argument below.
CONFIGURATION_SET = "ConfigSet"
# If necessary, replace us-west-2 with the AWS Region you're using for Amazon SES.
AWS_REGION = "us-west-2"
# The subject line for the email.
SUBJECT = "Customer service contact info"
# The full path to the file that will be attached to the email.
ATTACHMENT = "path/to/customers-to-contact.xlsx"
# The email body for recipients with non-HTML email clients.
BODY_TEXT = "Hello,\r\nPlease see the attached file for a list of customers to contact."
# The HTML body of the email.
BODY_HTML = """\
<html>
<head></head>
<body>
<h1>Hello!</h1>
<p>Please see the attached file for a list of customers to contact.</p>
</body>
</html>
"""
# The character encoding for the email.
CHARSET = "utf-8"
# Create a new SES resource and specify a region.
client = boto3.client('ses',region_name=AWS_REGION)
# Create a multipart/mixed parent container.
msg = MIMEMultipart('mixed')
# Add subject, from and to lines.
msg['Subject'] = SUBJECT
msg['From'] = SENDER
msg['To'] = RECIPIENT
# Create a multipart/alternative child container.
msg_body = MIMEMultipart('alternative')
# Encode the text and HTML content and set the character encoding. This step is
# necessary if you're sending a message with characters outside the ASCII range.
textpart = MIMEText(BODY_TEXT.encode(CHARSET), 'plain', CHARSET)
htmlpart = MIMEText(BODY_HTML.encode(CHARSET), 'html', CHARSET)
# Add the text and HTML parts to the child container.
msg_body.attach(textpart)
msg_body.attach(htmlpart)
# Define the attachment part and encode it using MIMEApplication.
att = MIMEApplication(open(ATTACHMENT, 'rb').read())
# Add a header to tell the email client to treat this part as an attachment,
# and to give the attachment a name.
att.add_header('Content-Disposition','attachment',filename=os.path.basename(ATTACHMENT))
# Attach the multipart/alternative child container to the multipart/mixed
# parent container.
msg.attach(msg_body)
# Add the attachment to the parent container.
msg.attach(att)
#print(msg)
try:
#Provide the contents of the email.
response = client.send_raw_email(
Source=SENDER,
Destinations=[
RECIPIENT
],
RawMessage={
'Data':msg.as_string(),
},
ConfigurationSetName=CONFIGURATION_SET
)
# Display an error if something goes wrong.
except ClientError as e:
print(e.response['Error']['Message'])
else:
print("Email sent! Message ID:"),
print(response['MessageId'])
To expand upon @adkl's answer, Amazon's own example is using older Python way of handling emails and attachments. Nothing wrong with that, just that current documentation on those modules is not comprehensive and might be confusing for new users like me.
Here's simple example on forming message with CSV attachment.
from email.message import EmailMessage
def create_email_message(sender: str, recipients: list, title: str, text: str,
attachment: BytesIO, file_name: str) -> EmailMessage:
msg = EmailMessage()
msg["Subject"] = title
msg['From'] = sender
msg['To'] = ', '.join(recipients)
msg.set_content(text)
data = attachment.read()
msg.add_attachment(
data,
maintype="text",
subtype="csv",
filename=file_name
)
return msg
# Client init, attachment file creation here
message = create_email_message(...)
try:
ses.send_raw_email(
Source=sender,
Destinations=recipients,
RawMessage={'Data': message.as_string()}
)
except ClientError as e:
logger.exception(f"Cannot send email report to {recipients}: {e}")
else:
logger.info("Sent report successfully")
In this example I use BytesIO
object as a source for attachment, but you can use any file-like object that supports read()
method.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With