Python smtplib and configparser as function - python

Hello I try to make a small monitoring, and in the monitoring.py, it should send a email with function sendmail.sende(). But i think, i got issues with variables. Could you give me some tips, what i do wrong please?
This is the sendmail.py
def sende():
import smtplib
import configparser
from email.message import Message
config = configparser.ConfigParser()
config.read('config.ini')
absender=(config['SMTP']['absender'])
password=(config['SMTP']['password'])
empfaenger=(config['SMTP']['empfaenger'])
smtphost=(config['SMTP']['smtphost'])
port=int(config['SMTP']['port'])
def readLastLog():
log=open('log', 'r')
for line in log:
last= line
print (last)
log.close()
#check for if variables is correct
print("Email Config:")
print(absender, '\n',empfaenger,'\n',smtphost,'\n',port)
server = smtplib.SMTP_SSL(host=smtphost, port=port)
#do I need realy need this?
#server.esmtp_features['auth'] = 'LOGIN PLAIN'
nachricht = Message()
nachricht.set_payload(readLastLog())
nachricht["Subject"] = "Kritische Warnung - Hardlimit erreicht"
nachricht["FROM"] = absender
nachricht["To"] = empfaenger
server.login(absender, password)
server.sendmail(absender, empfaenger, nachricht.as_string())
server.quit()
sende()
Error:
Traceback (most recent call last):
File "./sendmail.py", line 42, in <module>
sende()
File "./sendmail.py", line 38, in sende
server.login(absender, password)
File "/usr/lib/python3.5/smtplib.py", line 729, in login
raise last_exception
File "/usr/lib/python3.5/smtplib.py", line 720, in login
initial_response_ok=initial_response_ok)
File "/usr/lib/python3.5/smtplib.py", line 641, in auth
raise SMTPAuthenticationError(code, resp)
smtplib.SMTPAuthenticationError: (535, b'5.7.8 Error: authentication failed: authentication failure')

You did not describe what is going wrong, so just guessing. But the function readLastLog is wrong:
it only print to stdout and does not return anything - so actually returns None
it is later used to initialize the payload which gives only nachricht.set_payload(None)
Some other immediate improvements:
declaring a function inside another one is not wrong and have real use cases, but here I really wonder why readLastLog is defined inside sende body...
as you have built a Message with its To: and From: header, you could use the send_message function:
...
server.send_message(nachricht)
server.quit()

So i changed the readLastLog with a return, and made a seperate function,
Anyway that hasnt fixed the skript, so problem here was maybe with my #web.de mail.
Changed to a own mailserver with other email. Now i do reciving emails.
Sorry for taking ur time.
def readLastLog():
log=open('log', 'r')
for line in log:
lastlog= line
print (lastlog)
log.close()
return lastlog
def sende():
import smtplib
import configparser
from email.message import Message
config = configparser.ConfigParser()
config.read('config.ini')
absender=(config['SMTP']['absender'])
password=(config['SMTP']['password'])
empfaenger=(config['SMTP']['empfaenger'])
smtphost=(config['SMTP']['smtphost'])
port=int(config['SMTP']['port'])
#check for if variables is correct
print("Email Config:")
print(absender, '\n',empfaenger,'\n',smtphost,'\n',port)
server = smtplib.SMTP_SSL(host=smtphost, port=port)
#do I need realy need this?
#server.esmtp_features['auth'] = 'LOGIN PLAIN'
nachricht = Message()
nachricht.set_payload(readLastLog())
nachricht["Subject"] = "Kritische Warnung - Hardlimit erreicht"
nachricht["FROM"] = absender
nachricht["To"] = empfaenger
server.login(absender, password)
server.sendmail(absender, empfaenger, nachricht.as_string())
server.quit()

Related

Why am I receiving a TypeError when trying to use imapclient?

I am new to Python, and I am attempting to write code to get me into my gmail inbox, following along with a Udemy course. I have successfully installed imapclient, and importing impapclient does not return any error so I think I am okay on that.
When I go to write my next line of code conn = imapclient.IMAPClient('imap.gmail.com', ssl=True) and enter it, it gives me a Type Error
Traceback (most recent call last):
File "<pyshell#15>", line 1, in <module>
conn = imapclient.IMAPClient('imap.gmail.com', ssl=True)
File "C:\Users\china\AppData\Local\Programs\Python\Python39\lib\site-packages\imapclient\imapclient.py", line 254, in __init__
self._imap = self._create_IMAP4()
File "C:\Users\china\AppData\Local\Programs\Python\Python39\lib\site-packages\imapclient\imapclient.py", line 288, in _create_IMAP4
return tls.IMAP4_TLS(self.host, self.port, self.ssl_context,
File "C:\Users\china\AppData\Local\Programs\Python\Python39\lib\site-packages\imapclient\tls.py", line 44, in __init__
imaplib.IMAP4.__init__(self, host, port)
File "C:\Users\china\AppData\Local\Programs\Python\Python39\lib\imaplib.py", line 202, in __init__
self.open(host, port, timeout)
TypeError: open() takes 3 positional arguments but 4 were given
I know that both ssl=True and 'imap.gmail.com' are both required arguments, so I have not tried taking either out. I only have two lines of code, and I am following the exact code on the Udemy course, so I am not sure how to resolve this myself.
I had exactly the same problem.
The problem is that imapclient is only officially supported until Python 3.7, so you could try using imapclient with Python 3.7.
Or you could use imaplib module. It is a bit more complicated, but I tried using imaplib and pyzmail module (to make the text more readable)
import imaplib
import pyzmail
server = imaplib.IMAP4_SSL('IMAP_SERVER')
server.login('email#example.com', 'password')
server.select("INBOX")
typ, data = server.search(None, "ALL")
for num in data[0].split():
typ, data = server.fetch(num, '(RFC822)')
message = pyzmail.PyzMessage.factory(data[0][1])
print('THIS IS MESSAGE NO. ' + str(num))
if(message.text_part != None):
print(message.text_part.get_payload().decode(message.text_part.charset))
elif(message.html_part != None):
print(message.html_part.get_payload().decode(message.html_part.charset))
server.close()
server.logout()
im not an expert at Python, so using pyzmail might not be the optimal solution, but it worked for me using Python 3.9
for IMAP_SERVER you have to insert the right IMAP server domain name (like imap.gmail.com for gmail accounts)
and you have to enter your real email address and password (or get the password with input())
I got the same issue. And it has been fixed in IMAPClient 2.2.0.
https://imapclient.readthedocs.io/en/2.2.0/releases.html

How can I pass an external object to a YAML logging configuration file?

I am trying to set up an SMTPHandler in a YAML configuration file. Any time the script fails, I would like the logger to send an email to the person who started the script (e.g. user_email). Based on PEP 391, I am trying to refer to that external object by prefixing ext:// to the string I pass in.
# config.yaml
version: 1
handlers:
email:
class: logging.handlers.SMTPHandler
mailhost: ["smtp.office365.com", 587]
fromaddr: "email#place.com"
toaddrs: [ext://user_email]
subject: "TEST"
credentials: ["email#place.com", "password"]
secure: []
level: ERROR
loggers:
__main__:
level: INFO
handlers: [email]
# __main__.py
import yaml
import logging
import getpass
import logging.config
import logging.handlers
username = getpass.getuser()
user_email = f"{username}#place.com"
with open(r'./config.yaml') as cfg:
config = yaml.safe_load(cfg)
logging.config.dictConfig(config)
log = logging.getLogger(__name__)
try:
raise Exception()
except Exception:
log.exception('Unhandled Exception')
When I run this code, I get an smtplib.SMTPRecipientsRefused error, which says:
Traceback (most recent call last):
File "/Users/jessenestler/anaconda3/envs/facilityid_env/lib/python3.7/logging/handlers.py", line 1020, in emit
smtp.send_message(msg)
File "/Users/jessenestler/anaconda3/envs/facilityid_env/lib/python3.7/smtplib.py", line 967, in send_message
rcpt_options)
File "/Users/jessenestler/anaconda3/envs/facilityid_env/lib/python3.7/smtplib.py", line 881, in sendmail
raise SMTPRecipientsRefused(senderrs)
smtplib.SMTPRecipientsRefused: {'//user_email': (501, b'5.1.3 Invalid address')}
It looks like the // doesn't get stripped properly, but even when I write toaddrs: [ext:user_email] in my config file, I still get the same error, but it says smtplib.SMTPRecipientsRefused: {'user_email': (501, b'5.1.3 Invalid address')}.
How do I make my YAML config file accept the user_email variable so that it successfully sends an email? I'm in Python 3.7
I don't know how relevant this still is for you but I had the exact same bug.
Apparently the issue is from yaml's parser specially treating the "[ext:" character sequence. Simply strip the "[" character from that and leave it like the following:
toaddrs: ext://script_name.user_email
In which user_email is a global variable declared inside script_name.py
I think [ext: might be some special syntax from yaml's official specification or something. Really annoying

Python Django multiprocessing Error: "AttributeError: Can't pickle local object 'Command.handle.<locals>.f'"

I'm using python 3.6 django 1.11 windows10 and trying to send email using multiprocessing.
recipients are connected to database(django model) and email contents form is html(by django get_template), also used get_connection to send multi-email. What I'm trying is to use multiprocessing to accelerate sending rate.
My code is here
from django.core.management.base import BaseCommand
from django.core.mail import get_connection, EmailMessage
from django.template.loader import get_template
from multiprocessing import Pool
from sending.models import Subscriber
class Command(BaseCommand):
def handle(self, *args, **options):
connection = get_connection()
recipients = [i.email for i in Subscriber.objects.all()]
num = Subscriber.objects.count()
subject = 'Multiprocessing trying 1'
body = get_template('Newsletter20171229.html').render()
from_email = 'info#modoodoc.com'
def send(i):
msg = EmailMessage(subject, body, from_email, [recipients[i]])
msg.content_subtype = 'html'
print("Sending email to: " + recipients[i] + " No: " + str(i))
connection.send_messages([msg])
print("Complete.")
pool = Pool(processes=2)
pool.map(send, range(0, num))
print("Mail have just sent. The program is going to end.")
connection.close()
And I've got an error like this.
File "c:\python36-32\Lib\multiprocessing\connection.py", line 206, in send
self._send_bytes(_ForkingPickler.dumps(obj))
File "c:\python36-32\Lib\multiprocessing\reduction.py", line 51, in dumps
cls(buf, protocol).dump(obj)
AttributeError: Can't pickle local object 'Command.handle.<locals>.f'
How to fix this?
Thank you.
You must take the local function send out of handle (and the Command class) and put it at the top level of your module, according to the reference.
For closured variables like subject, body, from_email, etc., you can bundle them in a tuple and pass the tuple as a single argument to send. Or better, create msg objects outside send, and pass the msg list to pool.map():
msg_list = [EmailMessage(subject, body, from_email, [recipients[i]])
for i in range(0, num)]
for msg in msg_list
msg.content_subtype = 'html'
pool = Pool(processes=2)
pool.map(send, msg_list)
in which the module-level send function is defined as follows:
def send(msg):
print("Sending email to: {}".format(msg.recipients()[0]))
connection.send_messages([msg])
print("Complete.")

In a python try/except how can i pass data back to the calling script

I have a python script that gets executed from a php script.
This script sends and email and it is several orders of magnitude faster than php with swiftmailer, but i have a problem. I don't really understand much about python or charachter encoding, and sometimes an error is generated about the encoding from the MIMEText library of python. I googled and found a solution, but i still want to have the php script as a fallback in case of failure in the python script.
The problem is, in the except loop it seems i am unable to pass the data back to php before the script terminates? any ideas on what i'm doing wrong here? You can see in the except block that i say result = {type: 'failed'} then attempt to json encode it but the data doesn't get back to the php script.
import sys, json, os, smtplib, socket, logging
LOG_FILE='/tmp/mandrill.out'
logging.basicConfig(filename=LOG_FILE,level=logging.DEBUG)
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.header import Header
from email.utils import formataddr
try:
data = json.loads(sys.argv[1])
msg = MIMEMultipart('alternative')
msg['Subject'] = data['subject']
msg['From'] = formataddr((str(Header(u'Business Name', 'utf-8')), data['from']))
msg['To'] = data['to']
html = data['body']
email = MIMEText(html, 'html','utf-8')
username = 'username'
password = 'password'
msg.attach(email)
s = smtplib.SMTP('smtp.example.com', 587, socket.getfqdn(), 3)
s.login(username, password)
s.sendmail(msg['From'], msg['To'], msg.as_string())
s.quit()
result = {'type': 'success'}
print json.dumps(result)
except:
result = {'type': 'error'}
logging.exception('Got exception on main handler')
raise
return json.dumps(result)
EDIT TO INCLUDE TRACEBACK
ERROR:root:Got exception on main handler
Traceback (most recent call last):
File "/usr/share/nginx/mandrill/mandrill.py", line 16, in <module>
email = MIMEText(html, 'html')
File "/usr/lib/python2.7/email/mime/text.py", line 30, in __init__
self.set_payload(_text, _charset)
File "/usr/lib/python2.7/email/message.py", line 226, in set_payload
self.set_charset(charset)
File "/usr/lib/python2.7/email/message.py", line 262, in set_charset
self._payload = self._payload.encode(charset.output_charset)
UnicodeEncodeError: 'ascii' codec can't encode character u'\u2019' in position 9580: ordinal not in range(128)
You can't use return to return data to the PHP calling script, you'll need to print it as you are doing in the successful case.
Also, calling raise will propagate the exception up to the main part of your script, thus preventing execution of the return json.dumps(result) (which should be a print). You don't need to raise the exception again (your script will terminate anyway).
You might also (or instead) want to set a return code from the python script via sys.exit(), then your PHP script can check the return code to detect errors. The convention is to set a return code of 0 for success and anything else for failure.
Also, you are using a bare except clause which will catch all exceptions - what it the actual exception that you are seeing? Can you update your question with the full traceback?
The raise causes the Python script to terminate, so it never reaches the return.
The next problem is that return can only be used inside Python to return a value to the calling function. You probably want to print the JSON string instead (but this somewhat depends on how you are calling Python from PHP, which you have not documented here).
The traceback indicates that
email = MIMEText(html, 'html','utf-8')
is not actually true; you are lacking the 'utf-8' parameter in the code which caused the error traceback.
You are killing the program with sys.exit(1).
If you want to return data back to something, I would exchange the sys.exit(1) line with
return json.dumps(result)

Python 3.0 smtplib

I have a very simple piece of code that I used in previous versions of Python without issues (version 2.5 and prior). Now with 3.0, the following code give the error on the login line "argument 1 must be string or buffer, not str".
import smtplib
smtpserver = 'mail.somedomain.com'
AUTHREQUIRED = 1 # if you need to use SMTP AUTH set to 1
smtpuser = 'admin#somedomain.com' # for SMTP AUTH, set SMTP username here
smtppass = 'somepassword' # for SMTP AUTH, set SMTP password here
msg = "Some message to send"
RECIPIENTS = ['admin#somedomain.com']
SENDER = 'someone#someotherdomain.net'
session = smtplib.SMTP(smtpserver)
if AUTHREQUIRED:
session.login(smtpuser, smtppass)
smtpresult = session.sendmail(SENDER, RECIPIENTS, msg)
Google shows there are some issues with that error not being clear, but I still can't figure out what I need to try to make it work. Suggestions included defining the username as b"username", but that doesn't seem to work either.
UPDATE: just noticed from a look at the bug tracker there's a suggested fix also:
Edit smtplib.py and replace the existing encode_plain() definition with this:
def encode_plain(user, password):
s = "\0%s\0%s" % (user, password)
return encode_base64(s.encode('ascii'), eol='')
Tested here on my installation and it works properly.
Traceback (most recent call last):
File "smtptest.py", line 18, in <module>
session.login(smtpuser, smtppass)
File "c:\Python30\lib\smtplib.py", line 580, in login
AUTH_PLAIN + " " + encode_plain(user, password))
File "c:\Python30\lib\smtplib.py", line 545, in encode_plain
return encode_base64("\0%s\0%s" % (user, password))
File "c:\Python30\lib\email\base64mime.py", line 96, in body_encode
enc = b2a_base64(s[i:i + max_unencoded]).decode("ascii")
TypeError: b2a_base64() argument 1 must be bytes or buffer, not str
Your code is correct. This is a bug in smtplib or in the base64mime.py.
You can track the issue here:
http://bugs.python.org/issue5259
Hopefully the devs will post a patch soon.
As a variation on Jay's answer, rather than edit smtplib.py you could "monkey patch" it at run time.
Put this somewhere in your code:
def encode_plain(user, password):
s = "\0%s\0%s" % (user, password)
return encode_base64(s.encode('ascii'), eol='')
import smtplib
encode_plain.func_globals = vars(smtplib)
smtplib.encode_plain = encode_plain
This is kind of ugly but useful if you want to deploy your code onto other systems without making changes to their python libraries.
This issue has been addressed in Python3.1. Get the update at http://www.python.org/download/releases/3.1/

Categories

Resources