I am relatively new to Python.
I have a Python script that grabs some information from the internet and then inserts in a database. I want to run it with cron daily and have the script send me an email each day after it is done.
I already have the email part figured out and it is working for sending the elapsed time and the total records inserted in the database, like this:
at the start of the script:
records_inserted = 0
t1_start = time.perf_counter()
and at the end of the script:
t1_stop = time.perf_counter()
msg = EmailMessage()
msg.set_content("Total elapsed time: %.1f [min]" % ((t1_stop-t1_start)/60) +
"\n\nThe total number of records inserted in the database was: " +
str(records_inserted))
email_from = “xxxxxxx#gmail.com"
email_to = “xxxxx#xxxx.com"
gmail_password = “xxxxxx”
msg['From'] = email_from
msg['To'] = email_to
msg['Subject'] = “Script executed"
server = smtplib.SMTP('smtp.gmail.com', 587)
server.starttls()
server.login(email_from, gmail_password)
server.send_message(msg)
server.quit()
Now, if something in the webpages where I'm getting the content from breaks or changes or there is any error for any reason, I would like to see the errors also in that daily email.
What would be the best way to handle and store(maybe an array) in case there would be one or several errors during execution, and have the error number and description written as part of the email body so I am aware as soon as possible and can take a look and correct as necessary?
Thanks!
The solution proposed by #fernandezcuesta in the comments is working nicely for me.
I wrappped all the code that 'does the stuff' into a try: except Exception as _ex: and attach repr(_ex) to my mail
Related
This question already has an answer here:
How to send different content email to different recipient in Python?
(1 answer)
Closed 11 months ago.
I want my task to be 1 by 1 not bulk so I'm sending from list of emails. I want it sending to first one then repeats task and sends to second one. The code already works properly but it sends as bulk all emails together so when recipient gets the email it's like all the emails list included in header. That's why I want it to send it one by one from my list.
import smtplib, ssl
from time import strftime
from email.header import Header
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from colorama import *
from datetime import datetime
context = ssl.create_default_context()
def get_contacts(filename):
emails = []
with open(filename, mode='r', encoding='utf-8') as contacts_file:
for a_contact in contacts_file:
emails.append(a_contact.split()[0])
return emails
mails = get_contacts('smtp/contacts.txt')
smtp_user = 'mYsmtp#smtp.com'
def smtp(smtp_server, port, user, password):
context = ssl.create_default_context()
with smtplib.SMTP_SSL(smtp_server, port) as server:
try:
message = MIMEMultipart('alternative')
message['Subject'] = 'TESTING'
message['From'] = str(Header(f'INFO <{smtp_user}>'))
message['To'] = ', '.join(mails)
myfile = open('smtp/letter.txt', 'r')
data = myfile.read()
part = MIMEText(data, 'html')
message.attach(part)
now = datetime.now()
current_time = now.strftime("%H:%M:%S")
server.login(user, password)
server.sendmail(user, mails, message.as_string())
for email in mails:
print('\n',email + ''' SENT! ''')
except smtplib.SMTPAuthenticationError or smtplib.SMTPConnectError :
print('Dead SMTP')
Response:
You just have to move put inside your for email in mails:
for email in mails:
print('\n',email + ''' SENT! ''')
message['To'] = email
server.sendmail(user, mails, message.as_string())
Remove from your original script:
message['To'] = ', '.join(mails)
and
server.sendmail(user, mails, message.as_string())
Notes:
Next time consider undersanding your script before posting your question as this is a very simple thing to do and the answer is already all over the internet.
your try have too many instructions you should only have the instruction that may fail with the handled errors inside it, as the errors that you're handling are smtplib.SMTPAuthenticationError and smtplib.SMTPConnectError: server.login and server.sendmail
Do not post your entire script or big code chunks this is rude and you'll not getting appropriated answers this way.
Your code very explicitly adds all recipients to the To: header and then sends a single message.
As an aside, your code seems to be written for Python 3.5 or earlier. The email library was overhauled in 3.6 and is now quite a bit more versatile and logical. Probably throw away what you have and start over with the examples from the email documentation. Something like this, maybe:
from email.message import EmailMessage
from email.headerregistry import Address
...
mails = get_contacts('smtp/contacts.txt')
smtp_user = 'mYsmtp#smtp.com'
def generate_messages(recipients):
with open('smtp/letter.txt', 'r') as myfile:
data = myfile.read()
for recipient in recipients:
message = EmailMessage()
message['Subject'] = 'TESTING'
message['From'] = Address("INFO", *smtp_user.split("#"))
message['To'] = recipient
message.set_content(data, 'html')
yield message
def smtp(smtp_server, port, user, password, messages):
with smtplib.SMTP_SSL(smtp_server, port) as server:
server.login(user, password)
for message in messages:
now = datetime.now()
current_time = now.strftime("%H:%M:%S")
server.send_message(message)
smtp("smtp.example.com", 25, "me#example.com", "xyzzy", generate_messages(mails))
Untested, and I ripped out the exception handling because I'm probably unable to guess what sort of exception handling would make sense with this different flow.
The use of a generator to generate the messages is perhaps a bit confusing if you are new to Python. I'm thinking you want to minimize the number of separate login events to the SMTP server, and so the code basically has a callback to produce the next message from within the smtp function.
I'm using SMTPlib to automatically send out an email:
emailto = ['distro#email.com','me#email.com']
emailfrom = "me#email.com"
msg = MIMEMultipart('related')
msg['Subject'] = currentdate + " Subject"
msg['From'] = emailfrom
msg['To'] = ", ".join(emailto)
msgAlternative = MIMEMultipart('alternative')
msg.attach(msgAlternative)
msgAlternative.attach(msgText)
smtpObj = smtplib.SMTP('mail.email.com')
smtpObj.ehlo()
smtpObj.starttls()
smtpObj.sendmail(emailfrom, emailto, msg.as_string())
smtpObj.quit()
When I use this code I get the email, with distro#email.com in the "To:" line as well, but no one in distro#email.com gets it. I've sent to distribution lists before with no problem, but this specific one will not work. It is a fairly large list (~100 recipients)
Errors may pass unnoticed, because you are not checking the result of .sendmail() when sending to multiple addresses.
This method will not raise an exception if it succeeds to send the email to at least one recipient.
The important part of the docs is:
This method will return normally if the mail is accepted for at least
one recipient. Otherwise it will raise an exception.
If this method does not raise an exception, it returns a dictionary,
with one entry for each recipient that was refused. Each entry
contains a tuple of the SMTP error code and the accompanying error
message sent by the server.
Something like this should help to find the problem:
errors = smtpObj.sendmail(emailfrom, emailto, msg.as_string())
for recipient, (code, errmsg) in errors.items():
# ... print or log the error ...
First, let me say, I already know that this was asked at Forwarding an email with python smtplib already.
The reason that I am posting something so closely related to that question is that I have tried using the answers to that question, I have tried changing things, I have searched Google and relentlessly monkeyed with this for about 5 hours now, and I am willing to spend a lot more time on this
-- I just thought one of you might have the answer though :)
My problem is as follows, I am trying to forward an email from my gmail to another gmail, and in running as many python script as I can to try this simple task, I still cannot figure it out.
Here is the code that I am running(this is my modified version of what was posted in the other form):
import smtplib, imaplib, email, string
imap_host = "imap.gmail.com"
imap_port = 993
smtp_host = "smtp.gmail.com"
smtp_port = 587
user = "John.Michael.Dorian.4"
passwd = "mypassword"
msgid = 1
from_addr = "John.Michael.Dorian.4#gmail.com"
to_addr = "myotheremail#gmail.com"
# open IMAP connection and fetch message with id msgid
# store message data in email_data
client = imaplib.IMAP4_SSL(imap_host, imap_port)
client.login(user, passwd)
client.select()
typ, data = client.search(None, 'ALL')
for mail in data[0].split():
typ, data = client.fetch(msgid, "(RFC822)")
email_data = data[0][1]
client.close()
client.logout()
# create a Message instance from the email data
message = email.message_from_string(email_data)
# replace headers (could do other processing here)
message.replace_header("From", from_addr)
message.replace_header("To", to_addr)
print message.as_string()
# open authenticated SMTP connection and send message with
# specified envelope from and to addresses
smtp = smtplib.SMTP(smtp_host, smtp_port)
smtp.set_debuglevel(1)
smtp.ehlo()
smtp.starttls()
smtp.ehlo()
smtp.login(user, passwd)
smtp.sendmail(from_addr, to_addr, message.as_string())
smtp.quit()
The return from the SMTP debug says everything went okay, and I know that it is sending because I tried replacing the
smtp.sendmail(from_addr, to_addr, message.as_string())
With
smtp.sendmail(from_addr, to_addr, 'test')
And it worked fine. It prints the message.as_string() fine, and I am at a loss as how to get it to forward the email!
It doesn't have to be with SMTP or IMAP or any of this code(though it would be nice if it was) but I would really like to figure out how to do this.
I know its possible because I managed to do it yesterday, and the computer I was working on(running Windows of course) crashed and the file was gone.
For those of you who are wondering why I do not just set google to forward everything automatically, it is because I want a script that will eventually move a large amount of mail, once.
Thank you everyone!
More than likely, the Received: headers of the original email are causing gmail to drop the message. Try removing all of them before forwarding it.
If that doesn't fix it, print out the headers and code it to remove all of the ones that would not normally be there on a newly composed message.
However, why forward this way? It would be easier to just pull from one IMAP account and push it to another IMAP account directly.
In fact you could use Mozilla Thunderbird to add both accounts and just drag and drop the messages from one to the other.
I have the script below which I'm using to send say 10 messages myself<->myself. However, I've noticed that Python really takes a while to do that. Last year I needed a system to send about 200 emails with attachments and text and I implemented it with msmtp + bash. As far as I remember it was much faster.
Moving the while loop inside (around the smtp_serv.sendmail(sender, recepient, msg) function yields similar results).
Am I doing something wrong? Surely it can't be slower than bash + msmtp (and I'm only sending a 'hi' message, no attachments).
#! /usr/bin/python3.1
def sendmail(recepient, msg):
import smtplib
# Parameters
sender = 'login#gmail.com'
password = 'password'
smtpStr = 'smtp.gmail.com'
smtpPort = 587
# /Parameters
smtp_serv = smtplib.SMTP(smtpStr, smtpPort)
smtp_serv.ehlo_or_helo_if_needed()
smtp_serv.starttls()
smtp_serv.ehlo()
recepientExists = smtp_serv.verify(recepient)
if recepientExists[0] == 250:
smtp_serv.login(sender, password)
try:
smtp_serv.sendmail(sender, recepient, msg)
except smtplib.SMTPException:
print(recepientExists[1])
else:
print('Error', recepientExists[0], ':', recepientExists[1])
smtp_serv.quit()
for in in range(10):
sendmail('receiver#gmail.com', 'hi')
In this script it takes five times more time to setup SMTP connection (5 seconds) than to send a e-mail (1 second) so it could make sense to setup a single connection and send several e-mails instead of creating the connection each time:
#!/usr/bin/env python3
import smtplib
from contextlib import contextmanager
from datetime import datetime
from email.mime.text import MIMEText
from netrc import netrc
from timeit import default_timer as timer
#contextmanager
def logined(sender, password, smtp_host='smtp.gmail.com', smtp_port=587):
start = timer(); smtp_serv = smtplib.SMTP(smtp_host, smtp_port, timeout=10)
try: # make smtp server and login
smtp_serv.ehlo_or_helo_if_needed()
smtp_serv.starttls()
smtp_serv.ehlo()
print('smtp setup took (%.2f seconds passed)' % (timer()-start,))
start = timer(); smtp_serv.login(sender, password)
print('login took %.2f seconds' % (timer()-start,))
start = timer(); yield smtp_serv
finally:
print('Operations with smtp_serv took %.2f seconds' % (timer()-start,))
start = timer(); smtp_serv.quit()
print('Quiting took %.2f seconds' % (timer()-start,))
smtp_host = 'smtp.gmail.com'
login, _, password = netrc().authenticators(smtp_host)
with logined(login, password, smtp_host) as smtp_serv:
for i in range(10):
msg = MIMEText('#%d timestamp %s' % (i, datetime.utcnow()))
msg['Subject'] = 'test #%d' % i
msg['From'] = login
msg['To'] = login
smtp_serv.send_message(msg)
Output
smtp setup took (5.43 seconds passed)
login took 0.40 seconds
Operations with smtp_serv took 9.84 seconds
Quiting took 0.05 seconds
If your Python version doesn't have .send_message() then you could use:
smtp_serv.sendmail(from, to, msg.as_string())
You are opening the connection to the SMTP server and then closing it for each email. It would be more efficient to keep the connection open while sending all of the emails.
Maybe this comes very late, but I think it is relevant for the matter.
I had the same issue recently and realized, by searching around, that the call to connect the SMTP server may be very time consuming due to issues with domain name resolution, since the SMTP server performs a reverse lookup to verify the connecting client.
In my case this call was taking around 1 minute!:
s = smtplib.SMTP(smtp_server)
Solution was to fix the domain name resolution on the Linux box. After that, connection became very quick.
Hope this may be of help.
The real answer here is "profile that code!". Time how long different parts of the code take so you know where most of the time is spent. That way you'll have a real answer without guesswork.
Still, my guess would be that it is the calls to smtp_serv.verify(recipient) may be the slow ones. Reasons might be that the server sometimes needs to ask other SMTP servers for info, or that it does throttling on these operations to avoid having spammers use them massively to gather email addresses.
Also, try pinging the SMTP server. If the ping-pong takes significant time, I would expect sending each email would take at least that long.
How could I send an e-mail from my Python script that is being run on "Google App Engines" to one of my mail boxes?
I am just a beginner and I have never tried sending a message from a Python script. I have found this script (IN THIS TUTORIAL):
Here is the same script as a quote:
import sys, smtplib
fromaddr = raw_input("From: ")
toaddr = string.splitfields(raw_input("To: "), ',')
print "Enter message, end with ^D:"
msg = ''
while 1:
line = sys.stdin.readline()
if not line:
break
msg = msg + line
# The actual mail send
server = smtplib.SMTP('localhost')
server.sendmail(fromaddr, toaddrs, msg)
server.quit()
but I hardly understand how I could have this script run from "Google App Engine":
1) Firstly, I don't quite understand what e-mail address I need to place right after From: in this line:
fromaddr = raw_input("From: ")
Can I just place here any e-mail address of any e-mail boxes that I have?
2) Secondly, let's say I want to send a message to this e-mail address of mine brilliant#yahoo.com . Then the next line, I guess, must look this way:
toaddr = string.splitfields(raw_input("To: brilliant#yahoo.com"), ',')
Is this right?
3) Thirdly, let's say, the message that I want to send will be this sentence: Cats cannot fly! Then, I guess, the line that starts with msg = must look this way:
msg = 'Cats cannot fly!'
Is this correct?
4) If I upload this script as an application to "GAE", how often will it be sending this message to my mail box? Will it send this message to me only once or it will be sending it to me every second all the time until I delete the application? (This is why I haven't tried uploading this script so far)
Thank You all in advance for Your time and patience.
Sure - just use the Mail API as outlined in the docs:
Python
Java