在日常的命令行操作中可能存在通过邮件通知的需求,但是不同平台自带的邮箱工具无法统一,可以基于 Python 脚本实现跨平台的统一操作。

脚本内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#!/usr/bin/env python3

import argparse
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders
from email.header import Header
import json
import sys
import os


def send_email(
subject,
body,
sender,
receiver,
host,
port,
username,
password,
attachment_paths=None,
):
message = MIMEMultipart()
message["From"] = sender
message["To"] = receiver
message["Subject"] = Header(subject, "utf-8") # type: ignore

message.attach(MIMEText(body, "plain", "utf-8"))

if attachment_paths:
for attachment_path in attachment_paths:
if os.path.isfile(attachment_path):
try:
with open(attachment_path, "rb") as attachment_file:
part = MIMEBase("application", "octet-stream")
part.set_payload(attachment_file.read())
encoders.encode_base64(part)
part.add_header(
"Content-Disposition",
f"attachment; filename={os.path.basename(attachment_path)}",
)
message.attach(part)
except Exception as e:
print(f"Error: Failed to attach file '{attachment_path}'. {e}")
sys.exit(1)

try:
smtpObj = smtplib.SMTP_SSL(host, port)
smtpObj.login(username, password)
smtpObj.sendmail(sender, receiver, message.as_string())
print("Email sent successfully")
except smtplib.SMTPException as e:
print(f"Error: Failed to send email. {e}")
finally:
smtpObj.quit() if "smtpObj" in locals() else None


def main():
parser = argparse.ArgumentParser(description="Send Email with attachments")
parser.add_argument("receiver", help="Email receiver")
parser.add_argument("-s", "--subject", required=True, help="Email subject")
parser.add_argument(
"-c",
"--config",
default="mail-config.json",
help="Configuration file path, default is mail-config.json",
)
parser.add_argument("-m", "--message", help="Email content")
parser.add_argument(
"-a",
"--attach",
action="append",
help="Attachment file path (can be used multiple times)",
)
args = parser.parse_args()

try:
with open(args.config, "r") as config_file:
config = json.load(config_file)
except FileNotFoundError:
print(f"Error: Configuration file '{args.config}' not found.")
sys.exit(1)
except json.JSONDecodeError:
print(f"Error: Configuration file '{args.config}' is not a valid JSON.")
sys.exit(1)

required_keys = ["username", "password", "sender", "host", "port"]
for key in required_keys:
if key not in config:
print(f"Error: Missing required key '{key}' in configuration file.")
sys.exit(1)

mail_user = config["username"]
mail_pass = config["password"]
sender = config["sender"]
host = config["host"]
port = config["port"]

receiver = args.receiver

if args.message:
body = args.message
else:
body = sys.stdin.read()

send_email(
args.subject,
body,
sender,
receiver,
host,
port,
mail_user,
mail_pass,
args.attach,
)


if __name__ == "__main__":
main()

"""
mail-config.json

{
"username": "xxxxxxxx@163.com",
"password": "xxxxxxxxxxxxxxxx",
"sender": "xxxxxxxx@163.com",
"host": "smtp.163.com",
"port": 465
}
"""

"""
demo

(1)
python3 sendmail.py -s "subject" receiver@example.com
<input content, enter+ctrl+d>

(2)
python3 sendmail.py -s "subject" receiver@example.com < content.txt

(3)
cat content.txt | python3 sendmail.py -s "subject" receiver@example.com

(4)
python3 sendmail.py -s "subject" -m "content" receiver@example.com

(5)
python3 sendmail.py -s "subject" -m "content" -a attachment.pdf receiver@example.com
"""

使用说明

基本用法

  1. 使用 -s--subject 指定邮件主题。
  2. 使用位置参数指定收件人邮箱。
  3. 脚本默认从 stdin 读取邮件正文,可以在命令行输入正文内容,并使用回车 + Ctrl+D 结束输入。(注意:单独 Ctrl+D 无法结束输入)

在使用上尽量模仿系统中的 mail 命令,暂不支持附件发送功能。

可选用法

  • 可以使用 -m--message 指定邮件正文。
  • 可以使用 -a--attach 指定一个附件的路径,对于多个附件需要多次使用 -a <file>
  • 通过 <| 管道输入文件内容作为邮件正文。

示例

通过命令行输入内容作为邮件正文:

1
2
python3 sendmail.py -s "subject" receiver@example.com
<在命令行中输入正文内容,输入结束按回车 + Ctrl+D 发送>

通过 < 传递文件内容作为邮件正文:

1
python3 sendmail.py -s "subject" receiver@example.com < content.txt

通过 | 管道传递文件内容作为邮件正文:

1
cat content.txt | python3 sendmail.py -s "subject" receiver@example.com

通过 -m 选项指定邮件正文:

1
python3 sendmail.py -s "subject" -m "content" receiver@example.com

通过 -a 选项指定邮件附件:

1
python3 sendmail.py -s "subject" -m "content" -a attachment.pdf receiver@example.com

配置文件

脚本默认从 mail-config.json 中读取配置信息,如下所示:

1
2
3
4
5
6
7
{
"username": "xxxxxxxx@163.com",
"password": "xxxxxxxxxxxxxxxx",
"sender": "xxxxxxxx@163.com",
"host": "smtp.163.com",
"port": 465
}
  • username:登录邮箱账户。
  • password:专用客户端密码或授权码(通常不是邮箱登录密码)。
  • sender:发件人邮箱地址。
  • host:SMTP 服务器地址。
  • port:SMTP 服务器端口(通常 SSL 为 465)。

注意:有的邮箱服务(如网易邮箱)需单独申请“客户端授权码”作为 password,需要参考邮箱服务商的具体要求。

自定义配置路径

可以通过 --config 选项指定配置文件路径,例如:

1
python3 sendmail.py -s "subject" -m "content" receiver@example.com --config ${HOME}/.config/mail-config.json