How to specify remote file attachment for SMTP mail in Python - python-3.x

How can the remote attachment file be specified to be included in SMTP mail? Attachement file is located on different server (own username/password access)
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
mail_content = """ This is body content """
sender_addr = "sender_addr#server.com"
sender_pass = "apassword"
receiver_addr = "receiver_addr#server.com"
# Create MIME header
msg = MIMEMultipart()
msg['From'] = sender_addr
msg['To'] = receiver_addr
msg['Subject'] = 'A test mail subject sent by Python'
msg.attach(MIMEText(mail_content, 'plain'))
fname = "doc_1.pdf"
attach_file = open(fname, 'rb') # **<- How can I specify the remote path here?**
payload = MIMEBase('application', 'octet-stream')
# Attach an attachment to payload
payload.set_payload((attach_file).read())
encoders.encode_base64(payload)
# Add payload header with filename
payload.add_header('Content-Disposition', 'attachment', filename=fname)
msg.attach(payload)
# Create SMTP client
client = smtplib.SMTP('smtp.gmail.com', 587)
client.starttls()
client.login(sender_addr, sender_pass)
text = msg.as_string()
client.sendmail(sender_addr, receiver_addr, text)
client.quit()
print('Mail sent!')

https://www.rfc-editor.org/rfc/rfc2110#section-4.1 explains the Content-Location: header.
Assuming the content you want to link to is in the variable attach_uri, something like
# Create MIME header
msg = MIMEMultipart()
msg['From'] = sender_addr
msg['To'] = receiver_addr
msg['Subject'] = 'A test mail subject sent by Python'
msg.attach(MIMEText(mail_content, 'plain'))
payload = MIMEBase('application', 'octet-stream')
payload.add_header('Content-Location', attach_uri)
msg.attach(payload)
If you are asking how to retrieve stuff from a remote location and include it in an email, that's a pretty broad topic; but assuming you have the content on an HTTP server, try something like
import requests
r = requests.get(attachment_uri)
payload.set_payload(r.content)
before you msg.attach(payload).
With this, there should be no need to set the Content-Location: to the original URI any longer (why would the user care where it came from? And you don't want to reveal the password etc).

Related

Issues with smtplib sending mails

I tried following a youtube video on how to use smtplib to send emails, however whenever I try to send anything it gives me this error.
in alert_mail
msg.set_content(body)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/email/message.py", line 1162, in set_content
super().set_content(*args, **kw)
TypeError: super(type, obj): obj must be an instance or subtype of type
I really don't know why as I followed the video very closely, only changing the Gmail credentials and passwords to my own test accounts.
import smtplib
from email.message import EmailMessage
def alert_mail(subject, body, to):
msg = EmailMessage
msg.set_content(body)
msg["subject"] = subject
msg["to"] = to
user = "helios.alert.system#gmail.com"
msg["from"] = user
password = #2 way encription password would be here, but thats not the issue
server = smtplib.SMTP("smtp.gmail.com", 587)
server.starttls()
server.login(user,password)
server.send_message(msg)
server.quit()
alert_mail("Hey","Hey this is my first com method" , "helios.alert.system#gmail.com") # sends email to itself, doesn't work even when some different address is entred
Any advice will be appreciated!
The video: https://www.youtube.com/watch?v=B1IsCbXp0uE
You should add an opening and closing parenthesis to the msg = EmailMessage line, remember that EmailMessage is an object, so you must use the correct syntax for creating one. This code below should work:
server = smtplib.SMTP(GMAIL_SERVER, GMAIL_PORT)
server.starttls()
server.login(EMAIL_SOURCE, PWD_SOURCE)
msg = EmailMessage()
msg['From'] = EMAIL_SOURCE
msg['To'] = TO_EMAIL
msg['Subject'] = YOUR_SUBJECT
msg.set_content(bodyOfMail)
server.send_message(msg)
del msg
server.quit()

Send email from Databricks Notebook with attachment

I'm a newbie in Python and Spark world. And am trying to build a pyspark code to send an email from Databricks along with the attachment from the mount point location. I'm using below code to implement the same -
import smtplib
from pathlib import Path
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email.utils import COMMASPACE, formatdate
from email import encoders
def send_mail(send_from = <from_email>, send_to = <to_email>, subject = "Test", message = "Test", files=["/mnt/<Mounted Point Directory>/"],
server="<SMTP Host>", port=<SMTP Port>, username='<SMTP Username>', password='<SMTP Password>',
use_tls=True):
msg = MIMEMultipart()
msg['From'] = send_from
msg['To'] = COMMASPACE.join(send_to)
msg['Date'] = formatdate(localtime=True)
msg['Subject'] = subject
msg.attach(MIMEText(message))
for path in files:
part = MIMEBase('application', "octet-stream")
with open(path, 'rb') as file:
part.set_payload(file.read())
encoders.encode_base64(part)
part.add_header('Content-Disposition',
'attachment; filename="{}"'.format(Path(path).name))
msg.attach(part)
smtp = smtplib.SMTP(server, port)
if use_tls:
smtp.starttls()
smtp.login(username, password)
smtp.sendmail(send_from, send_to, msg.as_string())
smtp.quit()
But for some reason the code is giving me File or directory not exists exception.
Am I missing anything over here.
Thanks
You need to modify the code to make it working with DBFS, because the open function doesn't know anything about DBFS or other file systems, and can work only with local files (see documentation about DBFS).
You can do it as following:
if you're on "full Databricks", not Community Edition then you need to prepend the /dbfs to the file name, like, /dbfs/mnt/.... - this /dbfs mount is the way of accessing files on DBFS from the code that works with local files (but there are some limitations when writing to that location).
Or you can use dbutils.fs.cp command to copy file from DBFS to local file, and use that copy of file to attach, like this:
dbutils.fs.cp("/mnt/...", "file:///tmp/local-name")
with open("/tmp/local-name", "r"):
...

How to download imap email attachment with base64 content in python?

I am struggling to download one particular pdf file(having watermark) as an email attachment? I can send it to your email, if you give me your email address?
I tried below piece:-
fp = open(mail.filePath, 'wb')
body = mail.part.get_payload(decode = True)
file_data = base64.encodestring(body).decode()
file_data = file_data.encode('UTF-8')
#file_data = base64.urlsafe_b64decode(mail.part.get_payload(decode=True).encode('UTF-8'))
fp.write(file_data)
fp.close()
Try to use high level lib.
from imap_tools import MailBox
# get all attachments from INBOX and save them to files
with MailBox('imap.my.ru').login('acc', 'pwd', 'INBOX') as mailbox:
for msg in mailbox.fetch():
for att in msg.attachments:
print(att.filename, att.content_type)
with open('C:/1/{}'.format(att.filename), 'wb') as f:
f.write(att.payload)
https://github.com/ikvk/imap_tools

Parsing Attached .MSG Files with Python3

I'm trying to monitor a phishing inbox that could receive both normal emails (i.e. HTML/text based with potential attachments) as well as emails that have a .MSG file attached to it.
The goal is to have users send emails to phishing#company.com and once I parse out the various links (potentially malicious) as well as attachments (also potentially malicious, I'll perform some analysis on them.
The issue I'm running into is the body of the .msg file that is attached.
With the code below, I'm able to pull the to, from, subject, and all links within the original email. It also pulls down any attachments with the .msg file (i.e. on my test I was able to pull down a PDF within the .msg). However, I cannot get any of the to, from, subject, or body of the .msg file.
When I print it out as raw I get some of it in a very ugly format, but apparently with the multi-parts, I'm doing something wrong to get that piece of information.
I'm fairly new to Python so any help would be greatly appreciated.
import imaplib
import base64
import os
import email
from bs4 import BeautifulSoup
server = 'mail.server.com'
email_user = 'phishing#company.com'
email_pass = 'XXXXXXXXXXXX'
output_dir = '/tmp/attachments/'
body = ""
def get_body(msg):
if msg.is_multipart():
return get_body(msg.get_payload(0))
else:
return msg.get_payload(None, True)
def get_attachments(msg):
for part in msg.walk():
if part.get_content_maintype()=='multipart':
continue
if part.get('Content-Disposition') is None:
continue
fileName = part.get_filename()
if bool(fileName):
filePath = os.path.join(output_dir, fileName)
with open(filePath,'wb') as f:
f.write(part.get_payload(decode=True))
mail = imaplib.IMAP4_SSL(server)
mail.login(email_user, email_pass)
mail.select('INBOX')
result, data = mail.search(None, 'UNSEEN')
mail_ids = data[0]
id_list = mail_ids.split()
print(id_list)
for emailid in id_list:
result, email_data = mail.fetch(emailid, '(RFC822)')
raw_email = email_data[0][1]
raw_email_string = raw_email.decode('utf-8')
email_message = email.message_from_string(raw_email_string)
email_from = str(email.header.make_header(email.header.decode_header(email_message['From'])))
email_to = str(email.header.make_header(email.header.decode_header(email_message['To'])))
subject = str(email.header.make_header(email.header.decode_header(email_message['Subject'])))
print('From: ' + email_from)
print('To: ' + email_to)
print('Subject: ' + subject)
get_attachments(raw_email)
for part in email_message.walk():
body = part.get_payload(0)
content = body.get_payload(decode=True)
soup = BeautifulSoup(content, 'html.parser')
for link in soup.find_all('a'):
print('Link: ' + link.get('href'))
break
I got this working with the following code. I basically had to do multiple for loops within the .msg walk and then only pull out the relevant information within the text/html sections.
for emailid in id_list:
result, data = mail.fetch(emailid, '(RFC822)')
raw = email.message_from_bytes(data[0][1])
get_attachments(raw)
#print(raw)
header_from = mail.fetch(emailid, "(BODY[HEADER.FIELDS (FROM)])")
header_from_str = str(header_from)
mail_from = re.search('From:\s.+<(\S+)>', header_from_str)
header_subject = mail.fetch(emailid, "(BODY[HEADER.FIELDS (SUBJECT)])")
header_subject_str = str(header_subject)
mail_subject = re.search('Subject:\s(.+)\'\)', header_subject_str)
#mail_body = mail.fetch(emailid, "(BODY[TEXT])")
print(mail_from.group(1))
print(mail_subject.group(1))
for part in raw.walk():
if part.get_content_type() == 'message/rfc822':
part_string = str(part)
original_from = re.search('From:\s.+<(\S+)>\n', part_string)
original_to = re.search('To:\s.+<(\S+)>\n', part_string)
original_subject = re.search('Subject:\s(.+)\n', part_string)
print(original_from.group(1))
print(original_to.group(1))
print(original_subject.group(1))
if part.get_content_type() == 'text/html':
content = part.get_payload(decode=True)
#print(content)
soup = BeautifulSoup(content, 'html.parser')
for link in soup.find_all('a'):
print('Link: ' + link.get('href'))

Python's Email Message library output not getting accepted by Outlook 365 when i have a named attachments from

I've created a sample function to test sending emails with an attached html file, which i intend to use for reporting on automated test runs in the future (replacing an existing external powershell script). Note that I'm attaching the html file, not using the html as inline text in the body. I'm using our company's mailgun smtp account service to send the email.
I seem to have an issue with Outlook 365 (web hosted - uses the outlook.office.com domain) either rejecting or blocking the sent email, but interestingly the same email is received and accepted by my personal hotmail address (outlook.live.com domain). I've found Outlook 365 blocks or does not accept the email when I attempt to name the file in the email message object. But if I don't name it, it will come through (with a default name of "ATT00001.htm" ).
My code for this is below but they key line seems to be
msg.add_attachment(open_file.read(), maintype='text', subtype='html', filename=filename)
If I drop the filename key it works (but with a default assigned filename) e.g.
msg.add_attachment(open_file.read(), maintype='text', subtype='html')
I have a suspicion there is something in the attachment's header or Content-disposition that Outlook 365 doesn't agree with, but i'm not sure what it is or how to work around.
I'm using the following (Python 3.6.5, on Windows 10 machine, smtplib and email.message seem to be built in)
Here is the code:
import smtplib
from email.message import EmailMessage
import os
def send_mail():
MAILGUN_SMTP_LOGIN = "<my company's mailgun login>"
MAILGUN_SMTP_PASSWORD = "<my company's mailgun password>"
fromaddr = "muppet#sharklasers.com" # the from address seems to be inconsequential
toaddr = ['me#mycompanysdomainusingoffice365.com.au', 'me#hotmail.com']
msg = EmailMessage()
msg.preamble = 'This is preamble. Not sure where it should show in the email'
msg['From'] = fromaddr
msg['To'] = ', '.join(toaddr)
msg['Subject'] = 'Testing attached html results send'
msg.set_content(""" This is a test of attached html """)
filename = 'api_automatedtests_20180903_1341.html'
filepath = os.path.abspath('D:/work/temp/api_automatedtests_20180903_1341.html')
open_file = open(filepath, "rb")
# msg.make_mixed()
msg.add_attachment(open_file.read(), maintype='text', subtype='html', filename=filename)
# msg.add_attachment(open_file.read(), maintype='text', subtype='html')
server = smtplib.SMTP(host="smtp.mailgun.org", port=587)
server.ehlo()
server.starttls()
server.login(MAILGUN_SMTP_LOGIN, MAILGUN_SMTP_PASSWORD)
server.set_debuglevel(1)
server.send_message(msg)
server.quit()
if __name__ == "__main__":
send_mail()
What I've tried
Tried sending with the same code using a textfile (with appropriate types). e.g.
msg.add_attachment(open_file.read(), maintype='text', subtype='plain', filename=filename)
Result: This works as expected (comes through with the given name - the filename is a string variable e.g. testfile.txt)
adding msg.make_mixed() to make sure it is identified as a multipart message. Result: No effect
Turning on the smtp debug level 1, Result: Mailgun says that everything has worked fine (and the messages do appear as expected in my hotmail account)
Not using the filename key in the msg.add_attachment call.
Result: This works the attachment comes through at ATT00001.htm
Interestingly the default name is *.htm while the filename I'm trying to use is *.html
Tried using a filename with *.htm and a subtype of 'htm' (instead of html)
Result: Same as for html (received on hotmail but not on outlook 365)
Tried using the generic types of maintype=''application', subtype='octet-stream'.
e.g. msg.add_attachment(open_file.read(), maintype='application', subtype='octet-stream', filename=filename)
Result: Same as for html (received on hotmail but not on outlook 365)
Tried using mimetypes.guess as shown in this link
https://docs.python.org/3.6/library/email.examples.html
ctype, encoding = mimetypes.guess_type(path)
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)
with open(path, 'rb') as fp:
msg.add_attachment(fp.read(),
maintype=maintype,
subtype=subtype,
filename=filename)
Result: It's determined as maintype='text', subtype='html' and I get the same result as with my original code (ie arrives in hotmail but blocked by 365).
Checking my spam and clutter folders - was not there
Any suggestions on why the use of filename would be breaking it?
Update
After sending to a other email addresses with various providers I discovered:
1) muppet#sharklasers.com was not a trusted sender (can change this)
2) I discovered the attachment was being flagged as unsafe. The html file comes from pytest's html report with the single file option. It contains javascript for row expanders. Gmail warns the attachment may not be safe (office 365 just straight out blocks the email altogether).
Not sure how to work around 2). I can email the same file to myself between outlook 365 and gmail and vice versa and the file doesn't get blocked. It only get's blocked when I use the above script using python's libraries and Mailgun SMTP. I suspect there is something I need to change in the email header to get around this. But I don't know what.
There seems to be some connection between trying to add the filename and the attachment being marked as unsafe
Okay I figured it out. The problem was the content-type needed to include "name=filename" in it's value.
Also I needed to use maintype='multipart', subtype='mixed'.
I have 2 solutions.
solution 1
import smtplib
from email.message import EmailMessage
import os
def send_mail(body_text, fromaddr, recipient_list, smtp_login, smtp_pass, file_path):
msg = EmailMessage()
msg.preamble = 'This is preamble. Not sure where it should show'
msg['From'] = fromaddr
msg['To'] = ', '.join(recipient_list)
msg['Subject'] = 'API Testing results'
msg.set_content(body_text)
filename = os.path.basename(file_path)
open_file = open(file_path, "rb")
msg.add_attachment(open_file.read(), maintype='multipart', subtype='mixed; name=%s' % filename, filename=filename)
server = smtplib.SMTP(host="smtp.mailgun.org", port=587)
server.ehlo()
server.starttls()
server.login(smtp_login, smtp_pass)
server.send_message(msg)
server.quit()
if __name__ == "__main__":
smtp_login = "<my smtp login>"
smtp_pass = "<my smtp password>"
recipient_list = ['user1#mycompany.com.au', 'user2#mycompany.com.au']
file_path = os.path.abspath('D:/work/temp/api_automatedtests_20180903_1341.html')
body_text = "test results for 03/09/2018 "
fromaddr = 'autotesting#mycompany.com.au'
send_mail(body_text=body_text, recipient_list=recipient_list, smtp_login=smtp_login, smtp_pass=smtp_pass,
file_path=file_path)
solution 2 (according to the documentation using the email.mime libraries is a legacy solution and the EmailMessage method is supposed to be used in preference.
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
import os
def send_mail(body_text, fromaddr, recipient_list, smtp_login, smtp_pass, file_path):
msg = MIMEMultipart()
msg['From'] = fromaddr
msg['To'] = ', '.join(recipient_list)
msg['Subject'] = "Sending API test results"
msg.attach(MIMEText(body_text, 'plain'))
filename = os.path.basename(file_path)
attachment = open(file_path, "rb")
part = MIMEBase('multipart', 'mixed; name=%s' % filename)
part.set_payload(attachment.read())
encoders.encode_base64(part)
part.add_header('Content-Disposition', "attachment; filename= %s" % filename)
msg.attach(part)
server = smtplib.SMTP(host="smtp.mailgun.org", port=587)
server.starttls()
server.login(smtp_login, smtp_pass)
text = msg.as_string()
server.set_debuglevel(1)
server.sendmail(fromaddr, recipient_list, text)
server.quit()
if __name__ == '__main__':
smtp_login = "<my smtp login>"
smtp_pass = "<my smtp password>"
recipient_list = ['user1#mycompany.com.au', 'user2#mycompany.com.au']
file_path = os.path.abspath('D:/work/temp/api_automatedtests_20180903_1341.html')
body_text = " Api test results for 03/09/2018 "
fromaddr = "autotest#mycompany.com.au"
send_mail(body_text=body_text, fromaddr=fromaddr, recipient_list=recipient_list, smtp_login=smtp_login, smtp_pass=smtp_pass,
file_path=file_path)

Resources