Attach is not valid on a message with a non-multipart payload - python

I'm currently writing an application that needs to load an email into memory, add an attachment to it and send the same email back to the user. This has worked fine in the past, however I'm currently facing an issue where an email is sent in Content-Transfer-Encoding of base64.
I found a script online that converts a built in Python email message object to multipart, however whenever I do this, the original email doesn't get sent as base64 and now appears in plain text whenever I re-send the email.
Does anyone know how I could fix it? The (mostly redacted) email has been added and the code I used to convert the email to multipart. Thanks for the help in advance.
E-Mail
# Before conversion
From: ██████████ <█████#██████.com>
To: ████████ <███████#██████.com>
Subject: █████████
Date: Fri, ██ ███ 2017 00:18:17 +0200
Content-Language: nl-NL
Content-Type: text/html; charset="utf-8"
Content-Transfer-Encoding: base64
MIME-Version: 1.0
cmVkYWN0ZWRyZWRhY3RlZHJlZGFjdGVkcmVkYWN0ZWRyZWRhY3RlZHJlZGFjdGVkcmVkYWN0ZWRy
ZWRhY3RlZHJlZGFjdGVkcmVkYWN0ZWRyZWRhY3RlZHJlZGFjdGVkcmVkYWN0ZWRyZWRhY3RlZHJl
ZGFjdGVkcmVkYWN0ZWRyZWRhY3RlZHJlZGFjdGVkcmVkYWN0ZWRyZWRhY3RlZA0K
# After conversion
Content-Type: multipart/mixed; boundary="===============0883378942=="
MIME-Version: 1.0
From: ██████████ <█████#██████.com>
To: ████████ <███████#██████.com>
Subject: █████████
Date: Fri, ██ ███ 2017 00:18:17 +0200
Content-Language: nl-NL
Content-Transfer-Encoding: base64
MIME-Version: 1.0
--===============0883378942==
Content-Type: text/html; charset="utf-8"
cmVkYWN0ZWRyZWRhY3RlZHJlZGFjdGVkcmVkYWN0ZWRyZWRhY3RlZHJlZGFjdGVkcmVkYWN0ZWRy
ZWRhY3RlZHJlZGFjdGVkcmVkYWN0ZWRyZWRhY3RlZHJlZGFjdGVkcmVkYWN0ZWRyZWRhY3RlZHJl
ZGFjdGVkcmVkYWN0ZWRyZWRhY3RlZHJlZGFjdGVkcmVkYWN0ZWRyZWRhY3RlZA0K
--===============0883378942==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="foo.txt"
Hello world
--===============0883378942==--
Plain to Multipart code
# If this method is not used on an email object
# A `TypeError` is raised with the message "Attach is not valid on a message with a non-multipart payload"
def mail_to_multipart(mail):
"""
Convert an email to a multipart email
:param mail: Email object
:return: None
"""
if mail.is_multipart():
return mail
mail_new = MIMEMultipart("mixed")
headers = list((k, v) for (k, v) in mail.items() if k != "Content-Type")
for k, v in headers:
mail_new[k] = v
for k, v in headers:
del mail[k]
mail_new.attach(mail)
return mail_new

Apparently the issue was related to the Content-Transfer-Encoding header not remaining in the old multipart block, by changing the following line:
headers = list((k, v) for (k, v) in mail.items() if k != "Content-Type")
To this:
headers = list((k, v) for (k, v) in mail.items() if k not in ("Content-Type", "Content-Transfer-Encoding"))
Fixed the issue

Related

Read all email messages from a text file containing multiple email messages using python

I have a single txt file which contains multiple email messages. Attached a sample text file which contains multiple email (.eml format)
From details
Return-Path: <emailaddress>
Delivered-To: email#address.com
Received: details
Received-SPF: details
Authentication-Results: details
Received: details
ARC-Seal: details
ARC-Message-Signature: details
Received: details
From: details
To: details
Subject: details
Thread-Topic: details
Thread-Index: details
Date: details
Message-ID:details
Accept-Language: en-US
Content-Language: en-US
Content-Type: multipart/mixed;
boundary="_004_DM5PR13MB138821372E6760B35B854B0CB74A0DM5PR13MB1388namp_"
MIME-Version: 1.0
details
--_004_DM5PR13MB138821372E6760B35B854B0CB74A0DM5PR13MB1388namp_
Content-Type: multipart/alternative;
boundary="_000_DM5PR13MB138821372E6760B35B854B0CB74A0DM5PR13MB1388namp_"
--_000_DM5PR13MB138821372E6760B35B854B0CB74A0DM5PR13MB1388namp_
Content-Type: text/plain; charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable
test with FA
--_000_DM5PR13MB138821372E6760B35B854B0CB74A0DM5PR13MB1388namp_
Content-Type: text/html; charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable
<html>
<head>
<body>
details
</body>
</html>
--_000_DM5PR13MB138821372E6760B35B854B0CB74A0DM5PR13MB1388namp_--
--_004_DM5PR13MB138821372E6760B35B854B0CB74A0DM5PR13MB1388namp_
Content-Type: details
Content-Description: details
Content-Disposition: attachment; filename="p2_eml.eml"; size=37836;
creation-date="Tue, 04 Aug 2020 10:48:34 GMT";
modification-date="Tue, 04 Aug 2020 10:48:34 GMT"
Content-Transfer-Encoding: base64
base64encoded data
--_004_DM5PR13MB138821372E6760B35B854B0CB74A0DM5PR13MB1388namp_--
From details <--- 2nd email starts --->
Return-Path: <emailaddress>
Delivered-To: email#address.com
Received: details
Received-SPF: details
Authentication-Results: details
Received: details
more details
Using email lib by python, it can grab only 1st email message, but does not process rest of email messages.
But this creates a single msg object that refers to 1st email message in the txt file.
Is there a way i can fetch all email messages from txt file and process one by one?
msg = email.message_from_file(message) this only fetches the 1st email message object. Does not fetch the next message obj.
Code tried:
msg = email.message_from_file(message)
# Dump extra information To, From, Date, Subject header values.
dump_extra_info(msg)
decoded_content_list = []
for part in msg.walk():
charset = part.get_content_charset();
if part.get_content_type() == "application/octet-stream":
logger.info("found content disposition returning ...")
continue
decoded_data = part.get_payload(decode=True)
if decoded_data and charset is not None:
utf8decoded = decoded_data.decode(charset)
decoded_content_list.append(utf8decoded)
return ' '.join(decoded_content_list)```
```

MIME Attachments won't send with Subject Line

I'm having trouble with a bit of code sending an email with attachments AND a Subject line.
# Code exerpt from Oli: http://stackoverflow.com/questions/3362600/how-to-send-email-attachments-with-python
# Emails aren't sending with a subject--need to fix this.
def send_mail(self, send_from, send_to, subject, text, files=None, server="localhost"):
assert isinstance(send_to, list)
msg = MIMEMultipart(
Subject=subject,
From=send_from,
To=COMMASPACE.join(send_to),
Date=formatdate(localtime=True)
)
msg.attach(MIMEText(text))
for f in files or []:
with open(f, "rb") as fil:
msg.attach(MIMEApplication(
fil.read(),
Content_Disposition='attachment; filename="%s"' % basename(f),
Name=basename(f)
))
smtp = smtplib.SMTP(server)
smtp.sendmail(send_from, send_to, msg.as_string())
smtp.close()
This code sends an email fine, but it is not deliminating the 'Subject' line and the emails it sends have a subject line of "NO SUBJECT.' Here's what it shows when I print the first part of the MIME msg:
From nobody Thu Oct 29 16:17:38 2015
Content-Type: multipart/mixed; date="Thu, 29 Oct 2015 16:17:38 +0000";
to="me#email.com";
from="someserver#somewhere.com"; subject="TESTING";
boundary="===============0622475305469306134=="
MIME-Version: 1.0
--===============0622475305469306134==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Here we go, oh! ho! ho!
--===============0622475305469306134==
Content-Type: application/octet-stream; Content- Disposition="attachment;
filename=\"Log_Mill.py\""; Name="Log_Mill.py"
MIME-Version: 1.0
Content-Transfer-Encoding: base64
I might be able to figure it out if I plug away for hours and hours, but I'm hoping to avoid the extra work for such a trivial fix.
Any help is appreciated!
You are assigning the Subject etc. as attributes of the multipart container, but that's incorrect. The headers you want to specify should be passed to the msg itself as headers instead, like this:
msg = MIMEMultipart()
msg['Subject'] = subject
msg['From'] = send_from
msg['To'] = COMMASPACE.join(send_to)
msg['Date'] = formatdate(localtime=True)
The output should look more like
From nobody Thu Oct 29 16:17:38 2015
Date: Thu, 29 Oct 2015 16:17:38 +0000
To: <me#email.com>
From: <someserver#somewhere.com>
Subject: TESTING
Content-Type: multipart/mixed;
boundary="===============0622475305469306134=="
MIME-Version: 1.0
--===============0622475305469306134==
Content-Type: text/plain; .......
You could also use a package specialised for writing HTML emails, showing pictures inline and easily attach files!
The package I'm referring to is yagmail and I'm the developer/maintainer.
import yagmail
yag = yagmail.SMTP('email#email.com', 'email_pwd')
file_names = ['/local/path/f.mp3', '/local/path/f.txt', '/local/path/f.avi']
yag.send('to#email.com', 'Sample subject', contents = ['This is text'] + filenames)
That's all there is to it.
Use pip install yagmail to obtain your copy.
Contents can be a list where you also add text, you can just only have the file_names as contents, awesome no?
It reads the file, magically determines the encoding, and attached it :)
Read the github for other tricks like passwordless scripts, aliasing and what not.

Not all MIME text showed on mail server

I'm using Python email.mime lib to write emails, and I created two MIMEText objects and then attached them to Message as text (not as attachment), and as a result I got the MIME document as follows, as you can see there are two text objects, one is of type plain and the other is of type html, my question is that I can only see the latter text object (here is the html) in some mail clients, while I can see both text objects in some other mail clients (for example, live.com), so what caused this?
Content-Type: multipart/mixed; boundary="===============0542266593=="
MIME-Version: 1.0
FROM: john.smith#NYU.com
TO: john.smith#live.com, john.smith#gmail.com
SUBJECT: =?utf-8?q?A_Greeting_From_Postman?=
--===============0542266593==
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64
SGkhCkhvdyBhcmUgeW91PwpIZXJlIGlzIHRoZSBsaW5rIHlvdSB3YW50ZWQ6Cmh0dHA6Ly93d3cu
cHl0aG9uLm9yZw==
--===============0542266593==
Content-Type: text/html; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64
ICAgICAgICA8aHRtbD4KICAgICAgICAgIDxoZWFkPjwvaGVhZD4KICAgICAgICAgIDxib2R5Pgog
ICAgICAgICAgICA8cD5IaSE8YnI+CiAgICAgICAgICAgICAgIEhvdyBhcmUgeW91Pzxicj4KICAg
ICAgICAgICAgICAgSGVyZSBpcyB0aGUgPGEgaHJlZj0iaHR0cDovL3d3dy5weXRob24ub3JnIj5s
aW5rPC9hPiB5b3Ugd2FudGVkLgogICAgICAgICAgICA8L3A+CiAgICAgICAgICA8L2JvZHk+CiAg
ICAgICAgPC9odG1sPgogICAgICAgIA==
--===============0542266593==--
You have specified 'multipart/mixed' as the mime type. If you want only one item to be displayed, specify 'multipart/alternative', as so:
email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.encoders import encode_base64
# Note: 'alternative' means only display one of the items.
msg = MIMEMultipart('alternative')
msg['Subject'] = "Hello"
msg['From'] = 'me#example.com'
msg['To'] = 'you#example.com'
msg.attach(MIMEText('Hello!', 'plain'))
msg.attach(MIMEText('<b>Hello!</b>', 'html'))
# Not required, but you had it in your example, so I kept it.
for i in msg.get_payload():
encode_base64(i)
print msg.as_string()

How can I send Inline images in Email with Python/Django? [duplicate]

This question already exists:
How to send Inline images in Email with Python/Django?
Closed 9 years ago.
I'm trying to send an email with an inline image using Python/Django.
Here is the code showing how I am doing it.
It's still in development. So all it is meant to do for now is send a dummy email message with a picture of a bumble bee embedded in it.
Yet when I receive the email in my Gmail inbox, I see only the following text-based email. The various Mime parts of the email show up in the payload of the email as text.
I clicked the "Show Original" button in Gmail and cut-n-pasted the entire email below so you can see what I get.
Can someone suggest what I'm doing wrong here? And a possible solution?
Delivered-To: myemail#gmail.com
Received: by 10.58.189.196 with SMTP id gk4csp207059vec;
Mon, 17 Feb 2014 23:10:53 -0800 (PST)
X-Received: by 10.140.22.145 with SMTP id 17mr38512811qgn.0.1392707452834;
Mon, 17 Feb 2014 23:10:52 -0800 (PST)
Return-Path: <0000014443d53bd9-c1021b39-b43e-4d6f-bb55-0aff6c4b38f5-000000#amazonses.com>
Received: from a8-41.smtp-out.amazonses.com (a8-41.smtp-out.amazonses.com. [54.240.8.41])
by mx.google.com with ESMTP id j50si9661440qgf.137.2014.02.17.23.10.52
for <myemail#gmail.com>;
Mon, 17 Feb 2014 23:10:52 -0800 (PST)
Received-SPF: pass (google.com: domain of 0000014443d53bd9-c1021b39-b43e-4d6f-bb55-0aff6c4b38f5-000000#amazonses.com designates 54.240.8.41 as permitted sender) client-ip=54.240.8.41;
Authentication-Results: mx.google.com;
spf=pass (google.com: domain of 0000014443d53bd9-c1021b39-b43e-4d6f-bb55-0aff6c4b38f5-000000#amazonses.com designates 54.240.8.41 as permitted sender) smtp.mail=0000014443d53bd9-c1021b39-b43e-4d6f-bb55-0aff6c4b38f5-000000#amazonses.com
Return-Path: 0000014443d53bd9-c1021b39-b43e-4d6f-bb55-0aff6c4b38f5-000000#amazonses.com
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: Hello World3
From: My Django App <donotrespond#mydjangoapp.com>
To: myemail#gmail.com
Date: Tue, 18 Feb 2014 07:10:51 +0000
Message-ID: <0000014443d53bd9-c1021b39-b43e-4d6f-bb55-0aff6c4b38f5-000000#email.amazonses.com>
X-SES-Outgoing: 2014.02.18-54.240.8.41
Content-Type: multipart/related;
boundary="===============1003274537458441237=="
MIME-Version: 1.0
--===============1003274537458441237==
Content-Type: text/html; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
<p>Hello <img src="cid:myimage" /></p>
--===============1003274537458441237==
Content-Type: image/jpeg
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Content-Id: <myimage>
/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBxQTERUUEhIWFBUVFxcVFRQVGBUUFRcYFxUWFhQU
FRUYHCggGRolHRQVITEiJSkrLi4uFx8zODMsNygtLisBCgoKDg0OGhAQGywmICYzLDc3MCwvLCw1
<VERY LARGE PORTION SNIPPED>
BAgQIECAAIGaAsLKmnPVFQECBAgQIECAAAECBAgQIECAAIF0AsLKdCNTMAECBAgQIECAAAECBAgQ
IECAAIGaAsLKmnPVFQECBAgQIECAAAECBAgQIECAAIF0Av8HNFl0J1BnG68AAAAASUVORK5CYII=
--===============5170682983005376168==--
It looks like you have:
multipart/related
-> text/html
-> image/jpeg
I've also had trouble in the past sending email with the top part being multipart/related. Try this instead:
multipart/mixed
-> multipart/related
--> text/html
--> image/jpeg
Also, make sure and set the disposition on the image like this:
img.add_header("Content-Disposition", "inline", filename="myimage")

How to determine if a mail fetch by imap base64 encoded?

I saved the whole message as xx.eml, but some mails body tells that mail is encoding by base64 at the first line, for example:
charset="utf-8" Content-Transfer-Encoding: base64
charset="gb2312" Content-Transfer-Encoding: base64
I tried to get the keys of body[0][1], but there is no content-transfer-encoding field (only content-type).
How can I process that mails?
def saveMail(conn, num):
typ, body = conn.fetch(num, 'RFC822')
message = open(emldirPath + '\\' + num + '.eml', 'w+')
message.write(str(email.message_from_string(body[0][1])))
print email.message_from_string(body[0][1]).keys()
#['Received', 'Return-Path', 'Received', 'Received', 'Date', 'From', 'To',
# 'Subject', 'Message-ID', 'X-mailer', 'Mime-Version', 'X-MIMETrack',
# 'Content-Type', 'X-Coremail-Antispam']
message.close()
I found the problem, it's not decoding problem.
right mail as follow:
------=_Part_446950_1309705579.1326378953207
Content-Type: text/plain; charset=GBK
Content-Transfer-Encoding: base64
what my program download:
------=_Part_446950_1309705579.1326378953207
Content-Type: text/plain;
charset="utf-8"
Content-Transfer-Encoding: base64
when my program save the .eml file, it change line after 'text/plain;'
therefore outlook express can't parse the mail
if I edit the line to ""Content-Type: text/html;charset="utf-8"",
it works
Now the question is: how to edit my program to not let it change line?
Emails that are transfered as BASE64 must set Content-Transfer-Encoding. However you are most likely dealing with a MIME/Multipart message (e.g. both text/plain and HTML in the same message), in which case the transfer encoding is set separately for each part. You can test with is_multipart() or if Content-Type is multipart/alternative. If that is the case you use walk to iterate over the different parts.
EDIT: It is quite normal to send text/plain using quoted-printable and HTML using BASE64.
Content-Type: multipart/alternative; boundary="=_d6644db1a848db3cb25f2a8973539487"
Subject: multipart sample
From: Foo Bar <foo#example.net>
To: Fred Flintstone <fred#example.net>
--=_d6644db1a848db3cb25f2a8973539487
Content-Transfer-Encoding: base64
Content-Type: text/plain; charset=utf-8
SOME BASE64 HERE
--=_d6644db1a848db3cb25f2a8973539487
Content-Transfer-Encoding: base64
Content-Type: text/html; charset=utf-8
AND SOME OTHER BASE64 HERE

Categories

Resources