S3 PreSigned URL is cut when sent in an email - python

I have a script which generates an S3 PreSigned URL and sends it in an email.
The script works fine, but when the email is sent, it adds a new-line to the URL, which breaks it and makes it unusable in the email.
The only packages installed:
boto3
Jinja2
The script:
import boto3
from botocore.config import Config
from jinja2 import Environment, FileSystemLoader
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import smtplib
# AWS
my_config = Config(
region_name = 'eu-west-1'
)
s3 = boto3.client('s3', config=my_config)
bucket = "name of bucket"
# Jinja2
loader = FileSystemLoader('templates')
env = Environment(loader=loader)
email_template = env.get_template('test_template.html')
def create_presigned_url(bucket, object_name, expiration=259200):
response = s3.generate_presigned_url('get_object', Params={'Bucket': bucket, 'Key': object_name}, ExpiresIn=expiration)
return response
def sendEmail(download_link):
toEmail = 'me#email.com'
msg = MIMEMultipart('alternative')
title = 'Test email'
sender = 'me#email.com'
rendered_template = MIMEText(email_template.render({'download_link':download_link}), 'html')
msg['Subject'] = title
msg['From'] = sender
msg['To'] = toEmail
receivers = toEmail
msg.attach(rendered_template)
try:
smtpObj = smtplib.SMTP('mail.server.com', 25)
smtpObj.sendmail(sender, receivers, msg.as_string())
print ("Successfully sent email")
smtpObj.quit()
except Exception as e:
print (e, "Error: unable to send email")
if __name__ == "__main__":
selectedFile = 'file.txt'
# Download link
downloadURL = create_presigned_url(bucket, selectedFile)
# print(downloadURL)
# Send email
sendEmail(downloadURL)
Results
When I run the script, for some reason I get a newline somewhere inside of this long URL, which breaks the URL:
Here's the source from Outlook:
<meta http-equiv="Content-Type" content="text/html; charset=us-ascii">Download link: https://redacted/9%2FJOUw%3D&x-amz-security-token=IQoJb3JpZ2luX2VjEH0aCWV1LXdlc3QtMSJHMEUCIEBMYHD9wKhOfrR00jTv7RIcsRe3NbOU31QniJpEdps8AiEAi377qRmvQQXb5dXhRGJcXulFhRunGTSd0GRyGXHR2kMqzAQIdhAEGgwzNTgyODkxNTgxMTIiDH3bqAZMxVyKH3f6xyqpBL40WhPQMShoV8x1epn85Ml6qQ8Y1xdHe16xyoMWKqylbLGrndMFtYyOgs6LAlDvGJPrcF9xymGf8BqGsGHwCuWGdEcisxvwR%2FUoigjHBXP55fHpF%2FXnVupCRYDIVA5N%2BVOKW5%2BcljVN9KC3RKKvEeUncTGnXIaW5UHNAPiFSrgbbj9X%2FyBptkFmj5f4x2Zblm8crQS0rMTveCuoki3E06NO%2FKiDNiJQpF1vVphb%2F0spIR3CUxSx9HJHjvRBWTWQn9bmmT8rhp0lx%2Bzx9RLlpmE6hRRF6KBpNW%2B86y3EB%2BtMxVBuEhC5M1rCyjou6efK%2FA96wuwBN%2FmjD663vyZipiOGrj4yOFIMPklBu4L1SnfkxhZN8%2BNWXzwc%2B%2B5%2FNfL%2BVzyFpWS7TbGIM4A9TEvL3bPlgafvIl%2Fi24MOrN47UshpdpHGAjG20PBr0cbi75G7D%2B3UoSn%2Bzp0hZkAEACnwWtWtzEpVWbwatx%2FL1T8XF43o8OiKWqCfVxBjoZSc1itxRDOUqonYbCGY2Y0NlkXpvpHBZMcg7530dIFRBBxhTZo4RVXqkymTM4hEvDUw74R%2BDovr%2F%2BG5ji52Wpcng
954ESTpzMjOtuBXKcPtmEWTqx4au99ZP8lxbqKjq3BO%2FJLqrzTCPSEs6CTv7YbtzUqQ0r%2BkFyAU2RnpTTcYPJ5SD8ytlb4qUHb5RhEcn3bbJ5fsIRx%2B6q3LrhWkorDNKp5jh6oth1roRxXQM0swgN%2BzmwY6qQGnjWLAgUSUB9yf3heEdiFZo4DnC7ipW6BgsnkoeZJcPz5Ysx5PG4kzelCP89AsXQGD%2BtFqweusgWJVLo3dfyK3iLJ5myohn7mjSf1YVE%2B5CGlajc2HZl%2BoUOhI5gMMxpFXtpIL6jgTyY5r6ZwCKZ9g1afHO1kUF4VYir2M2BWYHTcB%2Bu8TANzoc15RJih8XmE%2FAWd%2FMQM7SQOQxsbmCiRSv5AeYMuok%2FSw&Expires=1668345190
I tried:
using | safe inside of my Jinja2 template.
I tried using the href HTML tag, no dice.
I don't know what else I can check and have no idea why it's happening. People mentioned this might be the cause: https://www.rfc-editor.org/rfc/rfc2822#section-2.1.1

It looks like the problem is related to max line length defined in the "Internet Message Format" RFC document 5322
2.1.1. Line Length Limits
There are two limits that this standard places on the number of characters in a line. Each line of characters MUST be no more than 998 characters, and SHOULD be no more than 78 characters, excluding the CRLF.
...
The more conservative 78 character recommendation is to accommodate the many implementations of user interfaces that display these messages which may truncate, or disastrously wrap, the display of more than 78 characters per line, in spite of the fact that such implementations are non-conformant to the intent of this specification (and that of [RFC2821] if they actually cause information to be lost). Again, even though this limitation is put on messages, it is encumbant upon implementations which display messages
Your line (with redacted information) has 961 characters, so adding those redacted information I assume you go over that limit of 998 characters.
Now, my thinking is that your local SMTP server is not splitting the line but the one on Amazon server may do that.
I assume that reducing the line length is not really an option :)
I would try with changing the markup, because now Outlook is looking for links and highlighting them in your message. I would try to add html markup for that with new line character inside, which hopefully will be ignored by the email client.
<a href="https://redacted/9%2FJOUw%3D&x-amz......
........">Download link</a>

try to change your jinja template to get something like
<meta http-equiv="Content-Type" content="text/html; charset=us-ascii">
Download link:
Download link
as you didn't add <a href= your link is interpretated as long text => and in long text editor can add line breaks

Related

AWS Lambda unable to send email through AWS SES

I have verified the following
AWS SES isn't in Sandbox. I can send email via console to non-verified email ids.
My Lambda function has a role attached with full access to SES and Lambda (since its initial basic testing gave full permissions)
The following below a basic code from AWS documentation, just hard coded my email id. Yet I can't receive any email. The lambda code runs successfully but I don't receive emails.
import json
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
print('Loading function')
def lambda_handler(event, context):
print("Received event: " + json.dumps(event, indent=2))
#print("value1 = " + event['key1'])
#print("value2 = " + event['key2'])
#print("value3 = " + event['key3'])
#return event['key1'] # Echo back the first key value
#raise Exception('Something went wrong')
SENDER = "[redacted email]"
RECIPIENT = event['email']
CONFIGURATION_SET = "ConfigSet"
AWS_REGION = "us-east-2"
SUBJECT = "Contact Us Form Details"
# 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='us-east-2')
# Create a multipart/mixed parent container.
msg = MIMEMultipart('mixed')
# Add subject, from and to lines.
msg['Subject'] = "Contact Us Form Details"
msg['From'] ="[redacted email]"
msg['To'] = "[redacted email]"
# 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="[redacted email]",
Destinations=[
"[redacted email]"
],
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'])
Attaching my cloud watch logs for reference
If your code is really what you have displayed to us, then the reason that it is not sending the email is because half your code is not being executed.
def lambda_handler(event, context):
print("Received event: " + json.dumps(event, indent=2))
#print("value1 = " + event['key1'])
#print("value2 = " + event['key2'])
#print("value3 = " + event['key3'])
#return event['key1'] # Echo back the first key value
#raise Exception('Something went wrong')
SENDER = "[redacted email]"
RECIPIENT = event['email']
CONFIGURATION_SET = "ConfigSet"
AWS_REGION = "us-east-2"
SUBJECT = "Contact Us Form Details"
# 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."
When AWS Lambda executes the function, it calls lambda_handler(). As per Python formatting, it will execute all indented lines since they form part of the function. This includes your print() statement.
However, starting with the BODY_TEXT = ... line, there is no indenting. This means that the code is part of the "main" program, and not part of the lambda_handler() function. It would be executed when the Lambda container is first instantiated, but not when the function is triggered.
Bottom line: If this is your actual code, you need to fix your indents.
If you are not getting any errors from when executing the lambda, then most probably you are not hitting the SES API. From what I see in your code, you are missing an Access Key Id and Secret Access Key. Try configuring your boto client like this:
client = boto3.client(
'ses',
region_name=region,
aws_access_key_id='aws_access_key_string',
aws_secret_access_key='aws_secret_key_string'
)
Also make sure that your lambda is deployed in the same region as your SES. I see you are using us-east-2.
Another discrepancy I see, which is also in the documentation is that in the official AWS documentation, Destinations is actually Destination. Try it without the 's'.
Can you also paste the cloudwatch logs for the lambda. I see it should print the message Id upon success. Does it?

Using Python and gmail API, how can I send messages with multiple attachements?

Looking to create and send messages with multiple files attached. Per the online gmail api documentation, there is a function for building messages with an attachment but no documentation for howto use it to create a message with multiple attachments.
Can I use the gmail API to send messages with multiple attachments programmatically? How might one do this?
With this function, you can send to one or multiple recipient emails, and also you can attach zero, one or more files. Coding improvement recommendations are welcome, however the way it is now, it works.
Python v3.7
smtplib from https://github.com/python/cpython/blob/3.7/Lib/smtplib.py (download the code and create the smtplib.py on your project folder)
def send_email(se_from, se_pwd, se_to, se_subject, se_plain_text='', se_html_text='', se_attachments=[]):
""" Send an email with the specifications in parameters
The following youtube channel helped me a lot to build this function:
https://www.youtube.com/watch?v=JRCJ6RtE3xU
How to Send Emails Using Python - Plain Text, Adding Attachments, HTML Emails, and More
Corey Schafer youtube channel
Input:
se_from : email address that will send the email
se_pwd : password for authentication (uses SMTP.SSL for authentication)
se_to : destination email. For various emails, use ['email1#example.com', 'email2#example.com']
se_subject : email subject line
se_plain_text : body text in plain format, in case html is not supported
se_html_text : body text in html format
se_attachments : list of attachments. For various attachments, use ['path1\file1.ext1', 'path2\file2.ext2', 'path3\file3.ext3']. Follow your OS guidelines for directory paths. Empty list ([]) if no attachments
Returns
-------
se_error_code : returns True if email was successful (still need to incorporate exception handling routines)
"""
import smtplib
from email.message import EmailMessage
# Join email parts following smtp structure
msg = EmailMessage()
msg['From'] = se_from
msg['To'] = se_to
msg['Subject'] = se_subject
msg.set_content(se_plain_text)
# Adds the html text only if there is one
if se_html_text != '':
msg.add_alternative("""{}""".format(se_html_text), subtype='html')
# Checks if there are files to be sent in the email
if len(se_attachments) > 0:
# Goes through every file in files list
for file in se_attachments:
with open(file, 'rb') as f:
file_data = f.read()
file_name = f.name
# Attaches the file to the message. Leaves google to detect the application to open it
msg.add_attachment(file_data, maintype='application', subtype='octet-stream', filename=file_name)
# Sends the email that has been built
with smtplib.SMTP_SSL('smtp.gmail.com', 465) as smtp:
smtp.login(se_from, se_pwd)
smtp.send_message(msg)
return True
Don't forget to activate less secure apps on your google account (https://myaccount.google.com/lesssecureapps) for this code to work.
Hope this helps

HTML and Plaintext email over Python through Zabbix

I'm working on a script that creates both an HTML and Plaintext email. This didn't seem like a hassle, but I can't hardcode the e-mail into the script I'm using. Allow me to explain why.
I'm using the Zabbix Monitoring system to keep an eye out for my systems. Allow me to use a printer for example. Once one of the toners is close to dying, I want both an HTML and plain text mail to be sent. Zabbix uses several macros however, which can be used to identify what system is being affected by what.
So, the HTML body needs to received before the script is run. Once the HTML body has been received with the correct values, it needs to have a plain copy too. As much as I'd like to have the plain-text contain the correct parameters (copied from the macros) too, I don't consider this a necessity.
This is what my script currently looks like.
#!/usr/bin/env python
list1=[0, 1, 2, 3, 4];
import mimetypes, os, smtplib, sys
from email import encoders
from email.mime.audio import MIMEAudio
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
me = "x"
you = "y"
DESTINATION=sys.argv[1]
SUBJECT=sys.argv[2]
MESSAGE=sys.argv[3]
PASS='password'
# Create message container - the correct MIME type is multipart/alternative.
msg = MIMEMultipart('alternative')
msg['Subject'] = "Link"
msg['From'] = me
msg['To'] = you
text = "A problem has been detected on {HOST.NAME} \n \n Trigger: {TRIGGER.NAME} \n Status: {TRIGGER.STATUS} \n Severity: {TRIGGER.SEVERITY} "
#html =
# Record the MIME types of both parts - text/plain and text/html.
part1 = MIMEText(text, 'plain')
#part2 = MIMEText(html, 'html')
# Attach parts into message container.
# According to RFC 2046, the last part of a multipart message, in this case
# the HTML message, is best and preferred.
msg.attach(part1)
#msg.attach(part2)
# Send the message via local SMTP server.
s = smtplib.SMTP('mysmtpserver')
s.ehlo()
s.starttls()
s.ehlo()
s.login(me, PASS)
# sendmail function takes 3 arguments: sender's address, recipient's address
# and message to send - here it is sent as one string.
s.sendmail(me, you, msg.as_string())
s.quit()
The reason why I can't have them being sent seperately or sending the HTML copy alone is because my e-mails get nuked by SpamAssassin. I'm intending to send certain error messages to my customers. So they are immediately notified when their toner is nearly empty, so they can place a new order. The HTML-email has been set up and has been working without failure.
So to put it onto perspective one more time.
Zabbix recieves an error.
There is a default HTML message written, macros become filled, this then needs to be imported to the script.
The script is then run.
There's an e-mail sent with both HTML content as well as the
plain-text for users that can't read the HTML-emails.
The reason the script is a mess is because I've already tried several options, but I can't seem to figure this one out.

Python: What is the correct MIME type for attaching html file to email

I've got a script that sends emails with html content in them.. works as expected...
I'm having trouble sending an attachment with the email.
The attachment is an html file stored in the active directory of the script... "test.html"
How do I attach the html file to the email? I've tried snippets from various other posts I've found relating to this, but each returned the same output of "no such file or directory".
code as follows:
import smtplib
import os
import email.encoders
import email.mime.text
import email.mime.base
import mimetools
import base64
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
# me == Outgoing email address
# you == Recipient's email address
me = "secret"
you = "secret"
# Create message container - the correct MIME type is multipart/alternative.
msg = MIMEMultipart('alternative')
msg['Subject'] = "TEST"
msg['From'] = me
msg['To'] = you
emailMsg = email.MIMEMultipart.MIMEMultipart('alternative')
# Create the body of the message (a plain-text and an HTML version).
html = """\
<html>
<head></head>
<body>test</p>
</body>
</html>"""
# Record the MIME types of both parts - text/plain and text/html.
part1 = MIMEText(text, 'plain')
part2 = MIMEText(html, 'html')
filename = "C:/CHRIS/ServerStatus/Oceaneering_Server_Status.html"
f = file(filename)
attachment = MIMEText(f.read(), _subtype='html')
attachment.add_header('Content-Disposition', 'attachment', filename=filename)
# Attach parts into message container.
# According to RFC 2046, the last part of a multipart message, in this case
# the HTML message, is best and preferred.
msg.attach(part1)
msg.attach(part2)
msg.attach(attachment)
# Send the message via local SMTP server.
mail = smtplib.SMTP('smtp.gmail.com', 587)
mail.ehlo()
# mail.login(username, password) to Outgoing email account
mail.login('secret', 'secret')
mail.sendmail(me, you, msg.as_string())
mail.quit()
i've updated my code in hopes to get this question back on topic... i've made a little progress with the help of Dirk and this link:
Attach a txt file in Python smtplib...
I've been able to physically send an attachment now, but the attachment is still coming through as a text type of file of sort and does not open as the original html file does.
So to reword my question... What is the corrective action for changing the MIME type of this code to correctly attach an .html file to an html based email?
The relative path and directory of my py script and the html file needed to be sent is as follows:
C:\CHRIS\ServerStatus\
This is the output i'm receiving with the code I have:
This is the way the html doc looks outside of the email script (The way it's supposed to look):
I would encourage you to use a library rather than deal with the -rather unpythonic- built-in mail modules, such as the highly recommended envelopes:
https://tomekwojcik.github.io/envelopes/index.html
install with:
pip install envelopes
Python code:
import os
from envelopes import Envelope
filename = "C:/CHRIS/ServerStatus/Oceaneering_Server_Status.html"
envelope = Envelope(
from_addr=(me),
to_addr=(you),
subject=u'Test',
text_body=u'Plain text version',
html_body=html
)
envelope.add_attachment(filename)
envelope.send('smtp.gmail.com', login='secret', password='secret', tls=True)

HTML email in Python

I'm trying to send an HTML email using smtplib. But I need the HTML content to have a table that is populated using values from a dictionary. I did look at the examples on the Python website. But it doesn't explain how to embed Python code within HTML. Any solutions/suggestions?
I also looked at this question. Can I just format it this way?
.format(dict_name)
From your link:
Here’s an example of how to create an HTML message with an alternative
plain text version: 2
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
# me == my email address
# you == recipient's email address
me = "my#email.com"
you = "your#email.com"
# Create message container - the correct MIME type is multipart/alternative.
msg = MIMEMultipart('alternative')
msg['Subject'] = "Link"
msg['From'] = me
msg['To'] = you
# Create the body of the message (a plain-text and an HTML version).
text = "Hi!\nHow are you?\nHere is the link you wanted:\nhttp://www.python.org"
html = """\
<html>
<head></head>
<body>
<p>Hi!<br>
How are you?<br>
Here is the link you wanted.
</p>
</body>
</html>
"""
and the sending section of it:
# Record the MIME types of both parts - text/plain and text/html.
part1 = MIMEText(text, 'plain')
part2 = MIMEText(html, 'html')
# Attach parts into message container.
# According to RFC 2046, the last part of a multipart message, in this case
# the HTML message, is best and preferred.
msg.attach(part1)
msg.attach(part2)
# Send the message via local SMTP server.
s = smtplib.SMTP('localhost')
# sendmail function takes 3 arguments: sender's address, recipient's address
# and message to send - here it is sent as one string.
s.sendmail(me, you, msg.as_string())
s.quit()
Edit 2022: for new comers, please advise with python’s latest stable release docs.
What you need is a template engine. That is, you need a python library that reads a file written in both HTML and code, interprets the code written within the HTML file (e.g. code that retrieves values from a dictionary), and then produces an HTML file for you.
The python wiki seems to have some suggestions

Categories