SMTP邮件发送类
EmailSender
代码
import os
import smtplib
import chardet
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.image import MIMEImage
from email.mime.application import MIMEApplication
FUNC_DICT = {MIMEText: ['.txt', '.html', ], MIMEImage: ['.jpg', '.png', ], }
SSLPORT = 465
NONSSLPORT = 25
class EmailSender:
def __init__(self, sender_info, isssl=False, port_num=None):
sender_info = self.sender_info_validate(sender_info)
self.host = sender_info['host']
self.usr = sender_info['usr']
self.pwd = sender_info['pwd']
self.addr = sender_info['addr']
self.isssl = isssl
self.port_num = port_num
try:
if self.isssl:
self.mailbox = smtplib.SMTP_SSL(self.host, self.port_num or SSLPORT)
else:
self.mailbox = smtplib.SMTP()
self.mailbox.connect(self.host, self.port_num or NONSSLPORT)
self.mailbox.login(self.usr, self.pwd)
print('邮箱登录成功')
except Exception as LoginError:
raise Exception('LoginError', f'登录邮箱失败:{str(LoginError)}')
def sender_info_validate(self, sender_info):
for _attr in ['host', 'usr', 'pwd', ]:
if (_attr not in sender_info) or (not sender_info[_attr]):
raise Exception('SenderInfoValidateError', f'sender_info验证失败:{_attr}未找到或为空')
if ('addr' not in sender_info) or (not sender_info['addr']):
sender_info['addr'] = sender_info['usr']
if not ('@' in sender_info['addr'] and '.com' in sender_info['addr']):
raise Exception('SenderInfoValidateError', f'sender_info验证失败:{sender_info["addr"]}格式错误')
return sender_info
def quit_mailbox(self):
self.mailbox.quit()
print('邮箱已退出')
def send_email(self, subject, content, attachment, receivers):
try:
message = MIMEMultipart()
message['From'] = self.addr
message['To'] = receivers[0]
message['Subject'] = subject
msg_factors = [self.handle_content(content)]
msg_factors.extend(self.handle_attachment(attachment))
for mf in msg_factors:
message.attach(mf)
print('邮件构建完成')
self.mailbox.sendmail(self.addr, receivers, message.as_string())
print('邮件发送成功')
except Exception as SendError:
raise Exception('SendEmailFailed', f'发送邮件失败:{str(SendError)}')
def handle_content(self, content):
if os.path.isfile(content):
return self.handle_file(content, content.split('.')[-1], handle_type='content')
else:
return MIMEText(content, 'plain', 'utf-8')
def handle_attachment(self, attachment):
res = []
for i in attachment:
if not os.path.isfile(i):
raise Exception('AttachmentNotFound', f'{i}:没有找到该附件')
res.append(self.handle_file(i, i.split('.')[-1]))
return res
def get_handle_func(self, filename):
file_type = os.path.splitext(filename)[-1].lower()
for func in FUNC_DICT:
if file_type in FUNC_DICT[func]:
return func, os.path.basename(filename)
else:
return MIMEApplication, os.path.basename(filename)
def handle_file(self, filename, file_type, handle_type=None):
func_info = self.get_handle_func(filename)
is_text_file = bool(file_type in ['html', 'txt', ])
if is_text_file:
with open(filename, 'rt', encoding=self.get_charset(filename)) as f:
temp_file = f.read()
res = func_info[0](temp_file, 'html' if filename.endswith('.html') else 'plain',
'utf-8')
if not handle_type:
res['Content-Type'] = 'application/octet-stream'
res.add_header('Content-Disposition', 'attachment', filename=('gbk', '', func_info[1]))
return res
else:
with open(filename, 'rb') as f:
temp_file = f.read()
res = func_info[0](temp_file)
res['Content-Type'] = 'application/octet-stream'
res.add_header('Content-Disposition', 'attachment', filename=('gbk', '', func_info[1]))
return res
def get_charset(self, filename):
with open(filename, 'rb') as f:
cont = f.read()
return chardet.detect(cont)['encoding']
测试用例
# 测试用例
# 账号密码信息已屏蔽,注意自己改
s_i = {'host': 'smtp.163.com', 'usr': 'xxxxxxxx@163.com', 'pwd': '********',
'addr': 'xxx@163.com'}
x = EmailSender(s_i, port_num=465, isssl=True)
x.send_email('测试', r'xx.txt',
[r'xx.png', r'xx.pdf', r'xxx.txt',
r'xx.zip'],
['xxxxxxxx@outlook.com'])
x.quit_mailbox()