首先安装环境
设置非交互模式,使用默认配置
sudo DEBIAN_FRONTEND=noninteractive apt-get update
安装postfix(自动选择Local only配置)
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y postfix
安装mailutils(会复用postfix配置)
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y mailutils
创建email-reminder文件夹 在这个文件夹里创建4个文件,分别是email_config.sh, run_task.sh, send_email.py, test_email_simple.sh
其中email_config.sh内容如下:
export QQ_EMAIL="XX" # XX修改为你的QQ邮箱
export QQ_PASSWORD="YY" # YY修改为你的授权码
export RECIPIENT="XX" # 收件人邮箱,通常和发件邮箱相同,只是起到提醒作用
run_task.sh内容如下:
# 加载邮箱配置
source email-reminder/email_config.sh
# 检查命令行参数
if [ $# -ne 1 ]; then
echo "用法: $0 <任务脚本路径>"
echo "示例: $0 /zengke/test2.sh"
exit 1
fi
# 获取任务脚本路径
TASK_SCRIPT=$1
# 检查脚本是否存在
if [ ! -f "$TASK_SCRIPT" ]; then
echo "错误: 脚本 '$TASK_SCRIPT' 不存在"
exit 1
fi
# 自动赋予脚本可执行权限
echo "正在为脚本 '$TASK_SCRIPT' 添加可执行权限..."
chmod +x "$TASK_SCRIPT"
if [ $? -ne 0 ]; then
echo "警告: 无法为脚本 '$TASK_SCRIPT' 添加可执行权限,但将尝试继续执行"
fi
# 设置时区为北京时间
export TZ='Asia/Shanghai'
# 注意:时间将在后续步骤中额外增加8小时以确保显示正确
# 记录开始时间戳
start_timestamp=$(date +%s)
# 获取开始时间并加上8小时
start_time=$(date -d "+8 hours" '+%Y-%m-%d %H:%M:%S %Z')
# start_time=$(date '+%Y-%m-%d %H:%M:%S %Z')
task_name="$TASK_SCRIPT"
echo "=========================================="
echo "任务开始执行: $start_time"
echo "执行脚本: $task_name"
echo "=========================================="
# 执行指定的任务脚本
bash "$TASK_SCRIPT"
# 检查执行状态
if [ $? -eq 0 ]; then
status="成功"
else
status="失败"
fi
# 记录结束时间戳
end_timestamp=$(date +%s)
# 获取结束时间并加上8小时
end_time=$(date -d "+8 hours" '+%Y-%m-%d %H:%M:%S %Z')
# end_time=$(date '+%Y-%m-%d %H:%M:%S %Z')
duration=$((end_timestamp - start_timestamp))
# 格式化耗时
hours=$((duration / 3600))
minutes=$(( (duration % 3600) / 60 ))
seconds=$((duration % 60))
duration_str=$(printf "%02d小时%02d分钟%02d秒" $hours $minutes $seconds)
# 构建邮件内容
subject="任务完成"
message="任务名称: $task_name
开始时间: $start_time
结束时间: $end_time
任务耗时: $duration_str
执行状态: $status"
echo ""
echo "=========================================="
echo "任务执行完成"
echo "状态: $status"
echo "开始时间: $start_time"
echo "结束时间: $end_time"
echo "任务耗时: $duration_str"
echo "=========================================="
echo ""
# 发送邮件
echo "📧 正在发送邮件通知..."
python3 /zengke/e-mail-remind/send_email.py "$subject" "$message" "$RECIPIENT" "$QQ_EMAIL" "$QQ_PASSWORD"
if [ $? -eq 0 ]; then
echo "✅ 邮件发送成功!"
else
echo "❌ 邮件发送失败"
fi
echo ""
echo "🎉 任务流程执行完毕"
send_email.py内容如下:
# -*- coding: utf-8 -*-
import smtplib
import sys
import os
import socket
import time
# # 导入时区相关模块,确保Python使用正确的时区
# import datetime
# import pytz
from email.mime.text import MIMEText
from email.header import Header
# 设置环境时区为上海,确保Python脚本使用正确的时区
os.environ['TZ'] = 'Asia/Shanghai'
# # 确保datetime使用正确的时区
# def get_beijing_time():
# return datetime.datetime.now(pytz.timezone('Asia/Shanghai'))
def send_qq_email(subject, message, recipient, sender_email, sender_password):
"""
使用QQ邮箱发送邮件
Args:
subject: 邮件主题
message: 邮件内容
recipient: 收件人邮箱
sender_email: 发件人QQ邮箱
sender_password: QQ邮箱授权码
"""
try:
# 打印诊断信息
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 开始发送邮件...")
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 诊断信息:")
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] - 发送方: {sender_email}")
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] - 接收方: {recipient}")
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] - SMTP服务器: smtp.qq.com:587")
# 尝试获取本地主机名和IP地址
try:
hostname = socket.gethostname()
local_ip = socket.gethostbyname(hostname)
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] - 本地主机名: {hostname}")
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] - 本地IP地址: {local_ip}")
except Exception as e:
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] - 获取本地网络信息失败: {e}")
# 创建邮件
msg = MIMEText(message, 'plain', 'utf-8')
msg['Subject'] = Header(subject, 'utf-8')
msg['From'] = sender_email
msg['To'] = recipient
# 连接QQ邮箱SMTP服务器(使用非SSL连接,稍后升级到TLS)
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 正在连接QQ邮箱SMTP服务器...")
# 尝试不同的连接策略
try:
# 方法1:不指定source_address,让系统自动选择
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 尝试连接方法1: 让系统自动选择本地地址")
server = smtplib.SMTP('smtp.qq.com', 587, timeout=30)
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 连接方法1成功建立初始连接")
except Exception as e1:
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 连接方法1失败: {e1}")
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 尝试连接方法2: 使用明确的本地地址")
# 方法2:使用特定的source_address
server = smtplib.SMTP('smtp.qq.com', 587, source_address=('', 0), timeout=30)
# 尝试设置socket选项(在某些Python版本中,SMTP对象可能没有直接的socket属性)
try:
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 尝试访问socket对象...")
# 不同Python版本中SMTP对象的socket属性名称可能不同
if hasattr(server, '_socket'):
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 使用_socket属性设置选项")
server._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sockname = server._socket.getsockname()
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 本地socket绑定地址: {sockname}")
elif hasattr(server, 'socket'):
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 使用socket属性设置选项")
server.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sockname = server.socket.getsockname()
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 本地socket绑定地址: {sockname}")
else:
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] SMTP对象没有直接的socket属性访问方式")
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 这通常不会影响邮件发送功能")
except Exception as e:
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] socket操作失败: {e}")
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 注意:socket配置错误通常不会阻止邮件发送,继续尝试...")
# 升级连接到TLS加密
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 升级连接到TLS加密...")
server.starttls()
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] TLS加密升级成功")
# 登录QQ邮箱
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 登录QQ邮箱...")
server.login(sender_email, sender_password)
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] QQ邮箱登录成功")
# 发送邮件
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 发送邮件...")
server.sendmail(sender_email, [recipient], msg.as_string())
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 邮件发送完成")
server.quit()
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] ✅ 邮件发送成功!")
return True
except smtplib.SMTPAuthenticationError:
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] ❌ 认证失败,请检查:")
print(" - QQ邮箱地址是否正确")
print(" - 授权码是否正确(不是QQ密码)")
print(" - 是否已开启SMTP服务")
return False
except socket.error as se:
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] ❌ 网络连接错误: {se}")
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 网络错误详情:")
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] - 错误类型: {type(se).__name__}")
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] - 错误编号: {se.errno}")
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] - 错误信息: {se.strerror}")
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 可能的解决方案:")
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 1. 检查网络连接和防火墙设置")
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 2. 确认SMTP服务器地址和端口正确")
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 3. 检查系统网络配置")
return False
except Exception as e:
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] ❌ 邮件发送失败: {e}")
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 错误详情:")
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] - 错误类型: {type(e).__name__}")
return False
def main():
if len(sys.argv) != 6:
print("用法: python3 send_email.py <主题> <内容> <收件人> <发件人> <授权码>")
print("示例: python3 send_email.py '测试' '内容' '123@qq.com' '123@qq.com' '授权码'")
sys.exit(1)
subject = sys.argv[1]
message = sys.argv[2]
recipient = sys.argv[3]
sender_email = sys.argv[4]
sender_password = sys.argv[5]
success = send_qq_email(subject, message, recipient, sender_email, sender_password)
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()
用法 bash run_task.sh XX.sh,其中XX.sh是你要运行的脚本
test_email_simple.sh的内容如下:
# 加载配置
source /email-reminder/email_config.sh
echo "=== QQ邮箱发送测试 ==="
echo "发件人: $QQ_EMAIL"
echo "收件人: $RECIPIENT"
echo "测试时间: $(date -d "+8 hours" '+%Y-%m-%d %H:%M:%S')"
# 测试邮件内容
subject="QQ邮箱配置测试"
message="这是一封QQ邮箱配置测试邮件。
如果收到此邮件,说明配置成功!"
# 测试时间: $(date -d "+8 hours" '+%Y-%m-%d %H:%M:%S')
# 服务器: $(hostname)"
# 使用独立的Python脚本发送邮件
python3 /email-reminder/send_email.py "$subject" "$message" "$RECIPIENT" "$QQ_EMAIL" "$QQ_PASSWORD"
可以先运行bash test_email_simple.sh,如果邮箱收到了测试邮件,说明已经配置成功。