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()
Related
I have a curious problem. When I send a message with the send_email1 function in my script, the message ends up in my Gmail spam folder, but when I use send_email2 it is successfully delivered to my inbox. The second message uses the legacy API, and I assume it will be deprecated at some point and that I should not use it any more.
Many thanks for the help.
import smtplib
from email.message import EmailMessage
from email.mime.text import MIMEText
def send_email1(subject: str, content: str):
msg = EmailMessage()
msg['From'] = sender
msg['To'] = recipient
msg['Subject'] = subject
msg.set_content(content)
with smtplib.SMTP('mysmtpserver') as s:
s.starttls()
s.send_message(msg)
def send_email2(subject: str, content: str):
msg = MIMEText(content)
msg['Subject'] = subject
msg['From'] = sender
msg['To'] = recipient
with smtplib.SMTP('mysmtpserver') as s:
s.starttls()
s.send_message(msg)
sender = 'sender#abc.com'
recipient = 'Recipient <recipient#abc.com>'
send_email1("Test message from Pyton1", "Test message from Python script. 1")
send_email2("Test message from Pyton2", "Test message from Python script. 2")
looking at the resulting mail-data, which is both compliant with RFC 5322, one can identify two differences:
From: sender#abc.com
To: Recipient <recipient#abc.com>
Subject: Test message from Pyton1
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: 7bit
MIME-Version: 1.0
Test message from Python script. 1
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: Test message from Pyton2
From: sender#abc.com
To: Recipient <recipient#abc.com>
Test message from Python script. 2
The first difference is the order. The mail which does not end up in the spam-folder declares the header-fields Content-Type and MIME-Version first. Secondly, the specified charset is charset="us-ascii" opposed to utf-8.
While we can only speculate about Gmail's spam filtering rules, I propose
to change the order of setting content and the header-fields as well as
explicitly stating the Content-Type (with its subtype and charset) by using
msg.set_content(content, subtype="plain", charset='us-ascii')
This results in a modificiation of send_email(...) like so:
def send_email1(subject: str, content: str):
msg = EmailMessage()
msg.set_content(content, subtype="plain", charset='us-ascii')
msg['From'] = sender
msg['To'] = recipient
msg['Subject'] = subject
Gmail controls what emails get sent to inbox or spam. Google has all sorts of data collection and analysis on what emails are spam and what are not spam. This seems to be google treating the one content type as spam and the other not.
at the end of the day its not how you send the mail from python its the content that you put into the mail (and other things like sending location, content type, attachments etc) that google will decide to send the email to spam or not.
For example if I send a mail with "Catchup" and a body of how we haven't spoken in a while. Google will probably send it to my inbox. However if I send an email with "CLICK THIS LINK TO WIN 100 MILLION" its probably going to get put in spam. Its not how you send the mail. Its the content of the mail and its properties.
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.
I am getting an "error: ValueError: Cannot convert mixed to alternative."
I get this error when I insert the open image and msg.add_attachment block (highlighted in btw #### ####). Without it, the code runs fine. I need to send the email as html and with the image attachment.
import os
import imghdr
from email.message import EmailMessage
import smtplib
EMAIL_ADDRESS = os.environ.get('EMAIL-USER')
EMAIL_PASSWORD = os.environ.get('EMAIL-PASS')
Message0 = "HelloWorld1"
Message1 = "HelloWorld2"
msg = EmailMessage()
msg['Subject'] = 'Hello WORLD'
msg['From'] = EMAIL_ADDRESS
msg['To'] = EMAIL_ADDRESS
msg.set_content('This is a plain text email, see HTML format')
########################################################################
with open('screenshot.png', 'rb') as f:
file_data = f.read()
file_type = imghdr.what(f.name)
file_name = f.name
msg.add_attachment(file_data, maintype='image', subtype=file_type, filename=file_name)
#########################################################################
msg.add_alternative("""\
<!DOCTYPE html>
<html>
<body>
<h1 style="color:Blue;">Hello World</h1>
{Message0}
{Message1}
</body>
</html>
""".format(**locals()), subtype='html')
with smtplib.SMTP_SSL('smtp.gmail.com', 465) as smtp:
smtp.login(EMAIL_ADDRESS, EMAIL_PASSWORD)
smtp.send_message(msg)
print("email sent")
For the end result, I need to be able to send an email via Python and attach images.
An e-mail can consist of a single part, or it can be a multi-part message.
If it is a multi-part message, it will usually be either a multipart/alternative, or a multipart/mixed.
multipart/alternative means there are 2 or more versions of the same content (e.g. plain text and html)
multipart/mixed is used when multiple different contents need to be packed together (e.g. an email and an attachment)
What actually happens when multipart is used is that email consists of a "multipart" container which contains additional parts, e.g. for text+html it is something like this:
multipart/alternative part
text/plain part
text/html part
In case of an email with attachment, you can have something like this:
multipart/mixed part
text/plain part
image/png part
So, the container is either mixed or alternative, but cannot be both. So, how to have both? You can nest them, e.g.:
multipart/mixed part
multipart/alternative part
text/plain part
text/html part
image/png part
So, now you have an email which consists of a message and an attachment, and the message has both plain text and html.
Now, in code, this is the basic idea:
msg = EmailMessage()
msg['Subject'] = 'Subject'
msg['From'] = 'from#email'
msg['To'] = 'to#email'
msg.set_content('This is a plain text')
msg.add_attachment(b'xxxxxx', maintype='image', subtype='png', filename='image.png')
# Now there are plain text and attachment.
# HTML should be added as alternative to the plain text part:
text_part, attachment_part = msg.iter_parts()
text_part.add_alternative("<p>html contents</p>", subtype='html')
BTW, you can then see what is in each part this way:
>>> plain_text_part, html_text_part = text_part.iter_parts()
>>> print(plain_text_part)
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: 7bit
This is a plain text
>>> print(html_text_part)
Content-Type: text/html; charset="utf-8"
Content-Transfer-Encoding: 7bit
MIME-Version: 1.0
<p>html contents</p>
>>> print(attachment_part)
Content-Type: image/png
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="image.png"
MIME-Version: 1.0
eHh4eHh4
>>> print(msg)
Subject: Subject
From: from#email
To: to#email
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="===============2219697219721248811=="
--===============2219697219721248811==
Content-Type: multipart/alternative;
boundary="===============5680305804901241482=="
--===============5680305804901241482==
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: 7bit
This is a plain text
--===============5680305804901241482==
Content-Type: text/html; charset="utf-8"
Content-Transfer-Encoding: 7bit
MIME-Version: 1.0
<p>html contents</p>
--===============5680305804901241482==--
--===============2219697219721248811==
Content-Type: image/png
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="image.png"
MIME-Version: 1.0
eHh4eHh4
--===============2219697219721248811==--
This is a complementary example to the one provided by #zvone. The difference here is that I want to have a mail with 2 versions. One in plain-text with an image as an attachement. The other part being the html with embedded image.
So the layout of the mail is like so:
multipart/mixed part
multipart/alternative part
multipart/related part (html with img)
text/html part
image/png part
text/plain part (plain text)
image/png part (attachement)
plain-text version: textfile.txt
Hello World
html version: mail_content.html
<html>
<head></head>
<body>
<h1>Hello World</h1>
<img src="cid:{graph_example_img_cid}" />
</body>
</html>
And of course the Python code (tested for Python 3.10). The embedding of the image follows the example from latest Python Standard Library of email.
Python code: main.py
# var
mail_server: str = "****smtp****"
user: str = "****#****.com"
me = user
you = user
# imports
import smtplib # mail actual sending function
import imghdr # And imghdr to find the types of our images
from email.message import EmailMessage
from email.utils import make_msgid
import datetime
# Open a plain text file for reading. For this example, assume that
# the text file contains only ASCII characters.
textfile_filepath: str = "textfile.txt"
with open(textfile_filepath) as fp:
# Create a text/plain message
msg = EmailMessage()
msg.set_content(fp.read())
# me == the sender's email address
# you == the recipient's email address
msg['Subject'] = 'Test Email with 2 versions'
msg['From'] = me
msg['To'] = you
# attachements
# Open the files in binary mode. Use imghdr to figure out the
# MIME subtype for each specific image.
image_filepath: str = "test_image.png"
with open(image_filepath, 'rb') as fp:
img_data = fp.read()
msg.add_attachment(img_data, maintype='image', subtype=imghdr.what(None, img_data))
text_part, attachment_part = msg.iter_parts()
## HTML alternative using Content ID
html_content_filepath: str = "mail_content.html"
graph_cid = make_msgid()
with open(html_content_filepath) as fp:
# Create a text/plain message
#msg = MIMEText(fp.read())
raw_html: str = fp.read()
# note that we needed to peel the <> off the msgid for use in the html.
html_content: str = raw_html.format(graph_example_img_cid=graph_cid[1:-1])
# add to email message
text_part.add_alternative(html_content,subtype='html')
# Now add the related image to the html part.
with open(image_filepath, 'rb') as img:
text_part, html_part = text_part.iter_parts()
html_part.add_related(img.read(), 'image', 'jpeg', cid=graph_cid)
# Send the message via our own SMTP server, but don't include the envelope header.
s = smtplib.SMTP(mail_server)
s.sendmail(me, [you], msg.as_string())
s.quit()
# log
current_time = datetime.datetime.now()
print("{} >>> Email sent (From: {}, To: {})".format(
current_time.isoformat(), me, you
))
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
I tried hard to find solution to this issues but all in vein, finally i have to ask you guys. I have HTML email (using Python's smtplib). Here is the code
Message = """From: abc#abc.com>
To: abc#abc.com>
MIME-Version: 1.0
Content-type: text/html
Subject: test
Hello,
Following is the message
""" + '\n'.join(mail_body) + """
Thank you.
"""
In above code, mail_body is a list which contains lines of output from a process. Now what i want is, to display these lines (line by line) in HTML email. What is happening now its just appending line after line. i.e.
I am storing the output(of process) like this :
for line in cmd.stdout.readline()
mail_body.append()
Current Output in HTML email is:
Hello,
abc
Thank you.
What i want :
Hello,
a
b
c
Thank you.
I just want to attach my process output in HTML email line by line. Can my output be achieved in any way?
Thanks and Regards
You could generate the email content to send using email package (from stdlib) e.g.:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from cgi import escape
from email.header import Header
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from smtplib import SMTP_SSL
login, password = 'me#example.com', 'my password'
# create message
msg = MIMEMultipart('alternative')
msg['Subject'] = Header('subject…', 'utf-8')
msg['From'] = login
msg['To'] = ', '.join([login,])
# Create the body of the message (a plain-text and an HTML version).
text = "Hello,\nFollowing is the message\n%(somelist)s\n\nThank you." % dict(
somelist='\n'.join(["- " + item for item in mail_body]))
html = """<!DOCTYPE html><title></title><p>Hello,<p>Following is the message
<ul>%(somelist)s</ul><p>Thank you. """ % dict(
somelist='\n'.join(["<li> " + escape(item) for item in mail_body]))
# According to RFC 2046, the last part of a multipart message, in this case
# the HTML message, is best and preferred.
msg.attach(MIMEText(text, 'plain', 'utf-8'))
msg.attach(MIMEText(html, 'html', 'utf-8'))
# send it
s = SMTP_SSL('smtp.mail.example.com', timeout=10) # no cert check on Python 2
s.set_debuglevel(0)
try:
s.login(login, password)
s.sendmail(msg['From'], msg['To'], msg.as_string())
finally:
s.quit()
in HTML, a new line is not \n it is <br> for "line break" but since you are also not using HTML tags in this email, you also need to know that in MIME messages, a newline is \r\n and not just \n
So you should write:
'\r\n'.join(mail_body)
For newlines that deal with the MIME message, but if you are going to use the HTML for formatting, then you need to know that <br> is the line break, and it would be:
'<br>'.join(mail_body)
To be comprehensive, you could try:
'\r\n<br>'.join(mail_body)
But I do now know what that would like like...