Attaching a single file to an e-mail - python

Please forgive me up front. When I've tried to research this question I end up looking at code that I simply can't comprehend. I have about 3 hours of experience with Python and am probably attempting more than I can handle.
The problem is simple. I can successfully call Python from R (my analysis software) to send an e-mail. Adding the message, subject, to, and from fields I can do. I'd like to be able to send an attachment. Life would be great if I could send just one attachment.
The code I have thus far is
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 email.utils
fromaddr = 'someone#gmail.com'
toaddrs = 'recipient#gmail.org'
msg = MIMEMultipart(MIMEText('This is the body of the e-mail'))
msg['From'] = email.utils.formataddr(('Benjamin Nutter', fromaddr))
msg['To'] = email.utils.formataddr(('Benjamin Nutter', toaddrs))
msg['Subject'] = 'Simple test message'
f = 'filename.pdf'
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))
msg.attach(part)
"username = 'user'
"password = 'pw'
server = smtplib.SMTP('smtp.gmail.com:587')
server.ehlo()
server.starttls()
server.ehlo()
server.login(username,password)
server.sendmail(fromaddr, toaddrs, msg.as_string())
server.quit()
When I run this code, I get the message
string payload expected: [type 'list'] (but with < not [)
I'm at my limit for self-learning today. I'm hoping this is an obvious fix to someone more experienced than myself.
I hope you're all having a great day.

You might try using 'mailer' instead of trying to use SMTP directly. Mailer can be found here.
Here is some simple code that shows how it works.
messages=[]
message = mailer.Message()
message.attach('filename.txt')
message.From = 'Cool guy <cool.guy#example.com>'
message.To = 'Random Dude <random.dude#example.com>'
message.Cc = 'Cards Fan <cardsfan#example.com>'
message.Subject = 'Test Email'
message.body = 'Here is the body of the email.'
messages.append(message)
emailer = mailer.Mailer(smtphost.example.com)
emailer.send(messages)
I cobbled this together from some examples I had locally. The mailer page linked above also shows other examples. Once I found this code, I converted all my other python email code to use this package.

I know it's bad form to answer my own question, but it started working miraculously with no changes. What a way to make my first impression, right?
Anyway, I wrapped it into an R function. This will send from gmail, but I haven't tried sending it from other accounts yet. I'm most interested in sending from Outlook, since I'd be using this to send analysis reports from within my scripts. When I entered my employer's SMTP server, it gave the error "SMTP AUTH extension not supported by server." I suspect I'll have to work this out with my tech support guys.
This will probably only work on Windows, thanks to the winDialog() functions. But it's a good start.
send.email <- function(to, from, subject,
message, attachment=NULL,
username, password,
server="smtp.gmail.com:587",
confirmBeforeSend=TRUE){
# to: a list object of length 1. Using list("Recipient" = "recip#somewhere.net") will send the message to the address but
# the name will appear instead of the address.
# from: a list object of length 1. Same behavior as 'to'
# subject: Character(1) giving the subject line.
# message: Character(1) giving the body of the message
# attachment: Character(1) giving the location of the attachment
# username: character(1) giving the username. If missing and you are using Windows, R will prompt you for the username.
# password: character(1) giving the password. If missing and you are using Windows, R will prompt you for the password.
# server: character(1) giving the smtp server.
# confirmBeforeSend: Logical. If True, a dialog box appears seeking confirmation before sending the e-mail. This is to
# prevent me to send multiple updates to a collaborator while I am working interactively.
if (!is.list(to) | !is.list(from)) stop("'to' and 'from' must be lists")
if (length(from) > 1) stop("'from' must have length 1")
if (length(to) > 1) stop("'send.email' currently only supports one recipient e-mail address")
if (length(attachment) > 1) stop("'send.email' can currently send only one attachment")
if (length(message) > 1){
stop("'message' must be of length 1")
message <- paste(message, collapse="\\n\\n")
}
if (is.null(names(to))) names(to) <- to
if (is.null(names(from))) names(from) <- from
if (!is.null(attachment)) if (!file.exists(attachment)) stop(paste("'", attachment, "' does not exist!", sep=""))
if (missing(username)) username <- winDialogString("Please enter your e-mail username", "")
if (missing(password)) password <- winDialogString("Please enter your e-mail password", "")
require(rJython)
rJython <- rJython()
rJython$exec("import smtplib")
rJython$exec("import os")
rJython$exec("from email.MIMEMultipart import MIMEMultipart")
rJython$exec("from email.MIMEBase import MIMEBase")
rJython$exec("from email.MIMEText import MIMEText")
rJython$exec("from email.Utils import COMMASPACE, formatdate")
rJython$exec("from email import Encoders")
rJython$exec("import email.utils")
mail<-c(
#Email settings
paste("fromaddr = '", from, "'", sep=""),
paste("toaddrs = '", to, "'", sep=""),
"msg = MIMEMultipart()",
paste("msg.attach(MIMEText('", message, "'))", sep=""),
paste("msg['From'] = email.utils.formataddr(('", names(from), "', fromaddr))", sep=""),
paste("msg['To'] = email.utils.formataddr(('", names(to), "', toaddrs))", sep=""),
paste("msg['Subject'] = '", subject, "'", sep=""))
if (!is.null(attachment)){
mail <- c(mail,
paste("f = '", attachment, "'", sep=""),
"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))",
"msg.attach(part)")
}
#SMTP server credentials
mail <- c(mail,
paste("username = '", username, "'", sep=""),
paste("password = '", password, "'", sep=""),
#Set SMTP server and send email, e.g., google mail SMTP server
paste("server = smtplib.SMTP('", server, "')", sep=""),
"server.ehlo()",
"server.starttls()",
"server.ehlo()",
"server.login(username,password)",
"server.sendmail(fromaddr, toaddrs, msg.as_string())",
"server.quit()")
message.details <-
paste("To: ", names(to), " (", unlist(to), ")", "\n",
"From: ", names(from), " (", unlist(from), ")", "\n",
"Using server: ", server, "\n",
"Subject: ", subject, "\n",
"With Attachments: ", attachment, "\n",
"And the message:\n", message, "\n", sep="")
if (confirmBeforeSend)
SEND <- winDialog("yesnocancel", paste("Are you sure you want to send this e-mail to ", unlist(to), "?", sep=""))
else SEND <- "YES"
if (SEND %in% "YES"){
jython.exec(rJython,mail)
cat(message.details)
}
else cat("E-mail Delivery was Canceled by the User")
}

Related

Unable to log in gmail smtp server in python

I following through a tutorial to send an email through python, but it returns an SMTPAuthentication error. Below is my source code:
import smtplib, getpass
# Connect to smtp server
smtpserver = smtplib.SMTP("smtp.gmail.com", 587)
smtpserver.ehlo()
smtpserver.starttls()
smtpserver.ehlo()
print("Successfully connected to gmail.")
# User login
print("Please Login")
gmail_user = str(raw_input("Enter your email: "))
gmail_pwd = getpass.getpass("Enter your password: ")
smtpserver.login(gmail_user, gmail_pwd)
# Destination email
to = str(raw_input("Enter the email you would like to send to: \n"))
# Message contents
header = "To:" + to + "\n" + "From: " + gmail_user + "\n" + "Subject:testing \n"
print header
msg = header + '\n this is test message\n\n'
# Begin sending email
smtpserver.sendmail(gmail_user, to, msg)
print "Success!"
# Close smtpserver
smtpserver.close()
Can anyone tell me what's wrong? I'm pretty sure I typed in the correct email and password. Thanks!
I'm guessing this is the error:
SMTPAuthenticationError: Application-specific password required
You can also try the standard settings of yagmail:
import yagmail
yag = yagmail.Connect(gmail_user, gmail_pwd)
yag.send(to, 'testing', 'this is test message')
yagmail's goal is to make it easy to send gmail messages without all the hassle. It also provides a list of common errors in the documentation.
It also suggests holding the user account in keyring so you never have to write username/pw again (and especially not write it in a script); just run once yagmail.register(gmail_user, gmail_pwd)
Hope to have helped, feel free to check the documentation

Why isn't this text showing up on my phone?

Here is the link to the original post: Why am I not able to send a Colon in a message but am able to send a Semicolon??? (Python, SMTP module) a
It's something to do with the colon in the time. I tried formatting it different ways, and the colon in the time makes the text show up on my phone blank. Here is a simple version of the texting function I am using. It is SMTP to a Verizon iPhone, if that makes a difference. I could use a semicolon and it'd work, but a colon doesn't:
import time
current = (str(time.strftime("%I:%M %p")))
print (current)
def Text():
import smtplib
ContentMatch = ("Content match, website checked at: ", current)
username = ("EmailUser")
password = ("Password1")
fromaddr = ("Username#gmail.com")
toaddrs = ("PhoneNumber#vtext.com")
message = (str(ContentMatch))
# The actual mail send
server = smtplib.SMTP('smtp.gmail.com:587')
server.starttls()
server.login(username,password)
server.sendmail(fromaddr, toaddrs, message)
server.quit()
Text()
Your message is not correctly formatted. The message string must be an RFC2822-compatible message. That is, it must include a header and an optional body.
Try this:
message = "From: %s\n\n%s\n"%(fromaddr, ContentMatch)
But Python provides special classes (e.g. email.mime.text.MIMEText) for formatting email messages, so you don't have to do it by hand:
import time
import smtplib
from email.mime.text import MIMEText
from email.utils import formatdate
current = time.strftime("%I:%M %p")
message_text = "Content match, website checked at: %s" % current
def Text():
# Parameters for sending
username = "xxx"
password = "xxx"
fromaddr = "xxx#example.com"
toaddr = "xxx#example.com"
# Create the message. The only RFC2822-required headers
# are 'Date' and 'From', but adding 'To' is polite
message = MIMEText(message_text)
message['Date'] = formatdate()
message['From'] = fromaddr
# Send the message
server = smtplib.SMTP('smtp.gmail.com:587')
server.starttls()
server.login(username, password)
server.sendmail(fromaddr, toaddr, message.as_string())
server.quit()
Text()

Python smtplib: only the first email out of several arrives to destination

I have put together a function send_email [1] to send emails with support for plain-text and html messages. It works well, but I have an issue I don't quite know how to debug. My system has sendmail as its MTA.
The function is called in a for loop, as follows:
for data in data_set:
subject, message = process(data) # Irrelevant stuff
send_email(subject, message, "fixed#email.address")
In the debug output of smtplib [1] I see that all calls to send_email completed successfully. The weird behavior is:
If the message is "short" (I tested with a single line), all sent messages actually arrive to fixed#email.address
If the message is "not short" (that is, the multiple lines I generate with the real process_data function), only the first email does arrive, while the others don't, even though the debug output of smtplib in [1] reports success for each and all of the emails.
If the message is equally "not short" but the destination address is different for each message, then all messages arrive to their intended destinations.
For the latter case, the for loop would look like:
addresses = ["fixed#email.address", "fixed.2#email.address", ...]
for data, addr in zip(data_set, addresses):
subject, message = process(data) # Irrelevant stuff
send_email(subject, message, addr)
The intended behavior is of course different addresses for different data, but I'm concerned that not understanding why this happens might bite me in an unexpected way later on.
[1] My send mail function:
import smtplib
import socket
import getpass
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
def send_email (subject, message, to, reply_to='', cc='', html_message=''):
COMMASPACE = ", "
user, host = get_user_and_host_names()
sender = '%s#%s' % (user, host)
receivers = make_address_list(to)
copies = make_address_list(cc)
msg = MIMEMultipart('alternative')
msg['Subject'] = subject
msg['From'] = sender
msg['To'] = COMMASPACE.join(receivers)
if reply_to:
msg.add_header('Reply-to', reply_to)
if len(copies):
msg.add_header('CC', COMMASPACE.join(copies))
# According to RFC 2046, the last part of a multipart message, in this case
# the HTML message, is best and preferred.
if message:
msg.attach( MIMEText(message, 'plain'))
if html_message:
msg.attach( MIMEText(html_message, 'html'))
smtpObj = smtplib.SMTP('localhost')
smtpObj.set_debuglevel(1)
smtpObj.sendmail(sender, receivers, msg.as_string())
smtpObj.quit()
print "\nSuccessfully sent email to:", COMMASPACE.join(receivers)
def get_user_and_host_names():
user = getpass.getuser()
host = socket.gethostname()
return user, host
def make_address_list (addresses):
if isinstance(addresses, str):
receivers = addresses.replace(' ','').split(',')
elif isinstance(addresses, list):
receivers = addresses
return receivers
A working solution I had for sending email:
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 smtplib
class EMail(object):
""" Class defines method to send email
"""
def __init__(self, mailFrom, server, usrname, password, files, debug=False):
self.debug = debug
self.mailFrom = mailFrom
self.smtpserver = server
self.EMAIL_PORT = 587
self.usrname = usrname
self.password = password
def sendMessage(self, subject, msgContent, files, mailto):
""" Send the email message
Args:
subject(string): subject for the email
msgContent(string): email message Content
files(List): list of files to be attached
mailto(string): email address to be sent to
"""
msg = self.prepareMail(subject, msgContent, files, mailto)
# connect to server and send email
server=smtplib.SMTP(self.smtpserver, port=self.EMAIL_PORT)
server.ehlo()
# use encrypted SSL mode
server.starttls()
# to make starttls work
server.ehlo()
server.login(self.usrname, self.password)
server.set_debuglevel(self.debug)
try:
failed = server.sendmail(self.mailFrom, mailto, msg.as_string())
except Exception as er:
print er
finally:
server.quit()
def prepareMail(self, subject, msgHTML, attachments, mailto):
""" Prepare the email to send
Args:
subject(string): subject of the email.
msgHTML(string): HTML formatted email message Content.
attachments(List): list of file paths to be attached with email.
"""
msg = MIMEMultipart()
msg['From'] = self.mailFrom
msg['To'] = mailto
msg['Date'] = formatdate(localtime=True)
msg['Subject'] = subject
#the Body message
msg.attach(MIMEText(msgHTML, 'html'))
msg.attach(MIMEText("Add signature here"))
if attachments:
for phile in attachments:
# we could check for MIMETypes here
part = MIMEBase('application',"octet-stream")
part.set_payload(open(phile, "rb").read())
Encoders.encode_base64(part)
part.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(phile))
msg.attach(part)
return msg
I hope this helps.
My Working solution to format your list in this manner (after many hours of experiment):
["firstemail#mail.com", "secondmail#mail.com", "thirdmail#email.com"]
I used:
to_address = list(str(self.to_address_list).split(","))
to convert my QString into string then into list with splitting with a ","
In my case COMMASPACE was not working, because on splitting a space was already added by default.

Cannot send an email with python smtp

I am developing an application using python where I need to send a file through mail. I wrote a program to send the mail but dont know there's something wrong. The code is posted below. Please any one help me with this smtp library. Is there's anything i m missing? And also can someone please tell me what will be the host in smtp! I am using smtp.gmail.com.
Also can any one tell me how can i email a file (.csv file). Thanks for the help!
#!/usr/bin/python
import smtplib
sender = 'someone#yahoo.com'
receivers = ['someone#yahoo.com']
message = """From: From Person <someone#yahoo.com>
To: To Person <someone#yahoo.com>
Subject: SMTP e-mail test
This is a test e-mail message.
"""
try:
smtpObj = smtplib.SMTP('smtp.gmail.com')
smtpObj.sendmail(sender, receivers, message)
print "Successfully sent email"
except:
print "Error: unable to send email"
You aren't logging in. There are also a couple reasons you might not make it through including blocking by your ISP, gmail bouncing you if it can't get a reverse DNS on you, etc.
try:
smtpObj = smtplib.SMTP('smtp.gmail.com', 587) # or 465
smtpObj.ehlo()
smtpObj.starttls()
smtpObj.login(account, password)
smtpObj.sendmail(sender, receivers, message)
print "Successfully sent email"
except:
print "Error: unable to send email"
I just noticed your request to be able to attach a file. That changes things since now you need to deal with encoding. Still not that tough to follow though I don't think.
import os
import email
import email.encoders
import email.mime.text
import smtplib
# message/email details
my_email = 'myemail#gmail.com'
my_passw = 'asecret!'
recipients = ['jack#gmail.com', 'jill#gmail.com']
subject = 'This is an email'
message = 'This is the body of the email.'
file_name = 'C:\\temp\\test.txt'
# build the message
msg = email.MIMEMultipart.MIMEMultipart()
msg['From'] = my_email
msg['To'] = ', '.join(recipients)
msg['Date'] = email.Utils.formatdate(localtime=True)
msg['Subject'] = subject
msg.attach(email.MIMEText.MIMEText(message))
# build the attachment
att = email.MIMEBase.MIMEBase('application', 'octet-stream')
att.set_payload(open(file_name, 'rb').read())
email.Encoders.encode_base64(att)
att.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(file_name))
msg.attach(att)
# send the message
srv = smtplib.SMTP('smtp.gmail.com', 587)
srv.ehlo()
srv.starttls()
srv.login(my_email, my_passw)
srv.sendmail(my_email, recipients, msg.as_string())

How to send an email with Python?

This code works and sends me an email just fine:
import smtplib
#SERVER = "localhost"
FROM = 'monty#python.com'
TO = ["jon#mycompany.com"] # must be a list
SUBJECT = "Hello!"
TEXT = "This message was sent with Python's smtplib."
# Prepare actual message
message = """\
From: %s
To: %s
Subject: %s
%s
""" % (FROM, ", ".join(TO), SUBJECT, TEXT)
# Send the mail
server = smtplib.SMTP('myserver')
server.sendmail(FROM, TO, message)
server.quit()
However if I try to wrap it in a function like this:
def sendMail(FROM,TO,SUBJECT,TEXT,SERVER):
import smtplib
"""this is some test documentation in the function"""
message = """\
From: %s
To: %s
Subject: %s
%s
""" % (FROM, ", ".join(TO), SUBJECT, TEXT)
# Send the mail
server = smtplib.SMTP(SERVER)
server.sendmail(FROM, TO, message)
server.quit()
and call it I get the following errors:
Traceback (most recent call last):
File "C:/Python31/mailtest1.py", line 8, in <module>
sendmail.sendMail(sender,recipients,subject,body,server)
File "C:/Python31\sendmail.py", line 13, in sendMail
server.sendmail(FROM, TO, message)
File "C:\Python31\lib\smtplib.py", line 720, in sendmail
self.rset()
File "C:\Python31\lib\smtplib.py", line 444, in rset
return self.docmd("rset")
File "C:\Python31\lib\smtplib.py", line 368, in docmd
return self.getreply()
File "C:\Python31\lib\smtplib.py", line 345, in getreply
raise SMTPServerDisconnected("Connection unexpectedly closed")
smtplib.SMTPServerDisconnected: Connection unexpectedly closed
Can anyone help me understand why?
I recommend that you use the standard packages email and smtplib together to send email. Please look at the following example (reproduced from the Python documentation). Notice that if you follow this approach, the "simple" task is indeed simple, and the more complex tasks (like attaching binary objects or sending plain/HTML multipart messages) are accomplished very rapidly.
# Import smtplib for the actual sending function
import smtplib
# Import the email modules we'll need
from email.mime.text import MIMEText
# Open a plain text file for reading. For this example, assume that
# the text file contains only ASCII characters.
with open(textfile, 'rb') as fp:
# Create a text/plain message
msg = MIMEText(fp.read())
# me == the sender's email address
# you == the recipient's email address
msg['Subject'] = 'The contents of %s' % textfile
msg['From'] = me
msg['To'] = you
# Send the message via our own SMTP server, but don't include the
# envelope header.
s = smtplib.SMTP('localhost')
s.sendmail(me, [you], msg.as_string())
s.quit()
For sending email to multiple destinations, you can also follow the example in the Python documentation:
# Import smtplib for the actual sending function
import smtplib
# Here are the email package modules we'll need
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
# Create the container (outer) email message.
msg = MIMEMultipart()
msg['Subject'] = 'Our family reunion'
# me == the sender's email address
# family = the list of all recipients' email addresses
msg['From'] = me
msg['To'] = ', '.join(family)
msg.preamble = 'Our family reunion'
# Assume we know that the image files are all in PNG format
for file in pngfiles:
# Open the files in binary mode. Let the MIMEImage class automatically
# guess the specific image type.
with open(file, 'rb') as fp:
img = MIMEImage(fp.read())
msg.attach(img)
# Send the email via our own SMTP server.
s = smtplib.SMTP('localhost')
s.sendmail(me, family, msg.as_string())
s.quit()
As you can see, the header To in the MIMEText object must be a string consisting of email addresses separated by commas. On the other hand, the second argument to the sendmail function must be a list of strings (each string is an email address).
So, if you have three email addresses: person1#example.com, person2#example.com, and person3#example.com, you can do as follows (obvious sections omitted):
to = ["person1#example.com", "person2#example.com", "person3#example.com"]
msg['To'] = ",".join(to)
s.sendmail(me, to, msg.as_string())
the ",".join(to) part makes a single string out of the list, separated by commas.
From your questions I gather that you have not gone through the Python tutorial - it is a MUST if you want to get anywhere in Python - the documentation is mostly excellent for the standard library.
When I need to mail in Python, I use the mailgun API which gets a lot of the headaches with sending mails sorted out. They have a wonderful app/api that allows you to send 5,000 free emails per month.
Sending an email would be like this:
def send_simple_message():
return requests.post(
"https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages",
auth=("api", "YOUR_API_KEY"),
data={"from": "Excited User <mailgun#YOUR_DOMAIN_NAME>",
"to": ["bar#example.com", "YOU#YOUR_DOMAIN_NAME"],
"subject": "Hello",
"text": "Testing some Mailgun awesomness!"})
You can also track events and lots more, see the quickstart guide.
I'd like to help you with sending emails by advising the yagmail package (I'm the maintainer, sorry for the advertising, but I feel it can really help!).
The whole code for you would be:
import yagmail
yag = yagmail.SMTP(FROM, 'pass')
yag.send(TO, SUBJECT, TEXT)
Note that I provide defaults for all arguments, for example if you want to send to yourself, you can omit TO, if you don't want a subject, you can omit it also.
Furthermore, the goal is also to make it really easy to attach html code or images (and other files).
Where you put contents you can do something like:
contents = ['Body text, and here is an embedded image:', 'http://somedomain/image.png',
'You can also find an audio file attached.', '/local/path/song.mp3']
Wow, how easy it is to send attachments! This would take like 20 lines without yagmail ;)
Also, if you set it up once, you'll never have to enter the password again (and have it safely stored). In your case you can do something like:
import yagmail
yagmail.SMTP().send(contents = contents)
which is much more concise!
I'd invite you to have a look at the github or install it directly with pip install yagmail.
Here is an example on Python 3.x, much simpler than 2.x:
import smtplib
from email.message import EmailMessage
def send_mail(to_email, subject, message, server='smtp.example.cn',
from_email='xx#example.com'):
# import smtplib
msg = EmailMessage()
msg['Subject'] = subject
msg['From'] = from_email
msg['To'] = ', '.join(to_email)
msg.set_content(message)
print(msg)
server = smtplib.SMTP(server)
server.set_debuglevel(1)
server.login(from_email, 'password') # user & password
server.send_message(msg)
server.quit()
print('successfully sent the mail.')
call this function:
send_mail(to_email=['12345#qq.com', '12345#126.com'],
subject='hello', message='Your analysis has done!')
below may only for Chinese user:
If you use 126/163, 网易邮箱, you need to set"客户端授权密码", like below:
ref: https://stackoverflow.com/a/41470149/2803344
https://docs.python.org/3/library/email.examples.html#email-examples
There is indentation problem. The code below will work:
import textwrap
def sendMail(FROM,TO,SUBJECT,TEXT,SERVER):
import smtplib
"""this is some test documentation in the function"""
message = textwrap.dedent("""\
From: %s
To: %s
Subject: %s
%s
""" % (FROM, ", ".join(TO), SUBJECT, TEXT))
# Send the mail
server = smtplib.SMTP(SERVER)
server.sendmail(FROM, TO, message)
server.quit()
While indenting your code in the function (which is ok), you did also indent the lines of the raw message string. But leading white space implies folding (concatenation) of the header lines, as described in sections 2.2.3 and 3.2.3 of RFC 2822 - Internet Message Format:
Each header field is logically a single line of characters comprising
the field name, the colon, and the field body. For convenience
however, and to deal with the 998/78 character limitations per line,
the field body portion of a header field can be split into a multiple
line representation; this is called "folding".
In the function form of your sendmail call, all lines are starting with white space and so are "unfolded" (concatenated) and you are trying to send
From: monty#python.com To: jon#mycompany.com Subject: Hello! This message was sent with Python's smtplib.
Other than our mind suggests, smtplib will not understand the To: and Subject: headers any longer, because these names are only recognized at the beginning of a line. Instead smtplib will assume a very long sender email address:
monty#python.com To: jon#mycompany.com Subject: Hello! This message was sent with Python's smtplib.
This won't work and so comes your Exception.
The solution is simple: Just preserve the message string as it was before. This can be done by a function (as Zeeshan suggested) or right away in the source code:
import smtplib
def sendMail(FROM,TO,SUBJECT,TEXT,SERVER):
"""this is some test documentation in the function"""
message = """\
From: %s
To: %s
Subject: %s
%s
""" % (FROM, ", ".join(TO), SUBJECT, TEXT)
# Send the mail
server = smtplib.SMTP(SERVER)
server.sendmail(FROM, TO, message)
server.quit()
Now the unfolding does not occur and you send
From: monty#python.com
To: jon#mycompany.com
Subject: Hello!
This message was sent with Python's smtplib.
which is what works and what was done by your old code.
Note that I was also preserving the empty line between headers and body to accommodate section 3.5 of the RFC (which is required) and put the include outside the function according to the Python style guide PEP-0008 (which is optional).
Make sure you have granted permission for both Sender and Receiver to send email and receive email from Unknown sources(External Sources) in Email Account.
import smtplib
#Ports 465 and 587 are intended for email client to email server communication - sending email
server = smtplib.SMTP('smtp.gmail.com', 587)
#starttls() is a way to take an existing insecure connection and upgrade it to a secure connection using SSL/TLS.
server.starttls()
#Next, log in to the server
server.login("#email", "#password")
msg = "Hello! This Message was sent by the help of Python"
#Send the mail
server.sendmail("#Sender", "#Reciever", msg)
It's probably putting tabs into your message. Print out message before you pass it to sendMail.
It's worth noting that the SMTP module supports the context manager so there is no need to manually call quit(), this will guarantee it is always called even if there is an exception.
with smtplib.SMTP_SSL('smtp.gmail.com', 465) as server:
server.ehlo()
server.login(user, password)
server.sendmail(from, to, body)
I haven't been satisfied with the package options for sending emails and I decided to make and open source my own email sender. It is easy to use and capable of advanced use cases.
To install:
pip install redmail
Usage:
from redmail import EmailSender
email = EmailSender(
host="<SMTP HOST ADDRESS>",
port=<PORT NUMBER>,
)
email.send(
sender="me#example.com",
receivers=["you#example.com"],
subject="An example email",
text="Hi, this is text body.",
html="<h1>Hi,</h1><p>this is HTML body</p>"
)
If your server requires a user and a password, just pass user_name and password to the EmailSender.
I have included a lot of features wrapped in the send method:
Include attachments
Include images directly to the HTML body
Jinja templating
Prettier HTML tables out of the box
Documentation:
https://red-mail.readthedocs.io/en/latest/
Source code: https://github.com/Miksus/red-mail
Thought I'd put in my two bits here since I have just figured out how this works.
It appears that you don't have the port specified on your SERVER connection settings, this effected me a little bit when I was trying to connect to my SMTP server that isn't using the default port: 25.
According to the smtplib.SMTP docs, your ehlo or helo request/response should automatically be taken care of, so you shouldn't have to worry about this (but might be something to confirm if all else fails).
Another thing to ask yourself is have you allowed SMTP connections on your SMTP server itself? For some sites like GMAIL and ZOHO you have to actually go in and activate the IMAP connections within the email account. Your mail server might not allow SMTP connections that don't come from 'localhost' perhaps? Something to look into.
The final thing is you might want to try and initiate the connection on TLS. Most servers now require this type of authentication.
You'll see I've jammed two TO fields into my email. The msg['TO'] and msg['FROM'] msg dictionary items allows the correct information to show up in the headers of the email itself, which one sees on the receiving end of the email in the To/From fields (you might even be able to add a Reply To field in here. The TO and FROM fields themselves are what the server requires. I know I've heard of some email servers rejecting emails if they don't have the proper email headers in place.
This is the code I've used, in a function, that works for me to email the content of a *.txt file using my local computer and a remote SMTP server (ZOHO as shown):
def emailResults(folder, filename):
# body of the message
doc = folder + filename + '.txt'
with open(doc, 'r') as readText:
msg = MIMEText(readText.read())
# headers
TO = 'to_user#domain.com'
msg['To'] = TO
FROM = 'from_user#domain.com'
msg['From'] = FROM
msg['Subject'] = 'email subject |' + filename
# SMTP
send = smtplib.SMTP('smtp.zoho.com', 587)
send.starttls()
send.login('from_user#domain.com', 'password')
send.sendmail(FROM, TO, msg.as_string())
send.quit()
Another implementation using gmail let's say:
import smtplib
def send_email(email_address: str, subject: str, body: str):
"""
send_email sends an email to the email address specified in the
argument.
Parameters
----------
email_address: email address of the recipient
subject: subject of the email
body: body of the email
"""
server = smtplib.SMTP('smtp.gmail.com', 587)
server.starttls()
server.login("email_address", "password")
server.sendmail("email_address", email_address,
"Subject: {}\n\n{}".format(subject, body))
server.quit()
I wrote a simple function send_email() for email sending with smtplib and email packages (link to my article). It additionally uses dotenv package to loads the sender email and password (please don't keep secrets in the code!). I was using Gmail for email service. The password was the App Password (here is Google docs on how to generate App Password).
import os
import smtplib
from email.message import EmailMessage
from dotenv import load_dotenv
_ = load_dotenv()
def send_email(to, subject, message):
try:
email_address = os.environ.get("EMAIL_ADDRESS")
email_password = os.environ.get("EMAIL_PASSWORD")
if email_address is None or email_password is None:
# no email address or password
# something is not configured properly
print("Did you set email address and password correctly?")
return False
# create email
msg = EmailMessage()
msg['Subject'] = subject
msg['From'] = email_address
msg['To'] = to
msg.set_content(message)
# send email
with smtplib.SMTP_SSL('smtp.gmail.com', 465) as smtp:
smtp.login(email_address, email_password)
smtp.send_message(msg)
return True
except Exception as e:
print("Problem during send email")
print(str(e))
return False
The above approach is OK for simple email sending. If you are looking for more advanced features, such as HTML content or attachments - it, of course, can be hand-coded, but I would recommend using existing packages, for example yagmail.
Gmail has a limit of 500 emails per day. For sending many emails per day please consider transactional email service providers, like Amazon SES, MailGun, MailJet, or SendGrid.
import smtplib
s = smtplib.SMTP(your smtp server, smtp port) #SMTP session
message = "Hii!!!"
s.sendmail("sender", "Receiver", message) # sending the mail
s.quit() # terminating the session
just to complement the answer and so that your mail delivery system can be scalable.
I recommend having a configuration file (it can be .json, .yml, .ini, etc) with the sender's email configuration , password and recipients.
This way you can create different customizable items according to your needs.
Below is a small example with 3 files, config, functions and main. Text-only mailing.
config_email.ini
[email_1]
sender = test#test.com
password = XXXXXXXXXXX
recipients= ["email_2#test.com", "email_2#test.com"]
[email_2]
sender = test_2#test.com
password = XXXXXXXXXXX
recipients= ["email_2#test.com", "email_2#test.com", "email_3#test.com"]
These items will be called from main.py, which will return their respective values.
File with functions functions_email.py:
import smtplib,configparser,json
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
def get_credentials(item):
parse = configparser.ConfigParser()
parse.read('config_email.ini')
sender = parse[item]['sender ']
password = parse[item]['password']
recipients= json.loads(parse[item]['recipients'])
return sender,password,recipients
def get_msg(sender,recipients,subject,mail_body):
msg = MIMEMultipart()
msg['Subject'] = subject
msg['From'] = sender
msg['To'] = ', '.join(recipients)
text = """\
"""+mail_body+""" """
part1 = MIMEText(text, "plain")
msg.attach(part1)
return msg
def send_email(msg,sender,password,recipients):
s = smtplib.SMTP('smtp.test.com')
s.login(sender,password)
s.sendmail(sender, recipients, msg.as_string())
s.quit()
File main.py:
from functions_email import *
sender,password,recipients = get_credenciales('email_2')
subject= 'text to subject'
mail_body = 'body....................'
msg = get_msg(sender,recipients ,subject,mail_body)
send_email(msg,sender,password,recipients)
Best regards!
import smtplib, ssl
port = 587 # For starttls
smtp_server = "smtp.office365.com"
sender_email = "170111018#student.mit.edu.tr"
receiver_email = "professordave#hotmail.com"
password = "12345678"
message = """\
Subject: Final exam
Teacher when is the final exam?"""
def SendMailf():
context = ssl.create_default_context()
with smtplib.SMTP(smtp_server, port) as server:
server.ehlo() # Can be omitted
server.starttls(context=context)
server.ehlo() # Can be omitted
server.login(sender_email, password)
server.sendmail(sender_email, receiver_email, message)
print("mail send")
After a lot of fiddling with the examples e.g here
this now works for me:
import smtplib
from email.mime.text import MIMEText
# SMTP sendmail server mail relay
host = 'mail.server.com'
port = 587 # starttls not SSL 465 e.g gmail, port 25 blocked by most ISPs & AWS
sender_email = 'name#server.com'
recipient_email = 'name#domain.com'
password = 'YourSMTPServerAuthenticationPass'
subject = "Server - "
body = "Message from server"
def sendemail(host, port, sender_email, recipient_email, password, subject, body):
try:
p1 = f'<p><HR><BR>{recipient_email}<BR>'
p2 = f'<h2><font color="green">{subject}</font></h2>'
p3 = f'<p>{body}'
p4 = f'<p>Kind Regards,<BR><BR>{sender_email}<BR><HR>'
message = MIMEText((p1+p2+p3+p4), 'html')
# servers may not accept non RFC 5321 / RFC 5322 / compliant TXT & HTML typos
message['From'] = f'Sender Name <{sender_email}>'
message['To'] = f'Receiver Name <{recipient_email}>'
message['Cc'] = f'Receiver2 Name <>'
message['Subject'] = f'{subject}'
msg = message.as_string()
server = smtplib.SMTP(host, port)
print("Connection Status: Connected")
server.set_debuglevel(1)
server.ehlo()
server.starttls()
server.ehlo()
server.login(sender_email, password)
print("Connection Status: Logged in")
server.sendmail(sender_email, recipient_email, msg)
print("Status: Email as HTML successfully sent")
except Exception as e:
print(e)
print("Error: unable to send email")
# Run
sendemail(host, port, sender_email, recipient_email, password, subject, body)
print("Status: Exit")
As far your code is concerned, there doesn't seem to be anything fundamentally wrong with it except that, it is unclear how you're actually calling that function. All I can think of is that when your server is not responding then you will get this SMTPServerDisconnected error. If you lookup the getreply() function in smtplib (excerpt below), you will get an idea.
def getreply(self):
"""Get a reply from the server.
Returns a tuple consisting of:
- server response code (e.g. '250', or such, if all goes well)
Note: returns -1 if it can't read response code.
- server response string corresponding to response code (multiline
responses are converted to a single, multiline string).
Raises SMTPServerDisconnected if end-of-file is reached.
"""
check an example at https://github.com/rreddy80/sendEmails/blob/master/sendEmailAttachments.py that also uses a function call to send an email, if that's what you're trying to do (DRY approach).

Categories