Im trying to send gmail email using python. The email includes plain text and an html image to be shown in the email. However when i try sending the email, the text is not showing (only image is shown).
Below is the code:
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
host='smtp.gmail.com'
port=587
username='sender#gmail.com'
password='mypassword'
from_email=username
to_list=['recipient#gmail.com']
email_conn=smtplib.SMTP(host,port)
email_conn.ehlo()
email_conn.starttls()
email_conn.login(username,password)
msg=MIMEMultipart('Alternative')
temp=MIMEMultipart('Alternative2')
msg['Subject']='Hello'
msg['From']=username
txt='Welcome home'
part1=MIMEText(txt,'plain')
msgText = MIMEText('<img src="cid:image1">', 'html')
temp.attach(msgText)
fp = open('/home/user/Pictures/image.jpg', 'rb')
msgImage = MIMEImage(fp.read())
fp.close()
# Define the image's ID as referenced above
msgImage.add_header('Content-ID', '<image1>')
temp.attach(msgImage)
msg.attach(part1)
msg.attach(temp)
email_conn.sendmail(from_email,to_list,msg.as_string())
email_conn.quit()
The immediate error is that you are creating a invalid MIME part with type multipart/Alternative2. You seem to be confusing the type (which should be one out of a limited set of IANA-approved labels) with a unique identifier.
More fundamentally, you seem to be following some obsolete email guideline. The proper way to create a new message in Python 3.6+ is to use the (no longer very) new EmailMessage API.
Also, you will want to restructure your code so that the message creation is not mixed with the message sending. In the following, I have simply removed all the smtplib code; this also makes it easy for you to troubleshoot locally with print(msg.as_string()) instead of sending the message.
from email.message import EmailMessage
from email.utils import make_msgid
username = 'sender#gmail.com'
to_list = ['recipient#gmail.com']
msg = EmailMessage()
msg['Subject'] = 'Hello'
msg['From'] = username
# Need recipient!
msg['To'] = ', '.join(to_list)
msg.set_content('Welcome home')
image_id = make_msgid()
# Notice closing slash at the end of <img ... />
msg.add_alternative('<img src="%s" />' % image_id.strip('<>'), subtype='html')
with open('/home/user/Pictures/image.jpg', 'rb') as fp:
msg.get_payload()[1].add_related(
fp.read(), 'image', 'jpeg', cid=image_id)
This rather closely follows the "asparagus" example from the email examples in the documentation.
You would then go on to create an SMTP session and smtp.send_message(msg) rather than take the detour to separately and explicitly convert the message to a string you can pass to the legacy sendmail method; this is one of the many improvements in the new API.
Related
I created a script in python to email with an attachment. I put it in databricks, and put it on a schedule. When I manually run this function, it fires only one email, but when it runs on the schedule, to emails are sent to each recipient. Although it sounds like a schedule issue, I believe it's a code issue - I was able to get it to work at some point, but now it is sending again.
If anyone can take a look at the code below and see if they can figure out why it would be duplicating, it would be appreciated!
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
from datetime import date
def SendEmail(recipient, subject,message_records,attach,cc,bcc,df):
server = smtplib.SMTP ('smtp.sendgrid.net', 587) # check server and port with your provider
server.ehlo()
server.starttls()
server.login("apikey", dbutils.secrets.get(scope = "XXXX", key = "XXXX")) # insert secret name
sender = dbutils.secrets.get(scope = "XXXX", key = "XXXX") # insert secret name
msg = MIMEMultipart()
msg['Subject'] = subject
msg['From'] = "noreply#noreply.com"
msg['cc'] = ", ".join([cc])
msg['To'] = recipient
rcpt = bcc.split(",") +cc.split(",") + [recipient]
message = """
<html>
<body>
Good morning, <br> <br>
"""+message_records+"""<br> <br>
Thank you and have a wonderful day!
</body>
</html>
"""
if attach==1:
msg.attach(MIMEText(message,'html'))
filename = "ASN-" + str(date.today())+'.csv'
attachment = MIMEApplication(df.to_csv(index=False))
attachment["Content-Disposition"] = 'attachment; filename=" {}"'.format(filename)
msg.attach(attachment)
else:
msg.attach(MIMEText(message,'html'))
server.sendmail(sender, rcpt, msg.as_string())
server.close()```
I suggest you look at the SENDGRID library for python. It uses the API key that you get from the service and it is built just for this service.
https://docs.sendgrid.com/for-developers/sending-email/v3-python-code-example
See if this code produces the same duplicate issue you are seeing.
If I remember right, send grid keeps track of the sent mail messages. Make sure the "to - recipient" list does not have duplicate addresses.
I do not see any obvious issues with the above code.
Here is the PyPi link. There are a ton of examples on usage.
https://pypi.org/project/sendgrid/
I am learning how to use Python for automating emails, and am facing issues in creating a "clean" email.
Problem: I have a Google doc which has a certain text, followed by an image, followed by some text. I wish to use this as the content of an email, which I wish to send using Python
Approach: Based on what I read on SO and blogs, I used MIMEMultipart and broke my original doc into 3 parts: text, image, text and I downloaded the html versions for both the text files. Thereafter, I used the following code:
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from string import Template
me = "myemail#gmail.com"
you = "youremail#gmail.com"
msg = MIMEMultipart()
msg['From'] = me
msg['To'] = you
msg['Subject'] = 'This is a test'
html1 = open('test_doc_part1.html')
part1 = MIMEText(html1.read(), 'html')
msg.attach(part1)
html1.close()
image_text = MIMEText('<img src="cid:image1">', 'html')
msg.attach(image_text)
fp = open('test_image.jpg', 'rb')
image = MIMEImage(fp.read())
fp.close()
image.add_header('Content-ID', '<image1>') # Define the image's ID as referenced in the HTML body above
msg.attach(image)
html2 = open('test_doc_part2.html')
part2 = MIMEText(html2.read(), 'html')
msg.attach(part2)
html2.close()
server = smtplib.SMTP('smtp.gmail.com', 587)
server.starttls()
server.login(me, "password")
text = msg.as_string()
# Do text substitutions here
server.sendmail(from_addr= me, to_addrs= you, msg=text)
server.quit()
Issue: Depending on the text sources, the text of my email is either hidden under the image, or all/some of it is underlined/garbled. I'm using gmail and viewing this email in Google Chrome. The original content of the received email can be viewed here. Can someone point out where I'm incorrect?
Here is my code:
FROM = ''
TO = ''
SMTPSERVER = ''
MYEMAILPASSWORD = ""
import smtplib
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
from email.mime.base import MIMEBase
from email import encoders
def physicallySend(listOfAttachments):
msg = MIMEMultipart('alternative')
msg['Subject'] = "Testing"
msg['From'] = FROM
msg['To'] = TO
textPart = MIMEText("Hello. I should be able to see this body.", 'plain')
msg.attach(textPart)
for attachmentFilename in listOfAttachments:
fp = open(attachmentFilename, 'rb')
file1 = MIMEBase('application','csv')
file1.set_payload(fp.read())
fp.close()
encoders.encode_base64(file1)
file1.add_header('Content-Disposition','attachment;filename=%s' % attachmentFilename)
msg.attach(file1)
server = smtplib.SMTP(SMTPSERVER)
server.ehlo()
server.starttls()
server.login(FROM, MYEMAILPASSWORD)
server.sendmail(FROM, to, msg.as_string())
server.quit()
return True
physicallySend(["testfile.csv"])
While I can see the text body fine on Gmail and Outlook, however on my iPhone (6.1.3) I only see the attachment, and not the body.
I found my solution in this comment: How do I send attachments using SMTP?
My first line should have been
msg = MIMEMultipart('mixed')
rather than 'alternative'.
For ones who seek the full solution to the question, that will work as intended on all email clients including iOS default email app.
When to use multipart/mixed
from the RFC
The primary subtype for multipart, "mixed", is intended for use when the body parts are independent and intended to be displayed serially. Any multipart subtypes that an implementation does not recognize should be treated as being of subtype "mixed".
so multipart/mixed type should be used when all of the parts of the message (text/html, text/plain, image/png etc.) are equally important and all should be displayed to the user
this means that if you attach text/html to the message and also text/plain as a fallback to html, both of them will be shown to the user one over the other which doesn't make any sense
When to use multipart/alternative
from the RFC
In particular, each of the parts is an "alternative" version of the same information. User agents should recognize that the content of the various parts are interchangeable. The user agent should either choose the "best" type based on the user's environment and preferences, or offer the user the available alternatives. In general, choosing the best type means displaying only the LAST part that can be displayed. This may be used, for example, to send mail in a fancy text format in such a way that it can easily be displayed anywhere
This means that whatever you attach to the multipart/alternative message can be treated as the same value but in different forms.
Why is that important?
if after the textual parts, you attach an image (image/png, image/jpeg) to the multipart/alternative message it can be treated as equally important as the message itself thus the only thing that the user will see is the image - without the text/html nor text/plain parts. This is not true for most of nowadays clients - they are smart enough to know what you mean, but the one that still does this is iOS default Mail app - you don't want to confuse those users.
Whats the correct way to do it then?
After the explanation this will make much more sense now
# keeps the textual and attachment parts separately
root_msg = MIMEMultipart('mixed')
root_msg['Subject'] = email.subject
root_msg['From'] = self._format_from_header()
root_msg['To'] = self._format_addrs(recipients=email.recipients)
alter_msg = MIMEMultipart('alternative')
plain_text = MIMEText('some text', 'plain', 'UTF-8')
html = MIMEText('<strong>some text</strong>', 'html')
alter_msg.attach(plain_text)
alter_msg.attach(html)
# now the alternative message (textual)
# is just a part of the root mixed message
root_msg.attach(alter_msg)
img_path = '...'
filename = os.path.basename(img_path)
with open(img_path, 'rb') as f:
attachment = MIMEImage(f.read(), filename=filename)
attachment.add_header('Content-Disposition', 'attachment', filename=filename)
# now the images (attachments) will be shown alongside
# either plain or html message and not instead of them
root_msg.attach(attachment)
I am using the following code to send email from unix.
Code
#!/usr/bin/python
import os
def sendMail():
sendmail_location = "/usr/sbin/sendmail" # sendmail location
p = os.popen("%s -t" % sendmail_location, "w")
p.write("From: %s\n" % "myname#company.com")
p.write("To: %s\n" % "yourname#company.com")
p.write("Subject: My Subject \n")
p.write("\n") # blank line separating headers from body
p.write("body of the mail")
status = p.close()
if status != 0:
print "Mail Sent Successfully", status
sendMail()
I am not sure how to add attachment to this email (attachment being on a different directory /my/new/dir/)
Sendmail is an extremely simplistic program. It knows how to send a blob of text over smtp. If you want to have attachments, you're going to have to do the work of converting them into a blob of text and using (in your example) p.write() to add them into the message.
That's hard - but you can use the email module (part of python core) to do a lot of the work for you.
Even better, you can use smtplib (also part of core) to handle sending the mail.
Check out http://docs.python.org/2/library/email-examples.html#email-examples for a worked example showing how to send a mail with attachments using email and smtplib
Use the email.mime package to create your mail instead of trying to generate it manually, it will save you a lot of trouble.
For example, sending a text message with an attachment could be as simple as:
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
msg = MIMEMultipart()
msg['From'] = 'fromaddress'
msg['To'] = 'toaddres'
msg['Subject'] = 'subject'
msg.attach(MIMEText('your text message'))
with open(filename, 'rb') as f:
attachment = MIMEApplication(f.read(), 'subtype')
attachment['Content-Disposition'] = 'attachment; filename="%s";' % filename
msg.attach(attachment)
message = msg.as_string()
Then you can write the message to sendmail, or use smtplib to send it.
'subtype' should either be replaced with the mime subtype of the attached document, or left out to send the attachment with the default type of application/octet-stream. Or if you know your file is text, you can use MIMEText instead of MIMEApplication.
I normally use the following to send a file "file_name.dat" as attachment:
uuencode file_name.dat file_name.dat | mail -s "Subject line" arnab.bhagabati#gmail.com
I'm a Python junior, so keep that in mind. In a Python script, I need to set a Return-Path address that is different than the sender's address. (I'm using Gmail as SMTP server.)
I've done lots of searching on this question and found plenty of "answers", but no solutions. I tried this link Setting Return-Path with Python sendmail for a MIME message but it's not working for me at all. I can change the "To:" address that the email recipient sees, but when they click "Reply", it's back to the sending email address again.
This is the function that I'm trying to write. It works well enough, except that I need to force a different Return-Path.
#!/usr/bin/python
import smtplib
import os
from email.MIMEMultipart import MIMEMultipart
from email.MIMEBase import MIMEBase
from email.MIMEText import MIMEText
from email.Utils import COMMASPACE, formatdate
from email import Encoders
import sap_mailserverdata as sf
def send_mail(sent_to, subject, body_text, sent_from_addr='', sent_from_name='', files=[], cc=[], bcc=[]):
"""Send emails with or without attachments."""
assert type(sent_to)==list
assert type(files)==list
assert type(cc)==list
assert type(bcc)==list
message = MIMEMultipart()
message['From'] = sent_from_addr
message['To'] = COMMASPACE.join(sent_to)
message['Date'] = formatdate(localtime=True)
message['Subject'] = subject
message['Cc'] = COMMASPACE.join(cc)
message.preamble = 'You need a MIME enabled mail reader to see this message.\n'
message.attach(MIMEText(body_text, 'html'))
for f in files:
part = MIMEBase('application', 'octet-stream')
part.set_payload(open(f, 'rb').read())
Encoders.encode_base64(part)
part.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(f))
message.attach(part)
addresses = []
for x in sent_to:
addresses.append(x)
for x in cc:
addresses.append(x)
for x in bcc:
addresses.append(x)
mail_server = smtplib.SMTP(sf.server, sf.server_port)
mail_server.ehlo()
mail_server.set_debuglevel(1)
mail_server.starttls()
mail_server.login(sf.username, sf.password)
mail_server.sendmail(sent_from_addr, addresses, message.as_string())
mail_server.quit()
What am I missing with this function to be able to reliably specify a different replyto Return-Path?
Reply-to and return path are two distinct beasts. See the RFC.
You can set Reply-to with:
msg['reply-to'] = 'smith#acme.com'
The return-path is set by the MTA to the address that receives bounces. It is controlled by the server administrator, so unless you work for Google I don't think this is under your control.
Most of the time one is after "Reply-to"; if you really need to change the return path you must use a SMTP server under your control and google for how to do this for the specific MTA you are using - many will have a white list of users and/or hosts that can override the return path.