SMTP邮件发送类

Posted on 2022-11-27 21:32  呱呱呱呱叽里呱啦  阅读(19)  评论(0编辑  收藏  举报

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()