I want to use the following python code to automize some reporting
from win32com import client
obj = client.Dispatch("Outlook.Application")
newMail = obj.CreateItem(0x0)
newMail.Subject = "This is the subject"
...
newMail.Body = "This is the text I want to send in the mail body"
But doing it this way deletes the signature. The following code
...
newMail.Body = "This is the text I want to send in the mail body" + newMail.Body
preserves the signature, but destroys the formating. Not acceptable for compliance reasons.
Is there a way to prepend text to the mail body to circumvent the termination of the signatures format?
tmp = newMail.Body.split('<body>')
# split by a known HTML tag with only one occurrence then rejoin
newMail.Body = '<body>'.join([tmp[0],yourString + tmp[1]])
Related
BACKGROUND
Regarding the following articles:
https://www.drupal.org/project/mimemail/issues/31524
Exclamation Point Randomly In Result of PHP HTML-Email
https://sourceforge.net/p/phpmailer/bugs/53/
All the problems and solutions refer to PHP issue, but I have run into this problem in Python.
If I send the emails directly to recipients, all is well, no exclamation marks appear, and the message displays properly.
However, utilizing our "Sympa" (https://www.sympa.org/) system that the University uses for it "mailing list" solution, emails from this system have the exclamation marks and line breaks inserted in the message and HTML breaks causing display issues.
The problem stems from line length. Any line longer than a magical 998 character length line gets this exclamation marks and line breaks inserted.
NOW THE QUESTION
One of the solutions they mention is encoding the body of a message in base64, which apparently is immune to the line length issue. However, I can not figure out how to properly form a message in Python and have the proper headers and encoding happen so the message will display properly in an email client.
Right now, I have only succeed in sending emails with base64 encode bodies as attached files. Bleck!
I need to send HTML encoded emails (tables and some formatting). I create one very long concatenated string of all the html squished together. It is ugly but will display properly.
HELP?!
NOTE: If anyone else has had this problem and has a solution that will allow me to send emails that are not plagued by line length issue, I am all ears!
Source Code as Requested
# Add support for emailing reports
import smtplib
# from email.mime.text import MIMEText
from email.mime.message import MIMEMessage
from email.encoders import encode_base64
from email.message import Message
... ...
headerData = {"rDate": datetime.datetime.now().strftime('%Y-%m-%d')}
msg_body = msg_header.format(**headerData) + contact_table + spacer + svc_table
theMsg = Message()
theMsg.set_payload(msg_body)
encode_base64(theMsg)
theMsg.add_header('Content-Transfer-Encoding', 'base64')
envelope = MIMEMessage(theMsg, 'html')
theSubject = "Audit for: "+aService['description']
envelope['Subject'] = theSubject
from_addr = "xxx#xxx"
envelope['From'] = from_addr
to_addrs = "xxx#xxxx"
# to_addrs = aService['contact_email']
envelope['To'] = to_addrs
# Send the message via our own SMTP server.
s = smtplib.SMTP('x.x.x.x')
s.sendmail(from_addr, to_addrs, envelope.as_string())
s.quit()
SOLUTION, thank you #Serge Ballesta
Going back to MIMEText, and specifying a character set seemed to do the trick:
envelope = MIMEText(msg_body, 'html', _charset='utf-8')
assert envelope['Content-Transfer-Encoding'] == 'base64'
envelope['Subject'] = "Audit for: "+aService['description']
from_addr = "f5-admins#utlists.utexas.edu"
envelope['From'] = from_addr
to_addrs = "xx-test#xx.xx.edu"
envelope['To'] = to_addrs
# Send the message via our own SMTP server.
s = smtplib.SMTP('xx.xx.xx.edu')
s.sendmail(from_addr, to_addrs, envelope.as_string())
s.quit()
Apparently I was just stabbing around and did not account for character set. Using MIMEText and not MIMEMessage.
Normally, email.mime.MIMEText automatically sets the Content-Transfert-Encoding to base64 if the body is not declared to be plain ASCII. So, assuming that body contains the HTML text of the body of the message (no mail headers there), declaring it as utf-8 should be enough:
msg = email.mime.text.MIMEText(body, 'html', _charset='utf-8')
# assert the cte:
assert msg['Content-Transfer-Encoding'] == 'base64'
theSubject = "Audit for: "+aService['description']
msg['Subject'] = theSubject
from_addr = "xxx#xxx"
msg['From'] = from_addr
to_addrs = "xxx#xxxx"
# to_addrs = aService['contact_email']
msg['To'] = to_addrs
# optionally set other headers
# msg['Date']=datetime.datetime.now().isoformat()
# Send the message
s = smtplib.SMTP('x.x.x.x')
s.sendmail(from_addr, to_addrs, msg.as_bytes())
s.quit()
Everytime I send an email with this function, it doesn't add the subject and the message to the right fields, but instead of that, it adds it to the 'from:' or something.
Here's the image of it.
Any idea how this can be fixed? Thanks for answer
import smtplib
## NON-ANONYMOUS EMAIL
def email():
# Parts of an email
SERVER = 'smtp.gmail.com'
PORT = 587
USER = 'something#gmail.com'
PASS = 'something'
FROM = USER
TO = ['something#riseup.net']
#SUBJECT = 'Test'
MESSAGE = 'Test message.'
# Connects all parts of email together
message = "From: %s\r\n To: %s\r\n %s" % (FROM, ", ".join(TO), MESSAGE)
# Sends an email
email = smtplib.SMTP()
email.connect(SERVER,PORT)
email.starttls()
email.login(USER,PASS)
email.sendmail(FROM, TO, message)
email.quit()
email()
You cannot have a space after the \r\n. An email header line is continued by indenting it, so your code is creating a really long From: header with all the data you are trying to put in different fields.
Anyway, manually gluing together snippets of plain text is a really crude and error-prone way to construct an email message. You will soon find that you need the various features of the Python email module anyway (legacy email is 7-bit single part ASCII only; you'll probably want one or more of attachments, content encoding, character set support, multipart messages, or one of the many other MIME features). This also coincidentally offers much better documentation for how to correcty create a trivial email message.
Following on from #tripleee suggestion to use the email module, here's a basic example using your current code:
import smtplib
from email.mime.text import MIMEText
## NON-ANONYMOUS EMAIL
def email():
# Parts of an email
SERVER = 'smtp.gmail.com'
PORT = 587
USER = 'something#gmail.com'
PASS = 'something'
FROM = USER
TO = ['something#riseup.net']
SUBJECT = 'Test'
# Create the email
message = MIMEText('Test message.')
message['From'] = FROM
message['To'] = ",".join(TO)
message['Subject'] = SUBJECT
# Sends an email
email = smtplib.SMTP()
email.connect(SERVER,PORT)
email.starttls()
email.login(USER,PASS)
email.sendmail(FROM, TO, message.as_string())
email.quit()
Notice how much easier it is to define the parts of the email using keys like message['Subject'] instead of attempting to build a string or 'gluing parts together' as tripleee put it.
The different fields (From, To, Subject, et cetera) you can access are defined in RFC 2822 - Internet Message Format.
These documents are not easy to read, so here's a list of some of the fields' keys you can use: To, From, Cc, Bcc, Reply-To, Sender, Subject.
You cannot have a space after the \r\n. An email header line is continued by indenting it, so your code is creating a really long From: header with all the data you are trying to put in different fields.
As triplee and the RFC-2822 document says, if you are wanting to build the email string manually look at the field definitions in that document which look similar to this example:
from = "From:" mailbox-list CRLF
You can translate this into Python code when building an email string like so:
"From: something#riseup.net \r\n"
I was able to get mine to work using:
("From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n%s"
% (gmail_user, recipient, subject, body))
Is there a way to send HTML-formatted email using Python's win32com.client (which utilizes Outlook 2007/2010). The format I'm using now looks like this:
import win32com.client
olMailItem = 0x0
obj = win32com.client.Dispatch("Outlook.Application")
newMail = obj.CreateItem(olMailItem)
newMail.Subject = "the subject"
newMail.Body = "body text"
newMail.To = "recipient#example.com"
attachment1 = "c:\\mypic.jpg"
newMail.Attachments.Add(attachment1)
newMail.Send()
This will send an email using Outlook, sent from the currently authenticated user, to the specified recipient, with a subject, content, and attached image.
I want to be able to send an inline image, which can be achieved using an "Embedded" attachment, or simply to link to and image using HTML, or embed an image using HTML and a Base64-encoded image.
HTML is my preferred approach, but any HTML I add to the body is formatted and encoded as plain text (e.g. < becomes <). Is there a way to tell Outlook the body content is HTML and should be parsed as such?
This is the way to make the body in html format
newMail.HTMLBody = htmltext
I've been trying to use 'import poplib' to access gmail, since I have Pop turned on in settings- but how do I actually then check the message for its 'from' address and then run something based on it? Also, what would be the command to strip the 'body' text from the message?
Here is how you can get subject and sender of each message in your GMail inbox using imaplib.
import imaplib
from email.parser import HeaderParser
conn = imaplib.IMAP4_SSL('imap.gmail.com')
conn.login('username#gmail.com', 'password')
# Select the mail box
status, messages = conn.select('INBOX')
if status != "OK":
print "Incorrect mail box"
exit()
if int(messages[0]) > 0:
for message_number in range(1,int(messages[0])+1):
data = conn.fetch(message_number, '(BODY[HEADER])')
parser = HeaderParser()
msg = parser.parsestr(data[1][0][1])
print "Subject: %s" % msg['subject']
print "From: %s" % msg['from']
You will probably need more information. Start from the official imaplib documentation.
there is the module rfc822
I guess messages from poplib can be donloaded from the server.
then put into a file
>>> f = StringIO.StringIO(message)
>>> import rfc822
and passed to
>>> rfc822.Message(f)
try this out.. and also check out the module documentation.
I hope it helps.
There is another python module:
>>> import email
>>> email.message_from_string(...)
This should provide you with read access for headers and also support muliple formats of body contents.
From the documentation:
POP3.retr(which)
Retrieve whole message number which, and set its seen flag. Result is in form (response, ['line', ...], octets).
So, assuming you have put the result of retr() into a variable called response, the lines of the message are stored as a list in response[1]. By RFC 2822 we know that the headers are separated from the body of the message by a blank line. The sender of the message will be in the From: header line. So we can just iterate over the lines of the message, stop when we get a blank line, and set a variable to our sender when we see a line that starts with From:.
sender = None
for line in response[1]:
if line.startswith("From: "):
sender = line.partition(" ")[2].strip()
elif line == "":
break
If you plan to do a lot with the headers, it might be useful to put them into a dictionary by header name. Since each header can appear multiple times, each value in the dictionary should be a list.
headers = {}
for line in response[1]:
if line == "":
break
line = line.partition(" ")
key = line[0].strip().rstrip(":")
value = line[2].stirp()
headers.setdefault(key, []).append(value)
After this you can use headers["From"][0] to get the sender of the message.
I wanted to show the basic way of doing this, because it's not very complicated, but Python can do most of the work for you. Again, assuming that your retr() result is in response:
import email
# convert our message back to a string and parse it
headers = email.parsefromstring("\n".join(response[0]), headersonly=True)
print headers["From"] # prints the sender
You can find out more about the message object in the documentation for the email module.
The From: line of an e-mail message may have additional text besides the e-mail address, such as the sender's name. You can extract the email address with a regular expression:
sender = re.find(r".*[ <](.+#.+)\b", headers["From"]).match(1)
Given an RFC822 message in Python 2.6, how can I get the right text/plain content part? Basically, the algorithm I want is this:
message = email.message_from_string(raw_message)
if has_mime_part(message, "text/plain"):
mime_part = get_mime_part(message, "text/plain")
text_content = decode_mime_part(mime_part)
elif has_mime_part(message, "text/html"):
mime_part = get_mime_part(message, "text/html")
html = decode_mime_part(mime_part)
text_content = render_html_to_plaintext(html)
else:
# fallback
text_content = str(message)
return text_content
Of these things, I have get_mime_part and has_mime_part down pat, but I'm not quite sure how to get the decoded text from the MIME part. I can get the encoded text using get_payload(), but if I try to use the decode parameter of the get_payload() method (see the doc) I get an error when I call it on the text/plain part:
File "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/
email/message.py", line 189, in get_payload
raise TypeError('Expected list, got %s' % type(self._payload))
TypeError: Expected list, got <type 'str'>
In addition, I don't know how to take HTML and render it to text as closely as possible.
In a multipart e-mail, email.message.Message.get_payload() returns a list with one item for each part. The easiest way is to walk the message and get the payload on each part:
import email
msg = email.message_from_string(raw_message)
for part in msg.walk():
# each part is a either non-multipart, or another multipart message
# that contains further parts... Message is organized like a tree
if part.get_content_type() == 'text/plain':
print part.get_payload() # prints the raw text
For a non-multipart message, no need to do all the walking. You can go straight to get_payload(), regardless of content_type.
msg = email.message_from_string(raw_message)
msg.get_payload()
If the content is encoded, you need to pass None as the first parameter to get_payload(), followed by True (the decode flag is the second parameter). For example, suppose that my e-mail contains an MS Word document attachment:
msg = email.message_from_string(raw_message)
for part in msg.walk():
if part.get_content_type() == 'application/msword':
name = part.get_param('name') or 'MyDoc.doc'
f = open(name, 'wb')
f.write(part.get_payload(None, True)) # You need None as the first param
# because part.is_multipart()
# is False
f.close()
As for getting a reasonable plain-text approximation of an HTML part, I've found that html2text works pretty darn well.
Flat is better than nested ;)
from email.mime.multipart import MIMEMultipart
assert isinstance(msg, MIMEMultipart)
for _ in [k.get_payload() for k in msg.walk() if k.get_content_type() == 'text/plain']:
print _
To add on #Jarret Hardie's excellent answer:
I personally like to transform that kind of data structures to a dictionary that I can reuse later, so something like this where the content_type is the key and the payload is the value:
import email
[...]
email_message = {
part.get_content_type(): part.get_payload()
for part in email.message_from_bytes(raw_email).walk()
}
print(email_message["text/plain"])
#This is what I have for a gmail account using the app password method.
from imap_tools import MailBox
import email
my_email = "your email"
my_pass = "app password"
mailbox = MailBox('imap.gmail.com').login(my_email, my_pass)
for msg in mailbox.fetch('Subject " "', charset='utf8'):
print("Message id:",msg.uid)
print("Message Subject:",msg.subject)
print("Message Date:", msg.date)
print("Message Text:", msg.text)
Try my lib for IMAP: https://github.com/ikvk/imap_tools
from imap_tools import MailBox, AND
# Get date, subject and body len of all emails from INBOX folder
with MailBox('imap.mail.com').login('test#mail.com', 'pwd') as mailbox:
for msg in mailbox.fetch():
print(msg.date, msg.subject, len(msg.text or msg.html))
See html2text: https://pypi.org/project/html2text/.
And may be msg.text is enough