Multipart E-Mail missing or corrupt attachment - python

I use the following code to send an e-mail with a pdf attachment. For most receivers this works without any issues but some clients show the pdf as corrupt or not at all. Thus I think there is probably something wrong and most clients are just forgiving enough to make it work anyway. Unfortunately, at this point I am out of ideas as I tried so many header combinations - all without success.
The pdf is base64 encoded.
def sendMail(receiver, pdf):
marker = "AUNIQUEMARKER"
message = """Subject: The Subject
From: {sender}
To: {receiver}
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary={marker}
--{marker}
Content-Type: text/plain; charset="utf-8"
Text goes here.
--{marker}
Content-Type: application/pdf; name="{filename}"
Content-Transfer-Encoding:base64
Content-Disposition: attachment; filename={filename}
{pdfcontent}
--{marker}--
""".format(marker=marker, sender="some#sender.com", receiver=receiver, filename="Test.pdf", pdfcontent=pdf)
port = 587
smtp_server = "some.server.com"
context = ssl.create_default_context()
with smtplib.SMTP(smtp_server, port) as server:
server.starttls(context=context)
server.login("user", "password")
server.sendmail("some#sender.com", [receiver, "cc#sender.com"], message.encode())
In case it is relevant, the pdf is created via LaTex as follows
pdfl = PDFLaTeX.from_texfile('latex/test.tex')
pdf, log, completed_process = pdfl.create_pdf(keep_pdf_file=False, keep_log_file=False)
pdfBase64 = base64.b64encode(pdf).decode()
Thanks for any help.
PS: Not showing the attachment at all might be fixed as I switched from Content-Type: multipart/alternative to multipart/mixed.

Well, apparently the base64 block should contain a newline every 76 characters. In my case that means I had to switch from base64.b64encode to base64.encodebytes as the latter does exactly this.

Related

Python sendmail, error

I am getting an additional content as given bellow when I am sending mail from unix server using python sendmail.This content is displayed in the mail.
From nobody Mon Dec 18 09:36:01 2017 Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit
My code is as follows.
#reading data from file
data = MIMEText(file('%s'%file_name).read())
#writing the content as html
content = MIMEText("<!DOCTYPE html><html><head><title></title></head><body>"+'%s'%data+"</body></html>", "html")
msg = MIMEMultipart("alternative")
msg["From"] = "sender#mail.com"
msg["To"] = "mymailid#mail.com"
msg["Subject"] = "python mail"
msg.attach(content)
p = Popen(["/usr/sbin/sendmail", "-t","-oi"], stdin=PIPE,universal_newlines=True)
p.communicate(msg.as_string())
You are constructing the email content in two parts, as data and content. You need to explicitly confirm that both are HTML. So change
data = MIMEText(file('%s'%file_name).read())
to
data = MIMEText(file('%s'%file_name).read(), "html")
You should look at the message string. The message you see is not a warning, it is just what you have writen into the message with:
data = MIMEText(file('%s'%file_name).read())
content = MIMEText("<!DOCTYPE html><html><head><title></title></head><body>"
+'%s'%data+"</body></html>", "html")
data.as_string() actually contains Content-Type: text/plain; ... because it has been added by the first MIMEText line, when you want to include it into the body of a HTML page.
What you really want is probably:
data = file(file_name).read()
content = MIMEText("<!DOCTYPE html><html><head><title></title></head><body>"
+'%s'%data+"</body></html>", "html")
But I also think that you do not need to include it into another level with a MIMEMultipart("alternative"): msg = content is probably enough.
Finally, I do not think that explicitely starting a new process to execute sendmail is really overkill, when the smtplib module from the standard library aloready knows how to send a message:
import smtplib
server = smtplib.SMTP()
server.send_message(msg)
server.quit()

which content-type should be chosen when sending email (SMTP)

I'm confused about content-type in email when sending email.I know the attachment file type is related to content-type.For some reason,I can't use "application/octet-stream".
For example,I want to send "pdf" attachment.
msg = MIMEMultipart()
msg['From'] = ""
msg['Subject'] = ""
part = MIMEApplication(open(attachment_path, 'rb').read())
filetype = os.path.splitext(attachment_path)[-1][1:]
newfilename = 'resume' + '.' + filetype
if filetype=="pdf":
part["Content-Type"] ="application/pdf"
elif filetype=="doc" or filetype=="docx":
part['Content-Type']="application/msword"
else:
pass
part.add_header('Content-Disposition', 'attachment', filename=newfilename)
msg.attach(part)
The infomation is below :
enter image description here
The two content-type : smtp header infomation and attachment header ?
Will they influence each other?
And "docx" ---can use application/msword?
Please forgive me for asking this silly question!
Thanks for any help!
The smtp header most likely will be like:
Content-Type: multipart/related; boundary="----=_NextPart_01D2B948.420196F0"
And each part of message will have its own context type, like this:
------=_NextPart_01D2B948.420196F0
Content-Location: file:///C:/18F2A310/testpage.htm
Content-Transfer-Encoding: quoted-printable
Content-Type: text/html; charset="us-ascii"
The headers don't impact each other. If you open an eml message or save an outlook message to MHT format, you'll seed the MIME code detail.
And from this link you can find the application context type supported:Media Types

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 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

How do I reply to an email using the Python imaplib and include the original message?

I'm currently using imaplib to fetch email messages from a server and process the contents and attachments.
I'd like to reply to the messages with a status/error message and links to the resulting generated content on my site if they can be processed. This should include the original message but should drop any attachments (which will be large) and preferably replace them with just their filenames/sizes.
Since I'm already walking the MIME message parts, I'm assuming what I need to do is build a new MIME message tree containing a copy of the original message and delete/replace the attachment nodes.
Before I start down that path, I was hoping someone can give me some tips. Is there any kind of library function to do this? Any kind of standard behavior I should stick to?
I currently know of/am using the imaplib, smtplib and email modules and but may have missed something obvious in there. This is running in Django too, so can use anything in django.core.email if that makes it easier.
The original MIME tree structure of the incoming message is as follows (using email.iterators._structure(msg)):
multipart/mixed
text/html (message)
application/octet-stream (attachment 1)
application/octet-stream (attachment 2)
Replying via GMail results in the following structure:
multipart/alternative
text/plain
text/html
I.e. they aren't being as smart as I thought, just discarding the attachments (good) and providing text and HTML versions that explicitly restructure the "quoted content."
I'm beginning to think that's all I should do too, just reply with a simple message as after discarding the attachments there's not much point in keeping the original message.
Still, might as well answer my original question since I've figured out how to now anyway.
First, replace all the attachments in the original message with text/plain placeholders:
import email
original = email.message_from_string( ... )
for part in original.walk():
if (part.get('Content-Disposition')
and part.get('Content-Disposition').startswith("attachment")):
part.set_type("text/plain")
part.set_payload("Attachment removed: %s (%s, %d bytes)"
%(part.get_filename(),
part.get_content_type(),
len(part.get_payload(decode=True))))
del part["Content-Disposition"]
del part["Content-Transfer-Encoding"]
Then create a reply message:
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.message import MIMEMessage
new = MIMEMultipart("mixed")
body = MIMEMultipart("alternative")
body.attach( MIMEText("reply body text", "plain") )
body.attach( MIMEText("<html>reply body text</html>", "html") )
new.attach(body)
new["Message-ID"] = email.utils.make_msgid()
new["In-Reply-To"] = original["Message-ID"]
new["References"] = original["Message-ID"]
new["Subject"] = "Re: "+original["Subject"]
new["To"] = original["Reply-To"] or original["From"]
new["From"] = "me#mysite.com"
Then attach the original MIME message object and send:
new.attach( MIMEMessage(original) )
s = smtplib.SMTP()
s.sendmail("me#mysite.com", [new["To"]], new.as_string())
s.quit()
The resulting structure is:
multipart/mixed
multipart/alternative
text/plain
text/html
message/rfc822
multipart/mixed
text/html
text/plain
text/plain
Or it's a bit simpler using Django:
from django.core.mail import EmailMultiAlternatives
from email.mime.message import MIMEMessage
new = EmailMultiAlternatives("Re: "+original["Subject"],
"reply body text",
"me#mysite.com", # from
[original["Reply-To"] or original["From"]], # to
headers = {'Reply-To': "me#mysite.com",
"In-Reply-To": original["Message-ID"],
"References": original["Message-ID"]})
new.attach_alternative("<html>reply body text</html>", "text/html")
new.attach( MIMEMessage(original) ) # attach original message
new.send()
The result ends (in GMail at least) showing the original message as "---- Forwarded message ----" which isn't quite what I was after, but the general idea works and I hope this answer helps someone trying to figure out how to fiddle with MIME messages.

Categories

Resources