python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > Python smtplib邮件自动发送

基于Python+smtplib实现邮件自动发送功能

作者:忆愿

工作中总有各种邮件需要定期发送,手动操作不仅繁琐还容易忘记,Python的smtplib库完美解决这个痛点,几行代码就能搞定邮件自动发送,还能加上附件、HTML格式美化、定时任务等花样玩法,所以本文介绍了基于Python+smtplib实现邮件自动发送功能,需要的朋友可以参考下

引言

工作中总有各种邮件需要定期发送,手动操作不仅繁琐还容易忘记。Python的smtplib库完美解决这个痛点,几行代码就能搞定邮件自动发送,还能加上附件、HTML格式美化、定时任务等花样玩法。我用这招把每天半小时的邮件工作缩减到几分钟,工作效率直接起飞。

smtplib是啥玩意儿

smtplib是Python自带的一个库,专门用来发送邮件的。SMTP(Simple Mail Transfer Protocol)就是简单邮件传输协议,是互联网上用来发送邮件的标准。这个库把复杂的协议细节都封装好了,咱们只需要调用几个简单的方法就能发邮件,不用关心底层的通信细节。

    import smtplib
    from email.mime.text import MIMEText
    from email.header import Header
    
    # 基本用法示例
    sender = 'your_email@example.com'
    receivers = ['receiver@example.com']
    
    # 创建一个简单的邮件
    message = MIMEText('这是测试邮件,别当真~', 'plain', 'utf-8')
    message['From'] = Header("Python自动发送", 'utf-8')
    message['To'] = Header("测试接收人", 'utf-8')
    message['Subject'] = Header('Python邮件测试', 'utf-8')
    
    try:
        smtp_obj = smtplib.SMTP('smtp.example.com')  # 邮件服务器地址
        smtp_obj.login('username', 'password')       # 登录邮箱
        smtp_obj.sendmail(sender, receivers, message.as_string())
        print("邮件发送成功")
    except smtplib.SMTPException:
        print("Error: 无法发送邮件")

看着挺多,实际就是创建连接、写点内容、登录、发送这几步。

温馨提示 :这个代码敲完不一定能直接跑,因为现在各大邮箱为了安全都开启了授权码登录,你得到邮箱设置里去申请一个授权码,用这个码来替代密码登录。

连接邮箱服务器,这事有点讲究

连邮箱服务器有两种方式:普通连接和SSL加密连接。现在基本都用第二种,安全嘛。

普通连接:

    smtp_obj = smtplib.SMTP('smtp.example.com', 25)  # 25是SMTP协议的默认端口

SSL加密连接:

    smtp_obj = smtplib.SMTP_SSL('smtp.example.com', 465)  # 465是SMTP over SSL的默认端口

几个常见邮箱的SMTP服务器地址:

我在上班的时候,天天用企业邮箱自动化处理各种通知,贼方便。

登录邮箱也很简单:

    smtp_obj.login('your_email@example.com', '你的授权码')

这里有个大坑,不是用你平时登录的密码,是要到邮箱设置里申请的专门用于第三方登录的授权码。每个邮箱申请方式不太一样:

不同邮箱叫法可能不同,像什么"安全码"、“应用专用密码”、“授权码”,反正不是你平时登录的那个密码就对了。

也有人问为啥现在邮箱这么麻烦,整这种授权码。别抱怨,这是为了安全。想想看,你平时登录密码要是泄露了,一切都完蛋。但授权码只能用于邮件收发,黑客就算拿到了也不能改你密码、不能删你邮件,安全多了。

发送纯文本邮件,小菜一碟

最基础的就是发纯文本邮件,真的超简单:

    from email.mime.text import MIMEText
    from email.header import Header
    
    # 创建纯文本邮件
    msg = MIMEText('这是邮件内容,想写啥写啥', 'plain', 'utf-8')
    msg['From'] = Header('Python自动发送', 'utf-8')
    msg['To'] = Header('老板', 'utf-8')
    msg['Subject'] = Header('每日工作汇报', 'utf-8')
    
    # 发送邮件
    smtp_obj.sendmail(sender, receivers, msg.as_string())

MIMEText 第一个参数是邮件内容,第二个参数指定内容类型(plain表示纯文本),第三个参数指定编码。

温馨提示 :收件人可以是一个列表,一次发给多个人:

    receivers = ['boss@example.com', 'colleague@example.com']

顺带提一句,如果你要给很多人发邮件,但又不想让他们看到彼此的邮箱地址(避免泄露信息),可以用密送(BCC):

    msg['Cc'] = Header('同事A,同事B', 'utf-8')  # 抄送
    msg['Bcc'] = Header('同事C,同事D', 'utf-8')  # 密送

密送的人会收到邮件,但其他收件人不会知道有人被密送。职场暗斗必备技能,老板想悄悄让你的同事也看看你的工作汇报,哈哈。

发HTML格式邮件,逼 格瞬间提升

纯文本太单调了,HTML格式可以做出花哨的样式,还能放图片、表格啥的:

    html_content = """
    <html>
    <body>
        <h1 style="color:red">项目进度报告</h1>
        <p>各位好:</p>
        <p>本周项目进展如下:</p>
        <ul>
            <li>需求分析:<span style="color:green">已完成</span></li>
            <li>系统设计:<span style="color:green">已完成</span></li>
            <li>编码:<span style="color:orange">进行中 (75%)</span></li>
            <li>测试:<span style="color:red">未开始</span></li>
        </ul>
        <p>详情请见<a href="http://example.com/report" rel="external nofollow" >完整报告</a></p>
    </body>
    </html>
    """
    
    # 创建HTML邮件
    message = MIMEText(html_content, 'html', 'utf-8')
    message['From'] = Header('项目负责人', 'utf-8')
    message['To'] = Header('项目组', 'utf-8')
    message['Subject'] = Header('每周项目进度', 'utf-8')

就是把 MIMEText 的第二个参数从 ‘plain’ 改成 ‘html’ 就行了,内容写HTML代码。这个技能真是救命,以前我每周都要手动排版项目周报,用了自动发送后,模板一套,数据一填,一键发送,省了大把时间。

需要注意的是,不是所有邮件客户端都支持所有HTML特性,尤其是那些新潮的CSS3属性。我有个同事用了渐变背景和弹性布局,结果老板的古董Outlook全乱了。建议保守点用HTML 4和基础CSS,兼容性最好。

要是想放图片,直接插入外部图片链接即可:

    <img src="https://example.com/logo.png" alt="公司Logo" />

但这要求收件人能访问这个图片链接,而且会暴露IP。更专业的做法是用 内嵌图片 ,下面我专门讲讲。

内嵌图片,让邮件更专业

邮件里直接嵌入图片,不依赖外部链接,看起来更专业,也不会被拦截:

    from email.mime.multipart import MIMEMultipart
    from email.mime.text import MIMEText
    from email.mime.image import MIMEImage
    
    # 创建复合邮件
    message = MIMEMultipart('related')
    message['From'] = Header('营销部门', 'utf-8')
    message['To'] = Header('客户', 'utf-8')
    message['Subject'] = Header('本月促销活动', 'utf-8')
    
    # HTML内容,引用内嵌图片
    html = """
    <html>
    <body>
        <h1>五月促销活动</h1>
        <p>尊敬的客户:</p>
        <p>查看我们的最新产品:</p>
        <img src="cid:image1" width="500">
        <p>限时八折优惠,欢迎选购!</p>
    </body>
    </html>
    """
    message.attach(MIMEText(html, 'html', 'utf-8'))
    
    # 添加图片
    with open('product.jpg', 'rb') as f:
        img = MIMEImage(f.read())
        img.add_header('Content-ID', '<image1>')  # 与HTML中的cid对应
        message.attach(img)

注意看,图片引用是通过 cid:image1 这种特殊语法实现的。Content-ID 标签必须用尖括号包住,而HTML中引用时不用尖括号,这个细节很容易出错。

添加附件,才是邮件发送的完全体

工作中发邮件哪能只发文字啊,各种文档、Excel表格、图片啥的都得带上。添加附件也不复杂,就是多写几行代码:

    from email.mime.multipart import MIMEMultipart
    from email.mime.text import MIMEText
    from email.mime.application import MIMEApplication
    import os
    
    # 创建带附件的邮件
    message = MIMEMultipart()
    message['From'] = Header('数据分析小组', 'utf-8')
    message['To'] = Header('产品经理', 'utf-8')
    message['Subject'] = Header('用户数据分析报告', 'utf-8')
    
    # 添加邮件正文
    message.attach(MIMEText('附件是本月用户增长数据,请查收。', 'plain', 'utf-8'))
    
    # 添加Excel附件
    excel_path = 'monthly_report.xlsx'
    with open(excel_path, 'rb') as f:
        excel_attachment = MIMEApplication(f.read())
        excel_attachment.add_header('Content-Disposition', 'attachment', filename=os.path.basename(excel_path))
        message.attach(excel_attachment)
    
    # 添加PDF附件
    pdf_path = 'analysis_report.pdf'
    with open(pdf_path, 'rb') as f:
        pdf_attachment = MIMEApplication(f.read())
        pdf_attachment.add_header('Content-Disposition', 'attachment', filename=os.path.basename(pdf_path))
        message.attach(pdf_attachment)

关键区别在于用了 MIMEMultipart 类,它允许组合多个部分成一个完整邮件。PDF、Excel、Word、图片都能添加,我的周报每次自动带上三个Excel和一份PPT,完全不用手动操作。

温馨提示 :如果附件名含中文,可能会乱码,需要特殊处理:

    from email.header import Header
    
    filename = 'sales_report_中文名.xlsx'
    excel_attachment.add_header('Content-Disposition', 'attachment', 
                               filename=('utf-8', '', filename))

附件大小也要注意,大多数邮箱对附件有大小限制:

超出限制邮件发不出去,要么压缩附件,要么用云盘分享链接。

批量发送个性化邮件,成为邮件届的"网红"

有时候需要给不同人发不同内容的邮件,比如给客户发通知,每个客户的称呼、订单信息都不同。这时就需要批量发送个性化邮件:

    import pandas as pd
    
    # 假设有个客户信息表格
    df = pd.read_excel('customers.xlsx')
    
    for index, row in df.iterrows():
        # 创建邮件
        msg = MIMEText(f"""尊敬的{row['name']}:
        
    感谢您购买我们的产品。您的订单号 {row['order_id']} 已发货,
    预计{row['estimated_delivery']}送达。
    如有问题,请联系我们的客服电话:400-123-4567。
    
    祝好,
    销售团队
        """, 'plain', 'utf-8')
        
        msg['From'] = Header('客户服务部', 'utf-8')
        msg['To'] = Header(row['name'], 'utf-8')
        msg['Subject'] = Header('您的订单已发货', 'utf-8')
        
        # 发送邮件
        smtp_obj.sendmail('service@example.com', row['email'], msg.as_string())
        print(f"已向 {row['name']} 发送邮件")
        
        # 适当延时,避免邮件服务器认为是垃圾邮件
        import time
        time.sleep(1)

这段代码会读取Excel表格中的客户信息,然后给每个客户发送包含其个人信息的邮件。我以前每天手动发的周报总结,现在全自动化了,而且还能根据不同部门定制不同内容,同事们都夸我效率高。

温馨提示 :批量发送时要控制发送频率,太快会被邮件服务器当成垃圾邮件行为,可能导致账号被封。最好每封邮件间隔1-5秒。

有个前同事因为这事吃了大亏,他一下发了5000封邮件,结果公司邮箱被服务商封了,整个公司几百人两天收不到外部邮件,差点被老板炒鱿鱼。服务器有发信额度,通常是每小时几百封,具体看你的邮箱服务协议。

定时自动发送,解放你的双手

手动运行脚本太low了,设置定时任务才是真正的自动化。Python有个schedule库,专门用来设置定时任务:

    import schedule
    import time
    
    def send_daily_report():
        # 这里放发送邮件的代码
        print("发送每日报告")
    
    def send_weekly_report():
        # 这里放发送周报的代码
        print("发送周报")
    
    # 每天上午9点发送日报
    schedule.every().day.at("09:00").do(send_daily_report)
    
    # 每周一下午5点发送周报
    schedule.every().monday.at("17:00").do(send_weekly_report)
    
    # 每月1号发送月报
    schedule.every().month_start.at("10:00").do(lambda: print("发送月报"))
    
    # 每隔2小时发送一次监控报告
    schedule.every(2).hours.do(lambda: print("发送监控报告"))
    
    while True:
        schedule.run_pending()
        time.sleep(60)  # 每分钟检查一次是否有任务需要执行

不過这种方法需要保持程序一直运行,更专业的做法是用操作系统的定时任务:

Linux上设置定时任务超简单,编辑crontab文件:

    crontab -e

加入以下内容设置定时任务:

    # 每天早上9点执行
    0 9 * * * python /path/to/send_daily_email.py
    
    # 每周一下午5点执行
    0 17 * * 1 python /path/to/send_weekly_email.py

我搞过一个项目监控系统,服务器出问题就自动发邮件通知我,大半夜收到邮件爬起来处理问题,救过好几次火。

Linux的cron表达式有点难记,尤其是星期几那部分(0和7都表示周日,1-6表示周一到周六)。一个小技巧是用在线cron生成工具,比如crontab.guru,可视化编辑cron表达式,直观很多。

使用环境变量保存敏感信息,别把密码写在代码里

直接把邮箱密码写在代码里太危险了,万一代码上传到GitHub,密码就泄露了。更安全的做法是使用环境变量:

    import os
    from dotenv import load_dotenv
    
    # 加载环境变量
    load_dotenv()
    
    # 从环境变量获取敏感信息
    email_user = os.getenv('EMAIL_USER')
    email_password = os.getenv('EMAIL_PASSWORD')
    smtp_server = os.getenv('SMTP_SERVER')
    
    # 使用这些变量连接邮箱
    smtp_obj = smtplib.SMTP_SSL(smtp_server, 465)
    smtp_obj.login(email_user, email_password)

创建一个 .env 文件存储敏感信息:

    EMAIL_USER=your_email@example.com
    EMAIL_PASSWORD=your_password
    SMTP_SERVER=smtp.example.com

记得把 .env 文件添加到 .gitignore 中,这样就不会被上传到代码仓库了。

如果是生产环境,建议用专门的密钥管理服务,比如阿里云KMS、AWS Secrets Manager等。在工作时,我们都用RAM角色授权+KMS管理密钥,安全性高很多。

处理邮件发送失败,加上错误重试机制

网络不稳定、邮件服务器偶尔抽风是常态,必须处理异常并重试:

    def send_email_with_retry(sender, receivers, message, max_retries=3):
        retry_count = 0
        while retry_count < max_retries:
            try:
                smtp_obj = smtplib.SMTP_SSL('smtp.example.com', 465)
                smtp_obj.login('username', 'password')
                smtp_obj.sendmail(sender, receivers, message.as_string())
                smtp_obj.quit()
                print("邮件发送成功")
                return True  # 发送成功
            except Exception as e:
                retry_count += 1
                print(f"发送失败 (尝试 {retry_count}/{max_retries}): {str(e)}")
                time.sleep(5)  # 等待5秒后重试
        
        print("发送邮件失败,已达到最大重试次数")
        return False  # 发送失败

这个函数会尝试发送邮件,如果失败会最多重试3次,每次间隔5秒。实际生产环境中,我设置了重试+告警机制,发不出去会自动打电话给我。

各种异常情况都需要考虑:

不同错误要有不同处理策略。比如认证错误多半是账号密码问题,重试也没用;但连接中断可能是网络波动,重试就有可能成功。

记录邮件发送日志,做个有据可查的人

日志是查问题的关键,要养成记录日志的好习惯:

    import logging
    
    # 配置日志
    logging.basicConfig(
        filename='email_sender.log',
        level=logging.INFO,
        format='%(asctime)s - %(levelname)s - %(message)s'
    )
    
    try:
        # 发送邮件的代码
        smtp_obj.sendmail(sender, receivers, message.as_string())
        logging.info(f"邮件已成功发送给 {receivers}")
    except Exception as e:
        logging.error(f"发送邮件失败: {str(e)}")

这样每次发送的结果都会记录到日志文件中,以后查问题超方便。

温馨提示 :生产环境中建议使用更完善的日志方案,比如按日期切割日志文件、设置日志轮转等。

    import logging
    from logging.handlers import RotatingFileHandler
    
    # 配置按大小轮转的日志
    handler = RotatingFileHandler(
        'email_sender.log', 
        maxBytes=10485760,  # 10MB
        backupCount=5
    )
    logger = logging.getLogger()
    logger.setLevel(logging.INFO)
    logger.addHandler(handler)

这样日志文件达到10MB时会自动创建新文件,最多保留5个备份文件,不会吃光磁盘空间。

完整实战例子:每日销售数据自动邮件系统

最后来个完整实例,整合前面说的所有技巧:

    import smtplib
    import os
    import pandas as pd
    import schedule
    import time
    import logging
    from email.mime.multipart import MIMEMultipart
    from email.mime.text import MIMEText
    from email.mime.application import MIMEApplication
    from email.header import Header
    from dotenv import load_dotenv
    from datetime import datetime
    
    # 配置日志
    logging.basicConfig(
        filename='sales_report_sender.log',
        level=logging.INFO,
        format='%(asctime)s - %(levelname)s - %(message)s'
    )
    
    # 加载环境变量
    load_dotenv()
    EMAIL_USER = os.getenv('EMAIL_USER')
    EMAIL_PASSWORD = os.getenv('EMAIL_PASSWORD')
    SMTP_SERVER = os.getenv('SMTP_SERVER')
    
    def generate_sales_report():
        """生成销售报告,返回报告文件路径"""
        try:
            # 假设这里连接数据库,提取数据并生成报告
            # 实际项目中,这里会有数据库查询、数据处理、生成图表等逻辑
            
            # 示例:创建一个简单的销售数据Excel
            df = pd.DataFrame({
                '产品': ['产品A', '产品B', '产品C', '产品D'],
                '销量': [123, 456, 789, 321],
                '收入': [12300, 45600, 78900, 32100]
            })
            
            today = datetime.now().strftime('%Y%m%d')
            report_path = f'sales_report_{today}.xlsx'
            df.to_excel(report_path, index=False)
            
            return report_path
        except Exception as e:
            logging.error(f"生成报告失败: {str(e)}")
            return None
    
    def send_sales_report(max_retries=3):
        """发送销售报告邮件,带附件和HTML格式内容"""
        logging.info("开始准备发送销售报告")
        
        # 生成报告
        report_path = generate_sales_report()
        if not report_path:
            logging.error("无法生成报告,取消发送邮件")
            return False
        
        # 获取今天的日期
        today = datetime.now().strftime('%Y-%m-%d')
        yesterday = (datetime.now() - pd.Timedelta(days=1)).strftime('%Y-%m-%d')
        
        # 创建邮件
        message = MIMEMultipart()
        message['From'] = Header('销售数据分析系统', 'utf-8')
        message['To'] = Header('销售团队', 'utf-8')
        message['Subject'] = Header(f'{yesterday} 销售数据报告', 'utf-8')
        
        # HTML邮件内容
        html_content = f"""
        <html>
        <body>
            <h2 style="color:#003366;">销售数据日报 - {yesterday}</h2>
            <p>各位销售经理:</p>
            <p>附件是 <strong>{yesterday}</strong> 的销售数据报表,主要数据如下:</p>
            <table border="1" cellpadding="5" cellspacing="0" style="border-collapse:collapse">
                <tr style="background-color:#f2f2f2">
                    <th>指标</th>
                    <th>数值</th>
                    <th>与上周同比</th>
                </tr>
                <tr>
                    <td>总销售额</td>
                    <td style="text-align:right">¥169,900</td>
                    <td style="color:green">↑ 12.3%</td>
                </tr>
                <tr>
                    <td>订单数</td>
                    <td style="text-align:right">1,689</td>
                    <td style="color:green">↑ 8.7%</td>
                </tr>
                <tr>
                    <td>平均客单价</td>
                    <td style="text-align:right">¥100.59</td>
                    <td style="color:green">↑ 3.3%</td>
                </tr>
            </table>
            <p>详细数据请查看附件,如有问题请及时联系数据组。</p>
            <p style="color:#888888">此邮件由系统自动发送,请勿回复。</p>
        </body>
        </html>
        """
        
        # 添加HTML内容
        message.attach(MIMEText(html_content, 'html', 'utf-8'))
        
        # 添加Excel附件
        with open(report_path, 'rb') as f:
            attachment = MIMEApplication(f.read())
            attachment_name = os.path.basename(report_path)
            attachment.add_header('Content-Disposition', 'attachment', filename=attachment_name)
            message.attach(attachment)
        
        # 发送邮件,带重试机制
        receivers = ['sales_manager@example.com', 'director@example.com', 'ceo@example.com']
        
        retry_count = 0
        while retry_count < max_retries:
            try:
                smtp_obj = smtplib.SMTP_SSL(SMTP_SERVER, 465)
                smtp_obj.login(EMAIL_USER, EMAIL_PASSWORD)
                smtp_obj.sendmail(EMAIL_USER, receivers, message.as_string())
                smtp_obj.quit()
                
                logging.info(f"销售报告邮件成功发送给 {', '.join(receivers)}")
                
                # 删除临时生成的报表文件
                os.remove(report_path)
                return True
                
            except Exception as e:
                retry_count += 1
                logging.warning(f"发送失败 (尝试 {retry_count}/{max_retries}): {str(e)}")
                time.sleep(5)  # 等待5秒后重试
        
        logging.error(f"发送销售报告邮件失败,已达到最大重试次数")
        return False
    
    # 设置定时任务:每天早上8:30发送报告
    def scheduled_job():
        logging.info("执行定时任务:发送每日销售报告")
        send_sales_report()
    
    # 如果直接运行脚本,执行测试发送
    if __name__ == "__main__":
        # 设置定时任务
        schedule.every().day.at("08:30").do(scheduled_job)
        
        print("邮件发送系统已启动,将在每天 08:30 发送销售报告")
        print("按 Ctrl+C 停止程序")
        
        try:
            # 立即发送一次作为测试
            print("正在发送测试邮件...")
            result = send_sales_report()
            print("测试邮件发送结果:", "成功" if result else "失败")
            
            # 持续运行,等待定时任务
            while True:
                schedule.run_pending()
                time.sleep(60)
        except KeyboardInterrupt:
            print("程序已停止")

这个完整示例包含了自动生成报表、格式化HTML邮件内容、添加附件、错误重试、日志记录等功能,可以直接部署到服务器上定时运行。我相信每天手动发数据报表的同学看到这个得笑出声,太多痛点一次性解决了。

这种自动化邮件系统可以应用在很多场景:

根据实际需求稍作修改就能派上用场。对许多初级程序员来说,Python自动发邮件可能是接触自动化的第一步,掌握了这个技能,你就会开始思考:“还有什么工作是可以用程序自动完成的?”

这个思路才是最值钱的。

从这个小小的邮件自动化开始,渐渐把团队里各种重复性工作都自动化了:数据采集、报表生成、监控告警、异常检测。最后整个团队的效率提升了3倍多,我也因此拿到了晋升机会。写代码最大的乐趣之一,就是用技术解决实际问题,让自己和同事的工作更轻松。

以上就是基于Python+smtplib实现邮件自动发送功能的详细内容,更多关于Python smtplib邮件自动发送的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文