This is my first article on getting started
A recent project in my work has a requirement to automatically send some messages to the specified mailbox, so how to implement the automatic mail sending function in Python? Here’s a brief introduction.
Python SMTP to send mail
Simple Mail Transfer Protocol (SMTP) is a Simple Mail Transfer Protocol. The Smplib library in Python encapsulates SMTP and supports SMTP. You can send plain text messages, HTML files, and messages with attachments.
First, we build a SendEmailManager class, which also follows the idea of object-oriented programming. The general structure is as follows:
class SendEmailManager(object) :
def __init__(self, **kwargs) :
# Initialize parameters.def _get_conf(self, key) :
# Get the configuration parameters.def _init_conf(self) :
Initialize configuration parameters.def _login_email(self) :
# Log in to the email server.def _make_mail_msg(self) :
# Build text mail objects.def do_send_mail(self) :
# Send email.Copy the code
def __init__(self, **kwargs)
Class initialization function, which can be used to set object properties and give initial values, can be arguments or fixed values, where the argument **kwargs is a variable dictionary of keyword arguments passed to the function argument, Here we are mainly on the SMTP server (here use QQ mailbox), send mail proxy mailbox, set in the mailbox client authorization password, variable parameters for some initialization. The specific code is as follows:
# SMTP server, here use QQ mailbox, other mailbox baidu
EMAIL_HOST = 'smtp.qq.com'
# Send email proxy mailbox
EMAIL_HOST_USER = '[email protected]'
The client authentication password is set in the mailbox. Note that this is not the mailbox password
EMAIL_HOST_PASSWORD = 'xxxxxxxxxxxxx'
Copy the code
def __init__(self, **kwargs) :
# Initialize parameters
self.email_host = EMAIL_HOST
self.email_host_user = EMAIL_HOST_USER
self.email_host_pass = EMAIL_HOST_PASSWORD
self.kwargs = kwargs
Copy the code
def _get_conf(self, key)
The key is used to read the value of the mutable parameter self.kwargs dictionary for other functions to use.
def _get_conf(self, key) :
# Get the configuration parameters
value = self.kwargs.get(key)
ifkey ! ="attach_file_list" and (value is None or value == ' ') :raise Exception("configuration parameter '%s' cannot be empty" % key)
return value
Copy the code
def _init_conf(self)
This function is responsible for initializing the configuration parameters returned by _get_conf so that subsequent functions can call them.
def _init_conf(self) :
Initialize configuration parameters
print(self._get_conf('receives'))
self.receives = self._get_conf('receives')
self.msg_subject = self._get_conf('msg_subject')
self.msg_content = self._get_conf('msg_content')
self.msg_from = self._get_conf('msg_from')
# attachment
self.attach_file_list = self._get_conf('attach_file_list')
Copy the code
def _login_email(self)
Log in to the email server. What I log in here is the QQ email server, and the port number is 465. For other email ports, please use Baidu, and the code is as follows:
def _login_email(self) :
# Log in to the email server
try:
server = smtplib.SMTP_SSL(self.email_host, port=465)
# set_debuglevel(1) prints out all the information about the SMTP server interaction
server.set_debuglevel(1)
# Log in to email
server.login(self.email_host_user, self.email_host_pass)
return server
except Exception as e:
print("mail login exception:", e)
raise e
Copy the code
def _make_mail_msg(self)
This function builds a mail instance object that handles the contents of a message. A normal email usually contains the sender information, email subject, email body, and some emails are attached with attachments. Please refer to the following code for specific Settings:
def _make_mail_msg(self) :
# Build mail objects
msg = MIMEMultipart()
msg.attach(MIMEText(self.msg_content, 'plain'.'utf-8'))
# Email Subject
msg['Subject'] = Header(self.msg_subject, "utf-8")
# Sender email information
msg['From'] = "<%s>" % self.msg_from
# msg['From'] = Header(self.msg_from + "<%s>" % self.email_host_user, "utf-8")
msg['To'] = ",".join(self.receives)
print("-", self.attach_file_list)
if self.attach_file_list:
for i, att in enumerate(self.attach_file_list):
# construct attachments to transfer files in the current directory
if not att:
break
att_i = MIMEText(open(att, 'rb').read(), 'base64'.'utf-8')
att_i["Content-Type"] = 'application/octet-stream'
Filename = filename = filename
att_i["Content-Disposition"] = 'attachment; filename="%s"' % att
msg.attach(att_i)
return msg
Copy the code
def do_send_mail(self)
To send an email, you string up the above several functions and go directly to the code:
def do_send_mail(self) :
# Send email
try:
self._init_conf()
server = self._login_email()
msg = self._make_mail_msg()
server.sendmail(self.email_host_user, self.receives, msg.as_string())
server.close()
print("Sent successfully!")
except Exception as e:
print("Mail sending exception", e)
Copy the code
Configure parameters to test whether mails can be sent normally:
if __name__ == "__main__":
mail_conf = {
'msg_from': '****@foxmail.com'.# The email sender's address
'receives': ['****@qq.com',].This is a list, because there may be more than one recipient
'msg_subject': 'Python Mail test '.# The subject of the email
'msg_content': 'hello'.The content of the email
'attach_file_list': {"test.py": "test.py"."test.txt": "./test.txt"}, # is a list of attachment file paths
}
manager = SendEmailManager(**mail_conf)
manager.do_send_mail()
Copy the code
Ok, the email was sent successfully, and the attachment is ok. Now that we have implemented sending email in Python, how to automatically send email? Next, let’s talk about it.
Automatic email sending
1. Code hardcoding
If = “if”; if = “if”; if = “if”;
def delayed_sending(manager) :
The # parameter manager is a SendEmailManager object
begin_time = int(time.time())
while True:
end_time = int(time.time())
if end_time - begin_time == 1*60*60:
threading.Thread(target=manager.do_send_mail()).start()
begin_time = end_time
print("Sent...")
Copy the code
At the same time in order to prevent unknown reasons blocking the normal operation of the program, add multithreading to achieve asynchronous send mail, so that the program will be executed every hour to the specified mailbox to send an email, as long as the program does not stop the mail will not stop to send!!
Let’s say we need to send an email to a designated email address at 8 o ‘clock every day. Let’s do this:
def delayed_sending(manager) :
The # parameter manager is a SendEmailManager object
hour = 8
minute = 0
second = 0
while True:
now = datetime.datetime.now()
if now.hour == hour and now.minute == minute and now.second == second:
threading.Thread(target=manager.do_send_mail()).start()
print("Sent...")
Copy the code
Although the above two ways to achieve the function, but, the way to achieve is really low!
2. Realize the timing function through modules
In addition to hardcoding, we can do this using the Schedule module of the Python task timing runtime:
Run the schedule module using python tasks
def send_mail_by_schedule(manager) :
schedule.every(20).minutes.do(manager.do_send_mail) # Execute every 20 minutes
schedule.every().hour.do(manager.do_send_mail) # Execute every hour
schedule.every().day.at("In its").do(manager.do_send_mail) # Execute once a day at 10:00
schedule.every().monday.do(manager.do_send_mail) Execute once a week on Monday
schedule.every().friday.at("At").do(manager.do_send_mail) Execute once every Friday at 21:00
while True:
schedule.run_pending()
time.sleep(1)
Copy the code
In a while True loop, schedule.run_pending() is used to keep the schedule running, and to query the list of tasks. In a task, you can set different times to run. This is similar to crontab.
However, if multiple scheduled tasks are executed, they are actually executed one by one from the top down. If the above task is complex and time-consuming, it will affect the running time of the following task. If you run five tasks every two minutes and each task takes one minute, that’s a total of five minutes, so that when the next two minutes come, the previous round of tasks will still be running and the next round of tasks will start again. The solution is simple: use multithreading/multi-process.
def run_threaded(manager) :
threading.Thread(target=manager.do_send_mail()).start()
Run the schedule module using python tasks
def send_mail_by_schedule(manager) :
schedule.every(1).minutes.do(run_threaded, manager) # Execute every 20 minutes
schedule.every().hour.do(run_threaded, manager) # Execute every hour
schedule.every().day.at("In its").do(run_threaded, manager) # Execute once a day at 10:00
schedule.every().monday.do(run_threaded, manager) Execute once a week on Monday
schedule.every().friday.at("At").do(run_threaded, manager) Execute once every Friday at 21:00
while True:
schedule.run_pending()
time.sleep(1)
Copy the code
In this way, we can create a thread for each scheduled task and let the task run in the thread, thus achieving the effect of multiple tasks working in parallel. This way is not less time and effort than the first way, and does not require a lot of code at all, perfect!
conclusion
Tasks can also be executed automatically by using asynchronous task schedulers such as tasks like celery tasks that are not included in the tasks queue.
Once you’ve got the automated email out of the way, you can have the app send us the daily weather forecast, the daily news, chicken soup, etc. (or to your boyfriend or girlfriend) hahaha.
Finally, I would like to thank my girlfriend for her tolerance, understanding and support in work and life.