I have the code below that works perfectly fine when I use it to send a txt file, an image or audio. However, it doesn't work when I try to send zip files, rar files, or any other file that doesn't have its own MIME (which is not related to MIMEText, MIMEImage or MIMEAudio).
In conclusion, whenever I reach the else part (the MIMEBase command) I do something wrong and get the error:
e.send_mail(TARGET, SUBJECT, "file.zip")
msg.attach(part) //two lines after the else's end
AttributeError: 'str' object has no attribute 'append'
the code:
def send_mail(self, target, subject, *file_names):
"""
send a mail with files to the target
#param target: send the mail to the target
#param subject: mail's subject
#param file_names= list of files to send
"""
msg = email.MIMEMultipart.MIMEMultipart()
msg['From'] = self.mail
msg['To'] = email.Utils.COMMASPACE.join(target)
msg['Subject'] = subject
for file_name in file_names:
f = open(file_name, 'rb')
ctype, encoding = mimetypes.guess_type(file_name)
if ctype is None or encoding is not None:
ctype = 'application/octet-stream'
maintype, subtype = ctype.split('/', 1)
# in case of a text file
if maintype == 'text':
part = MIMEText(f.read(), _subtype=subtype)
# in case of an image file
elif maintype == 'image':
part = MIMEImage(f.read(), _subtype=subtype)
# in case of an audio file
elif maintype == 'audio':
part = MIMEAudio(f.read(), _subtype=subtype)
# any other file
else:
part = MIMEBase(maintype, subtype)
msg.set_payload(f.read())
part.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(file_name))
msg.attach(part)
f.close()
# ssl server doesn't support or need tls, so don't call server_ssl.starttls()
self.server_ssl.sendmail(self.mail, target, msg.as_string())
#server_ssl.quit()
self.server_ssl.close()
I have seen similar codes but I don't understand what is wrong with mine.
could you please explain me what I am messing up?
thank you!
if it helps anyone here is the answer:
the main problem was that I changed the msg payload instead of the zip file's
def send_mail(self, target, subject, body, *file_names):
"""
send a mail with files to the target
#param target: send the mail to the target
#param subject: mail's subject
#param file_names= list of files to send
"""
msg = MIMEMultipart()
msg['From'] = self.mail
msg['To'] = target
msg['Subject'] = subject
body_part = MIMEText(body, 'plain')
msg.attach(body_part)
for file_name in file_names:
f = open(file_name, 'rb')
ctype, encoding = mimetypes.guess_type(file_name)
if ctype is None or encoding is not None:
ctype = 'application/octet-stream'
maintype, subtype = ctype.split('/', 1)
# in case of a text file
if maintype == 'text':
part = MIMEText(f.read(), _subtype=subtype)
# in case of an image file
elif maintype == 'image':
part = MIMEImage(f.read(), _subtype=subtype)
# in case of an audio file
elif maintype == 'audio':
part = MIMEAudio(f.read(), _subtype=subtype)
# any other file
else:
part = MIMEBase(maintype, subtype)
part.set_payload(f.read())
encoders.encode_base64(part)
part.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(file_name))
msg.attach(part)
f.close()
# ssl server doesn't support or need tls, so don't call server_ssl.starttls()
self.server_ssl.sendmail(self.mail, target, msg.as_string())
self.server_ssl.quit()
Related
import imaplib
import email
from email.header import decode_header
import webbrowser
import os
# account credentials
username = "example#stack.com"
password = "exapleforstack"
imap_server = "imap.one.com"
def clean(text):
# clean text for creating a folder
return "".join(c if c.isalnum() else "_" for c in text)
# create an IMAP4 class with SSL
imap = imaplib.IMAP4_SSL(imap_server)
# authenticate
imap.login(username, password)
status, messages = imap.select("INBOX")
imap.search(None, 'SUBJECT', '"exampleforstack"')
# number of top emails to fetch
N = 3
# total number of emails
messages = int(messages[0])
for i in range(messages, messages-N, -1):
# fetch the email message by ID
res, msg = imap.fetch(str(i), "(RFC822)")
for response in msg:
if isinstance(response, tuple):
# parse a bytes email into a message object
msg = email.message_from_bytes(response[1])
# decode the email subject
subject, encoding = decode_header(msg["Subject"])[0]
if isinstance(subject, bytes):
# if it's a bytes, decode to str
subject = subject.decode(encoding)
# decode email sender
From, encoding = decode_header(msg.get("From"))[0]
if isinstance(From, bytes):
From = From.decode(encoding)
print("Subject:", subject)
print("From:", From)
# if the email message is multipart
if msg.is_multipart():
# iterate over email parts
for part in msg.walk():
# extract content type of email
content_type = part.get_content_type()
content_disposition = str(part.get("Content-Disposition"))
try:
# get the email body
body = part.get_payload(decode=True).decode()
except:
pass
if content_type == "text/plain" and "attachment" not in content_disposition:
# print text/plain emails and skip attachments
print(body)
elif "attachment" in content_disposition:
# download attachment
filename = part.get_filename()
if filename:
folder_name = clean(subject)
if not os.path.isdir(folder_name):
# make a folder for this email (named after the subject)
os.mkdir(folder_name)
filepath = os.path.join(folder_name, filename)
# download attachment and save it
open(filepath, "wb").write(part.get_payload(decode=True))
else:
# extract content type of email
content_type = msg.get_content_type()
# get the email body
body = msg.get_payload(decode=True).decode()
if content_type == "text/plain":
# print only text email parts
print(body)
if content_type == "text/html":
# if it's HTML, create a new HTML file and open it in browser
folder_name = clean(subject)
if not os.path.isdir(folder_name):
# make a folder for this email (named after the subject)
os.mkdir(folder_name)
filename = "index.html"
filepath = os.path.join(folder_name, filename)
# write the file
open(filepath, "w").write(body)
# open in the default browser
webbrowser.open(filepath)
print("="*100)
# close the connection and logout
imap.close()
imap.logout()
I tried using the search method a couple different ways, like:
res, msg = imap.search(None, 'SUBJECT', "example")
and
res, msg = imap.search(None, 'SUBJECT, "example"')
but my code just gives an error and automatically fetches the most recent 3.
I've tried replacing the line:
res, msg = imap.fetch(str(i), "(RFC822)")
with
res, msg = imap.search(None, 'SUBJECT', '"example"')
but the program returns nothing at all.
how would I go about implementing the search I got this code from pythoncode and altered its credentials.
But I'm not sure why I can't implement the search function.
i am trying to make a script which whill extract attachments from base64 encoded files.
base64 files are on amazon s3. Idea was, to take the data from file on s3, save it in temp file and pass it to the 'extraction' part of the code.
Script below perfectly works with a single file which is stored locally on my pc.
from email.message import EmailMessage
from email.header import decode_header
with open('mail2', 'r') as efile: # mail2 - это название файла который на s3 лежит
msg = EmailMessage.message_from_file(efile)
subject, encoding = decode_header(msg["Subject"])[0]
print(subject)
From, encoding = decode_header(msg.get("From"))[0]
if isinstance(From, bytes):
From = From.decode(encoding)
print(From)
if msg.is_multipart():
# iterate over email parts
for part in msg.walk():
# extract content type of email
content_type = part.get_content_type()
content_disposition = str(part.get("Content-Disposition"))
try:
# get the email body
body = part.get_payload(decode=True).decode()
except:
pass
if "attachment" in content_disposition:
# download attachment
filename = part.get_filename()
if filename:
#filepath = os.path.join(folder_name, filename)
# download attachment and save it
open(filename, "wb").write(part.get_payload(decode=True))
else:
pass
In order to avoid constant uploadings from s3, i decided to get the body of files and transfer them to temp file using boto3.
my_bucket = s3.Bucket('bucket_name')
substring = "reestrs"
for obj in my_bucket.objects.all():
#получение данных
if re.search(substring, obj.key):
raw_data = obj.get()['Body'].read()
temp = tempfile.NamedTemporaryFile()
temp.write(raw_data)
print(temp)
with open(temp.name, 'r') as efile:
msg = email.message_from_file(temp.name)
subject, encoding = decode_header(msg["Subject"])[0]
print(subject)
From, encoding = decode_header(msg.get("From"))[0]
if isinstance(From, bytes):
From = From.decode(encoding)
print(From)
if msg.is_multipart():
# iterate over email parts
for part in msg.walk():
# extract content type of email
content_type = part.get_content_type()
content_disposition = str(part.get("Content-Disposition"))
try:
# get the email body
body = part.get_payload(decode=True).decode()
except:
pass
if "attachment" in content_disposition:
# download attachment
filename = part.get_filename()
if filename:
#filepath = os.path.join(folder_name, filename)
# download attachment and save it
open(filename, "wb").write(part.get_payload(decode=True))
temp.close()
else:
pass
Whenever i launch this script, result is
File "rum_fin.py", line 42, in <module>
msg = email.message_from_file(temp.name)
File "/usr/lib/python3.8/email/__init__.py", line 54, in message_from_file
return Parser(*args, **kws).parse(fp)
File "/usr/lib/python3.8/email/parser.py", line 53, in parse
data = fp.read(8192)
AttributeError: 'str' object has no attribute 'read'
I've tested scipt above with a single file. Unfortunately, result is the attachment with base64 name insted of normal csv/png/txt
obj = s3.Object('bucket', 'filename')
raw_data = obj.get()['Body'].read()
temp = tempfile.NamedTemporaryFile()
temp.write(raw_data)
print(temp)
# In[7]:
with open(temp.name, 'r') as efile: # mail2 - это название файла который на s3 лежит
msg = email.message_from_file(efile)
subject, encoding = decode_header(msg["Subject"])[0]
print(subject)
From, encoding = decode_header(msg.get("From"))[0]
if isinstance(From, bytes):
From = From.decode(encoding)
print(From)
I suppose, it's something to do with the way of passing the data from actual file to the temp file. Any ideas how to solve it?
I need resend the email from inbox a#example.com email to b#example.org. Is it possible to send the payload without decoding and encoding it when creating a new email? Whether text, HTML, or attachments. You just need to send the original letter from another mailbox without forwarding notifications.
mail = imaplib.IMAP4_SSL(server)
mail.login(login, password)
mail.list()
mail.select("inbox")
result, data = mail.search(None, "UNSEEN")
if result == 'OK':
for num in data[0].split():
result, data = mail.fetch(num, "(RFC822)")
raw_email = data[0][1]
try:
oldmsg = email.message_from_string(raw_email)
except TypeError:
oldmsg = email.message_from_bytes(raw_email)
newmsg = email.mime.multipart.MIMEMultipart(boundary="----")
newmsg['Subject'] = decode_mime_words(oldmsg['Subject'])
newmsg['From'] = login
newmsg['Date'] = strftime("%a, %d %b %Y %H:%M:%S %z")
newmsg['User-Agent'] = "Roundcube Webmail"
newmsg['To'] = to
#part = MIMEBase('application', 'octet-stream')
if oldmsg.is_multipart():
for oldpayload in oldmsg.get_payload():
print(oldpayload.get_payload())
print("-_________________-")
else:
print(oldmsg.get_payload())
# get attachments
for part in oldmsg.walk():
if part.get_content_maintype() == 'multipart':
continue
if part.get('Content-Disposition') is None:
continue
fileName = part.get_filename()
if (fileName[:11] == '=?koi8-r?B?') or (fileName[:11] == '=?KOI8-R?B?'):
fileName = base64.b64decode(fileName[11:]).decode(
'KOI8-R')
elif (fileName[:10] == '=?utf-8?B?') or (fileName[:10] == '=?UTF-8?B?'):
fileName = base64.b64decode(fileName[10:])
fileName = fileName.decode('utf-8')
# name attachemnts
if bool(fileName):
filePath = os.path.join('attach', fileName)
if not os.path.isfile(filePath):
fp = open(filePath, 'wb')
fp.write(part.get_payload(decode=True))
part = MIMEApplication(open(filePath, 'rb').read())
part.add_header('Content-Disposition', 'attachment', filename=fileName)
newmsg.attach(part)
fp.close()
I created a function in python 3.7 to create draft emails with attached spreadsheets for all the clients.
The code creates that draft email correctly and attaches the files (.xlsx) but these files can no more be opened on gmail. Not sure if this is specific to the encoding that I am using.
(The files can be opened successfully on my desktop computer)
Below is the code:
def CreateMessageWithAttachment(sender, to, cc, subject, message_text, file_dir, filename):
"""Create email messages with attachment, for sending to partners"""
message = MIMEMultipart('alternative')
message['to'] = to
message['from'] = sender
message['cc'] = cc
message['subject'] = subject
msg = MIMEText(message_text, 'html')
message.attach(msg)
path = os.path.join(file_dir, filename)
content_type, encoding = mimetypes.guess_type(path)
if content_type is None or encoding is not None:
content_type = 'application/octet-stream'
main_type, sub_type = content_type.split('/', 1)
# main_type, sub_type = 'gzip', None
fp = open(path, 'rb')
msg = MIMEBase(main_type, sub_type)
msg.set_payload(fp.read())
fp.close()
msg.add_header('Content-Disposition', 'attachment', filename=filename)
message.attach(msg)
raw = base64.urlsafe_b64encode(message.as_bytes())
raw = raw.decode()
return {'raw': raw}
#return {'raw': base64.urlsafe_b64encode(message.as_bytes()).decode()}
I am assuming, the encoding of the files is what corrupting the spreadsheets attached. Any help would be appreciated.
Try changing these two lines:
msg = MIMEBase(main_type, sub_type)
msg.set_payload(fp.read())
To just this:
msg = MIMEApplication(fp.read(), sub_type)
I created a script who send mail whith a specific output took from a server.
I splited this output and each element I sent it to a html cell.
I also created a header for the table what is looks like that:
def get_html_table_header(*column_names):
header_string = '<tr width=79 style="background:#3366FF;height:23.25pt;font-size:8.0pt;font-family:Arial,sans-serif;color:white;font-weight:bold;" >'
for column in column_names:
if column is not None:
header_string += '<td>' + column + '</td>'
header_string += '</tr>'
return header_string
def get_concrete_html_table_header():
return get_html_table_header('Num. Row','Cell1','Cell2','Cell3','Comment (enter your feedback below)','Cell4','Cell5','Cell6','Cell7','Cell8','Cell9','Cell10')
When I print the result of this function in linux konsole, it looks like that:
<tr width=79 style="background:#3366FF;height:23.25pt;font-size:8.0pt;font-family:Arial,sans-serif;color:white;font-weight:bold;" ><td>Num. Row</td><td>Cell1</td><td>Cell2</td><td>Cell3</td><td>Comment (enter your feedback below)</td><td>Cell4</td><td>Cell5</td><td>Cell6</td><td>Cell7</td><td>Cell8</td><td>Cell9</td><td>Cell10</td></tr>
When I receive the email, source looks like that:
<tr width="79" style="background:#3366FF;height:23.25pt;font-size:8.0pt;font-family:Arial,sans-serif;color:white;font-weight:bold;"><td>Num. Row</td><td>Cell1</td><td>Cell2</td><td>Cell3</td><td>Comment (enter your feedback below)</td><td>Cell4</td><td>Cell5</td><td>Cell6</td><td>Cell7</td><td>Cell8</td><td>Cell9</td>< td>Cell10</td></tr>
To build email body I`m using function:
def build_email_body(CRs_list):
global criterial_number
if 0 == len(CRs_list):
return None
email_body = ''
email_body += '<html><head><title>My Title</title></head><body>'
email_body += '<p align="center"><font color="#176b54" size="+2"><b>Some info</b></font></p>'
email_body += '<p align="center"><font color="#176b54" size="+1">Another info</font></p>'
email_body += '<table align="center" BORDER=1 CELLSPACING=2 CELLPADDING=2 COLS=3 WIDTH="100%">'
email_body += get_concrete_html_table_header()
for CR in CRs_list:
email_body += get_html_table_row()#create row for every output received(11 cells for every output, according with the header)
email_body += '</table>'
email_body += '</table><br><p align="left"><font color="#176b54" size="+1"><b>=> This is an automatic generated email via script<br>'
email_body += '<br><br>Have a nice day!</b></font></p><br></body></html>'
return email_body
To send email I`m using function:
def send_email(body, recipients, subject, file):
#inform just sender
if None == body:
body = "WARNING -> NO entries retrieved after 5 retries<br>CRAU output:<br>" + dct_newCRs_output + "<br>" + duration
#override recipients to not set junk info
recipients = sender
email = Email(SMTP_SERVER, SENDER, recipients, _CC, subject, body, 'html', file)
email.send()
send() is imported from class Email:
import os, smtplib
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
import mimetypes
class Email:
__config = {}
def __init__(self, smtp_server, sender, recipients, cc, subject, body, body_type, attachments=None):
self.__config = {'smtp_server': smtp_server,
'sender': sender,
'recipients': recipients,
'cc': cc,
'subject': subject,
'body':body,
'body_type':body_type, #plain|html
'attachments':attachments #list of files
}
def getSmtpServer(self):
return self.__config.get('smtp_server')
def getSender(self):
return self.__config.get('sender')
def getRecipients(self):
return self.__config.get('recipients')
def getCc(self):
return self.__config.get('cc')
def getSubject(self):
return self.__config.get('subject')
def getBody(self):
return self.__config.get('body')
def getBodyType(self):
return self.__config.get('body_type')
def getAttachments(self):
return self.__config.get('attachments')
def setSmtpServer(self, host):
self.__config['smtp_server'] = smtp_server
return self
def setSender(self, sender):
self.__config['sender'] = sender
return self
def setRecipients(self, recipients):
self.__config['recipients'] = recipients
return self
def setCc(self, cc):
self.__config['cc'] = cc
return self
def setSubject(self, subject):
self.__config['subject'] = subject
return self
def setBody(self, body):
self.__config['body'] = body
return selfMIMEMultipart
def setBodyType(self, body_type):
self.__config['body_type'] = body_type
return self
def setAttachments(self, attachments):
self.__config['attachments'] = attachments
return self
def attachFilesToEmail(self, attachments, msg):
if None == attachments:
tmpmsg = msg
msg = MIMEMultipart()
msg.attach(tmpmsg)
if None != attachments:
for fname in attachments:
if not os.path.exists(fname):
print "File '%s' does not exist. Not attaching to email." % fname
continue
if not os.path.isfile(fname):
print "Attachment '%s' is not a file. Not attaching to email." % fname
continue
# Guess at encoding type
ctype, encoding = mimetypes.guess_type(fname)
if ctype is None or encoding is not None:
# No guess could be made so use a binary type.
ctype = 'application/octet-stream'
maintype, subtype = ctype.split('/', 1)
if maintype == 'text':
fp = open(fname)
attach = MIMEText(fp.read(), _subtype=subtype)
fp.close()
elif maintype == 'image':
fp = open(fname, 'rb')
attach = MIMEImage(fp.read(), _subtype=subtype)
fp.close()
elif maintype == 'audio':
fp = open(fname, 'rb')
attach = MIMEAudio(fp.read(), _subtype=subtype)
fp.close()
else:
fp = open(fname, 'rb')
attach = MIMEBase(maintype, subtype)
attach.set_payload(fp.read())
fp.close()
# Encode the payload using Base64
encoders.encode_base64(attach)
# Set the filename parameter
filename = os.path.basename(fname)
attach.add_header('Content-Disposition', 'attachment', filename=filename)
msg.attach(attach)
def send(self):
# Create message container - the correct MIME type is multipart/alternative.
msg = MIMEMultipart('alternative')
msg['Subject'] = self.getSubject()
msg['From'] = self.getSender()
msg['To'] = self.getRecipients()
msg['CC'] = self.getCc()
# Record the MIME types of both parts - text/plain and text/html.
#part1 = MIMEText(text, 'plain')
#part2 = MIMEText(html, 'html')
part = MIMEText(self.getBody(), self.getBodyType())
# 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(part)
# Add attachments, if any
self.attachFilesToEmail(self.getAttachments(), msg)
# Send the message via local SMTP server.
s = smtplib.SMTP(self.getSmtpServer())
# sendmail function takes 3 arguments: sender's address, recipient's address
# and message to send - here it is sent as one string.
s.sendmail(self.getSender(), (self.getRecipients() + self.getCc()).split(","), msg.as_string())
s.quit()
I hope is enough information.
Can someone explain to me, why is happening this and how can I fix it?
Your code looks correct, the problem is elsewhere.
< is what you get when you add < as text to a HTML document (since < means "start new element", you need to escape this character in plain text).
The interesting part here is why does it happen only once in the whole string. If all the < had been replaced, my guess would be that you accidentally added the table as text to the HTML body of the mail.
Maybe the space in < td> is a clue: Mails shouldn't have more than 72 characters per line. So maybe some mail server wraps the HTML? Outlook is known to mess a lot with the mails it receives.
Try to send the HTML code as multipart attachment. See Sending HTML email using Python