目录
一、阿里云企业邮箱群发邮件全流程实现
1. 准备工作与环境配置
2. 收件人列表管理
3. 邮件内容构建
4. 附件添加实现
5. 邮件发送核心逻辑
二、开发过程中遇到的问题与解决方案
1. 附件发送失败问题
2. 中文文件名乱码问题
3. 企业邮箱认证失败
三、完整工作流程总结
四、注意事项
五、最终代码展示
在现代办公场景中,批量处理邮件已成为许多企业的常规需求。Python凭借其丰富的库生态和简洁的语法,成为实现办公自动化的首选工具。日常办公中,很多企业都是通过内部邮箱进行通知,因此Python批量邮件群发功能十分重要。本文将详细介绍如何利用Python结合阿里云企业邮箱服务,实现带附件的批量邮件发送功能,并分享在实际开发过程中遇到的问题及解决方案。
一、阿里云企业邮箱群发邮件全流程实现
1. 准备工作与环境配置
首先需要确保已开通阿里云企业邮箱服务并获取正确的SMTP配置信息:
# 阿里云企业邮箱配置
SMTP_HOST = "smtp.qiye.aliyun.com"
SMTP_PORT = 465 # SSL加密端口
USERNAME = "yourname@yourcompany.com"
PASSWORD = "yourpassword" # 注意保护敏感信息
DISPLAY_NAME = "公司邮件系统" # 发件人显示名称
2. 收件人列表管理
使用pandas库从Excel表格中智能识别邮箱列,自动处理数据格式,此处我们默认把所有邮箱放到email.xlsx这个Excel文件中,表头用"email"或"邮箱"或"mail":
def get_recipients(excel_path):
"""从Excel获取收件人列表(自动识别邮箱列)"""
try:
df = pd.read_excel(excel_path, engine='openpyxl')
# 智能识别邮箱列(支持中英文列名)
email_col = next(
(col for col in df.columns
if any(kw in col.lower() for kw in ['mail', '邮箱', 'email'])),
None
)
# 数据清洗处理
recipients = (
df[email_col]
.astype(str)
.str.strip()
.str.lower()
.dropna()
.unique()
.tolist()
)
return recipients
except Exception as e:
print(f"❌ 读取收件人列表失败: {e}")
return []
3. 邮件内容构建
创建多部分邮件对象,支持纯文本内容和附件:
def build_email_message(receiver):
"""构建带附件的邮件消息体"""
msg = MIMEMultipart('alternative')
# 设置邮件头(符合RFC标准)
msg['From'] = formataddr([DISPLAY_NAME, USERNAME])
msg['Reply-to'] = USERNAME
msg['TO'] = receiver
msg['Message-id'] = email.utils.make_msgid()
msg['Date'] = email.utils.formatdate()
msg['Subject'] = Header(EMAIL_SUBJECT_TEXT, 'utf-8')
# 添加纯文本正文
textplain = MIMEText(EMAIL_CONTENT_TEXT, _subtype='plain', _charset='UTF-8')
msg.attach(textplain)
return msg
4. 附件添加实现
关键点在于正确设置附件的Content-Type和Content-Disposition头信息:
def add_attachments(msg, attachment_paths):
"""添加附件到邮件消息体"""
for attachment_path in attachment_paths:
try:
with open(attachment_path, "rb") as f:
filename = os.path.basename(attachment_path)
att = MIMEText(f.read(), 'base64', 'utf-8')
att["Content-Type"] = 'application/octet-stream'
att.add_header(
"Content-Disposition",
"attachment",
filename=("gbk", "", filename) # 解决中文文件名编码问题
)
msg.attach(att)
except Exception as e:
print(f"✗ 添加附件失败: {attachment_path} | 错误: {str(e)}")
5. 邮件发送核心逻辑
支持SSL和非SSL两种连接方式,增强兼容性:
def send_email(receiver, msg):
"""通过阿里云企业邮箱发送邮件"""
try:
# 尝试SSL连接
try:
client = smtplib.SMTP_SSL(SMTP_HOST, SMTP_PORT)
except:
# 回退到普通连接
client = smtplib.SMTP(SMTP_HOST, 25, timeout=5)
client.login(USERNAME, PASSWORD)
client.sendmail(USERNAME, [receiver], msg.as_string())
client.quit()
return True
except Exception as e:
print(f"✗ 发送错误: {receiver} | 错误: {str(e)}")
return False
二、开发过程中遇到的问题与解决方案
1. 附件发送失败问题
最初使用DeepSeek生成的代码发送附件时,虽然邮件能正常发出,但收件方无法看到附件。经过排查发现:
问题原因:附件内容类型(Content-Type)设置不正确,且未正确处理文件名编码。
解决方案:参照阿里云官方Demo,明确设置:
att["Content-Type"] = 'application/octet-stream'
att.add_header("Content-Disposition", "attachment", filename=("gbk", "", filename))
而且文件不是以二进制方式打开,而是以"base64"方式读取。所以修改上传附件功能核心代码:
with open(attachment_path, "rb") as f:
filename = os.path.basename(attachment_path)
att = MIMEText(f.read(), 'base64', 'utf-8')
att["Content-Type"] = 'application/octet-stream'
att.add_header("Content-Disposition", "attachment", filename=filename)
msg.attach(att)
2. 中文文件名乱码问题
问题表现:附件名中的中文显示为乱码
解决方案:使用MIME编码处理文件名:
from email.header import Header
filename = Header(os.path.basename(attachment_path), 'utf-8').encode()
3. 企业邮箱认证失败
问题表现:频繁出现认证失败
解决方案:
确认使用的是SMTP专用密码(非邮箱登录密码)
尝试切换SSL和非SSL端口
检查阿里云控制台是否开启了SMTP服务
三、完整工作流程总结
- 准备阶段:配置SMTP参数,准备邮件内容和附件
- 数据读取:从Excel智能识别并提取收件人邮箱,并存为列表
- 邮件构建:创建MIMEMultipart对象;添加文本内容;添加附件(处理编码问题,注意阅读方式)
- 发送测试:先向自己发送测试邮件
- 批量发送:遍历收件人列表发送邮件
- 结果统计:记录成功/失败数量
四、注意事项
发送频率控制:
阿里云企业邮箱有发送频率限制,建议在循环中添加延时:
time.sleep(5) # 5秒间隔
错误重试机制:对发送失败的邮件实现自动重试:
max_retries = 2
for attempt in range(max_retries):
if send_email_with_attachment(to_email):
break
日志记录:建议将发送结果记录到日志文件,便于后续跟踪:
with open("send_log.txt", "a") as log:
log.write(f"{time.ctime()},{to_email},{'成功' if result else '失败'}\n")
通过本文介绍的方法,企业可以高效地利用Python和阿里云企业邮箱服务实现批量邮件发送,大幅提升办公效率。
在群发邮件时,一定要正确处理邮件格式、附件编码以及错误处理机制,这些细节决定了程序的稳定性和可靠性。
五、最终代码展示
使用本代码时,所有邮件地址放到email.xlsx里,新建附件文件夹,所有附件都放在其中。
代码如下:
# -*- coding:utf-8 -*-
import smtplib
import email.utils # 修改这里,直接导入email.utils
import pandas as pd
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.utils import formataddr
from email.header import Header
import email.utils
import traceback, os, time
from glob import glob
# 阿里云企业邮箱配置
SMTP_HOST = "smtp.qiye.aliyun.com"
SMTP_PORT = 465
USERNAME = "Your EMAIL"
PASSWORD = "YOUR PASSWORD"
DISPLAY_NAME = "公司邮件系统"
# 邮件内容配置(重命名变量以避免冲突)
EMAIL_SUBJECT_TEXT = "重要通知:系统升级维护"
EMAIL_CONTENT_TEXT = """
尊敬的同事:
根据公司IT规划,我们将于2023年12月15日(周五)20:00至24:00进行系统升级维护。
升级期间所有业务系统将暂停访问,请提前做好工作安排。
如有疑问请联系IT支持部门:
电话:400-123-4567
邮箱:support@yourcompany.com
IT管理中心
"""
# 修改附件路径为绝对路径(示例)
ATTACHMENTS = [os.path.abspath(file) for file in glob(os.path.join("附件", "*"))
if os.path.isfile(file)]
def get_recipients(excel_path):
"""从Excel获取收件人列表(自动识别邮箱列)"""
try:
if not os.path.isfile(excel_path):
raise FileNotFoundError(f"文件不存在: {excel_path}")
df = pd.read_excel(excel_path, engine='openpyxl')
email_col = next(
(col for col in df.columns
if any(kw in col.lower() for kw in ['mail', '邮箱', 'email'])),
None
)
if not email_col:
available_columns = ", ".join(df.columns)
raise ValueError(f"未找到邮箱列,可用列有: {available_columns}")
recipients = (
df[email_col]
.astype(str)
.str.strip()
.str.lower()
.dropna()
.unique()
.tolist()
)
print(f"从Excel读取到 {len(recipients)} 个有效邮箱地址")
return recipients
except Exception as e:
print(f"❌ 读取收件人列表失败: {e}")
return []
def send_email_with_attachment(receiver):
"""发送带附件的邮件(阿里云企业邮箱专用)"""
try:
# 创建多部分邮件对象
msg = MIMEMultipart('alternative')
# 设置邮件头(符合阿里云demo规范)
msg['From'] = formataddr([DISPLAY_NAME, USERNAME])
msg['Reply-to'] = USERNAME
msg['TO'] = receiver
msg['Message-id'] = email.utils.make_msgid() # 现在可以正确调用了
msg['Date'] = email.utils.formatdate() # 现在可以正确调用了
msg['Subject'] = EMAIL_SUBJECT_TEXT
# 添加邮件正文
textplain = MIMEText(EMAIL_CONTENT_TEXT, _subtype='plain', _charset='UTF-8')
msg.attach(textplain)
# 添加附件(按照阿里云demo方式)
for attachment_path in ATTACHMENTS:
try:
if not os.path.isfile(attachment_path):
print(f"⚠ 附件不存在: {attachment_path}")
continue
with open(attachment_path, "rb") as f:
filename = os.path.basename(attachment_path)
att = MIMEText(f.read(), 'base64', 'utf-8')
att["Content-Type"] = 'application/octet-stream'
att.add_header("Content-Disposition", "attachment", filename=filename)
msg.attach(att)
print(f"✓ 已添加附件: {filename}")
except Exception as e:
print(f"✗ 添加附件失败: {attachment_path} | 错误: {str(e)}")
traceback.print_exc()
# 建立连接并发送(按照阿里云demo方式)
try:
client = smtplib.SMTP_SSL(SMTP_HOST, SMTP_PORT)
print('smtp_ssl----连接服务器成功,现在开始检查账号密码')
except Exception as e1:
client = smtplib.SMTP(SMTP_HOST, 25, timeout=5)
print('smtp----连接服务器成功,现在开始检查账号密码')
except Exception as e2:
print('抱歉,连接服务超时')
return False
try:
client.login(USERNAME, PASSWORD)
print('账密验证成功')
except:
print('抱歉,账密验证失败')
return False
client.sendmail(USERNAME, [receiver], msg.as_string())
client.quit()
print(f"✓ 已发送至: {receiver}")
return True
except Exception as e:
print(f"✗ 发送错误: {receiver} | 错误: {str(e)}")
traceback.print_exc()
return False
if __name__ == "__main__":
print("\n=== 阿里云企业邮箱批量发送系统 ===")
print(f"发件人: {DISPLAY_NAME} <{USERNAME}>")
# 步骤1:获取收件人
recipients = get_recipients("email.xlsx")
if not recipients:
print("❌ 程序终止:没有有效的收件人地址")
else:
# 步骤2:测试发送(给自己发一封)
print("\n正在发送测试邮件...")
if not send_email_with_attachment(USERNAME):
print("❌ 测试邮件发送失败,请检查配置")
else:
# 步骤3:批量发送
print("\n开始批量发送...")
success_count = 0
for to_email in recipients:
if to_email != USERNAME.lower(): # 避免重复发送
if send_email_with_attachment(to_email):
success_count += 1
# 发送结果统计
print(f"\n发送完成: 成功 {success_count}/{len(recipients)}")
if success_count < len(recipients):
print("⚠ 注意:部分邮件发送失败,请查看上方日志")
print("\n=== 程序执行结束 ===")
代码运行展示:
参考内容:
如何使用Python3.6及以上版本投递SMTP邮件_阿里邮箱(Alibaba Mail)-阿里云帮助中心
https://zhuanlan.zhihu.com/p/1894738611020214432