插入一条消息和附件,并发送电子邮件/签名/加密内容。
项目描述
信封
Quick layer over python-gnupg, M2Crypto, smtplib, magic and email handling packages. Their common use cases merged into a single function. Want to sign a text and tired of forgetting how to do it right? You do not need to know everything about GPG or S/MIME, you do not have to bother with importing keys. Do not hassle with reconnecting to an SMTP server. Do not study various headers meanings to let your users unsubscribe via a URL. You insert a message, attachments and inline images and receive signed and/or encrypted output to the file or to your recipients' e-mail. Just single line of code. With the great help of the examples below.
Envelope("my message")
.subject("hello world")
.to("example@example.com")
.attach(file_contents, name="attached-file.txt")
.smtp("localhost", 587, "user", "pass", "starttls")
.signature()
.send()
# Inline image
Envelope("My inline image: <img src='cid:image.jpg' />")
.attach(path="image.jpg", inline=True)
# Load a message and read its attachments
Envelope.load(path="message.eml").attachments()
# in bash: envelope --load message.eml --attachments
安装
-
从PyPi使用单条命令安装
pip3 install envelope
- 或者安装当前的GitHub master版本
pip3 install git+https://github.com/CZ-NIC/envelope.git
- 或者直接下载项目并运行
python3 -m envelope
-
如果您计划使用GPG进行签名/加密,请确保系统上已安装GPG,可以使用
sudo apt install gpg
,并可能查看配置您的GPG教程。 -
如果您计划使用S/MIME,您应该确保一些先决条件:
sudo apt install swig build-essential python3-dev libssl-dev && pip3 install M2Crypto
-
如果您计划发送电子邮件,请准备SMTP凭证或访问配置您的SMTP教程。
-
如果您要收到的电子邮件不在您的本地域中,请访问DMARC部分。
-
包python-magic作为依赖项使用。由于与file-magic包的知名名称冲突,如果您需要使用后者,无需担心,在安装envelope后,运行
pip uninstall python-magic && pip install file-magic
即可。这两个项目都与底层libmagic
兼容。这可能是已经安装的。然而,如果它尚未安装,请安装sudo apt install libmagic1
。
Bash completion
- 运行:
apt install bash-completion jq
- 复制:extra/envelope-autocompletion.bash 到
/etc/bash_completion.d/
- 重新启动终端
用法
例如,让我们以三种相同的方式生成一个包含GPG加密的"Hello world"内容的output_file
。
CLI
在终端中以CLI应用程序启动,查看envelope --help
envelope --message "Hello world" \
--output "/tmp/output_file" \
--from "me@example.com" \
--to "remote_person@example.com" \
--encrypt-path "/tmp/remote_key.asc"
模块:流畅接口
如果您的IDE支持自动完成,这是一个舒适的方式创建结构。
from envelope import Envelope
Envelope().message("Hello world")\
.output("/tmp/output_file")\
.from_("me@example.com")\
.to("remote_person@example.com")\
.encrypt(key_path="/tmp/remote_key.asc")
模块:单行函数
您可以通过将其作为模块导入来轻松编写一个单行函数,以加密您的代码或从应用程序中发送电子邮件。请参阅pydoc3 envelope
或下面的文档。
from envelope import Envelope
Envelope(message="Hello world",
output="/tmp/output_file",
from_="me@example.com",
to="remote_person@example.com",
encrypt="/tmp/remote_key.asc")
文档
envelope --help
(CLI参数帮助)和pydoc3 envelope
(查看模块参数帮助)应包含与这里相同的信息。
命令列表
所有参数都是可选的。
- --param在CLI中使用
- .param(value)表示位置参数
- .param(value=)表示关键字参数
- Envelope(param=)是单行参数
任何可达到的内容
当提到任何可获取的内容时,我们指的是纯文本、字节或流(例如:从open()
)。在模块接口中,您可以使用指向文件的Path
对象。在CLI接口中,提供了额外的标志。
如果对象不可访问,它将立即引发FileNotFoundError
。
Envelope().attach(path="file.jpg")
# Could not fetch file .../file.jpg
# FileNotFoundError: [Errno 2] No such file or directory: 'file.jpg'
输入/输出
-
message:消息/正文文本。如果没有设置字符串,则读取消息。此外,当"Content-Transfer-Encoding"设置为"base64"或"quoted-printable"时,它将被解码(当快速读取EML文件内容时很有用,例如:
cat file.eml | envelope --message
)。- --message:字符串。为空则读取。
- --input:(CLI仅限)消息文件的路径。(
--message
参数的替代方案。) - .message():在
str
中读取当前消息。 - .message(text):将消息设置为任何可获取的内容。
- .message(path=None, alternative="auto", boundary=None)
path
:文件的路径。替代
: "auto", "html", "plain" 您可以指定电子邮件文本替代。一些电子邮件阅读器更喜欢显示纯文本版本而不是HTML。默认情况下,我们尝试自动确定内容类型(见 mime)。print(Envelope().message("He<b>llo</b>").message("Hello", alternative="plain")) # (output shortened) # Content-Type: multipart/alternative; # boundary="===============0590677381100492396==" # # --===============0590677381100492396== # Content-Type: text/plain; charset="utf-8" # Hello # # --===============0590677381100492396== # Content-Type: text/html; charset="utf-8" # He<b>llo</b>
- 边界: 当指定替代时,如果您不希望创建随机边界,您可以设置电子邮件边界。
- .body(path=None):
.message
的别名(不带alternative
和boundary
参数) - .text(path=None):
.message
的别名(不带alternative
和boundary
参数) - 信封(message=): 任何可获取的内容
设置字符串的等效方法(在 Python 和 Bash 中)。
Envelope(message="hello") == Envelope().message("hello")
envelope --message "hello"
设置文件内容的等效方法(在 Python 和 Bash 中)。
from pathlib import Path Envelope(message=Path("file.txt")) == Envelope(message=open("file.txt")) == Envelope.message(path="file.txt")
envelope --input file.txt
信封有时可以处理错误的编码或尝试打印出有意义的警告。
# Issue a warning when trying to represent a mal-encoded message. b ="€".encode("cp1250") # converted to bytes b'\x80' e = Envelope(b) repr(e) # WARNING: Cannot decode the message correctly, plain alternative bytes are not in Unicode. # Envelope(message="b'\x80'") # When trying to output a mal-encoded message, we end up with a ValueError exception. e.message() # ValueError: Cannot decode the message correctly, it is not in Unicode. b'\x80' # Setting up an encoding (even ex-post) solves the issue. e.header("Content-Type", "text/plain;charset=cp1250") e.message() # '€'
-
输出: 要写入的文件的路径(否则返回内容)。
- --output
- .output(output_file)
- Envelope(output=)
收件人
- from: 电子邮件 – 如果加密则需要。
- --from 电子邮件。留空以读取值。
- --no-from 声明我们想要加密且永不解密。
- .from_(email): 电子邮件 | False | None。如果为 None,则返回当前
From
作为 地址 对象(即使是空的)。 - Envelope(from_=): 发件人电子邮件或 False 以明确省略。在无发件人加密时,我们不使用他们的密钥,因此我们无法再次解密。
# These statements are identical. Envelope(from_="identity@example.com") Envelope().from_("identity@example.com") # This statement produces both From header and Sender header. Envelope(from_="identity@example.com", headers=[("Sender", "identity2@example.com")]) # reading an Address object a = Envelope(from_="identity@example.com").from_() a == "identity@example.com", a.host == "example.com"
- to: 电子邮件或可迭代的更多电子邮件。在加密时,我们使用这些身份的密钥。可以在字符串中给出多个地址,由逗号(或分号)分隔。(同样适用于
to
、cc
、bcc
和reply-to
。)- --to: 一个或多个电子邮件地址。留空以读取。
$ envelope --to first@example.com second@example.com --message "hello" $ envelope --to first@example.com second@example.com
- .to(email_or_more): 如果为 None,则返回当前 地址 列表。如果为 False 或 "",则清除当前列表。
Envelope() .to("person1@example.com") .to("person1@example.com, John <person2@example.com>") .to(["person3@example.com"]) .to() # ["person1@example.com", "John <person2@example.com>", "person3@example.com"]
- Envelope(to=): 电子邮件或可迭代的更多电子邮件。
- --to: 一个或多个电子邮件地址。留空以读取。
- cc: 电子邮件或可迭代的更多电子邮件。可以在字符串中给出多个地址,由逗号(或分号)分隔。(同样适用于
to
、cc
、bcc
和reply-to
。)- --cc: 一个或多个电子邮件地址。留空以读取。
- .cc(email_or_more): 如果为 None,则返回当前 地址 列表。如果为 False 或 "",则清除当前列表。
Envelope() .cc("person1@example.com") .cc("person1@example.com, John <person2@example.com>") .cc(["person3@example.com"]) .cc() # ["person1@example.com", "John <person2@example.com>", "person3@example.com"]
- Envelope(cc=)
- bcc: 电子邮件或可迭代的更多电子邮件。可以在字符串中给出多个地址,由逗号(或分号)分隔。(同样适用于
to
、cc
、bcc
和reply-to
。)头部不会发送。- --bcc: 一个或多个电子邮件地址。留空以读取。
- .bcc(email_or_more): 如果为 None,则返回当前 地址 列表。如果为 False 或 "",则清除当前列表。
- Envelope(bcc=)
- reply-to: 电子邮件或可迭代的更多电子邮件。可以在字符串中给出多个地址,由逗号(或分号)分隔。(同样适用于
to
、cc
、bcc
和reply-to
。)该字段不会被加密。- --reply-to: 电子邮件地址或留空以读取值。
- .reply_to(email_or_more): 如果为 None,则返回当前 地址 列表。如果为 False 或 "",则清除当前列表。
- Envelope(reply_to=)
- from_addr: SMTP 信封 MAIL FROM 地址。
- --from-addr: 电子邮件地址或留空以读取值。
- from_addr(email):电子邮件或False。如果为None,则返回当前
SMTP信封MAIL FROM
作为一个地址对象(即使是空的)。 - .Envelope(from_addr=)
发送
-
send:通过电子邮件将消息发送给收件人。在CLI中为空白时发送,或为False时打印调试信息。
- --send
- .send(send=True, sign=None, encrypt=None)
- send:True立即发送。在CLI中为False(或0/false/no)时打印调试信息。
- 返回转换成bool的对象,如果消息成功发送则返回True。
- Envelope(send=)
$ envelope --to "user@example.org" --message "Hello world" --send 0 **************************************************************************************************** Have not been sent from - to user@example.org Content-Type: text/html; charset="utf-8" Content-Transfer-Encoding: 7bit MIME-Version: 1.0 Subject: From: To: user@example.org Date: Mon, 07 Oct 2019 16:13:37 +0200 Message-ID: <157045761791.29779.5279828659897745855@...> Hello world
-
subject:邮件主题。使用GPG加密,使用S/MIME时可见。
- --subject
- .subject(text=None, encrypt=None):
text
主题文本。encrypt
当使用PGP加密时,使用该文本代替实际受保护的主体。False表示不加密。- 如果没有指定任何参数,则返回当前主题。
- Envelope(subject=)
- Envelope(subject_encrypted=)
-
date:
- .date(date)
str|False
指定日期头(否则自动添加日期)。如果为False,则不会自动添加日期头。
- .date(date)
-
smtp:SMTP服务器
- --smtp
- .smtp(host="localhost", port=25, user=, password=, security=, timeout=3, attempts=3, delay=3, local_hostname=None)
- Envelope(smtp=)
- 参数
host
可以包含主机名或以下输入格式之一(例如:INI文件的路径或dict
)security
如果未设置,则对于端口587自动设置为starttls
,对于端口465设置为tls
timeout
SMTP等待多长时间才超时。attempts
我们尝试将消息发送到SMTP服务器的次数。delay
在重新尝试超时连接之前要睡眠多少秒。local_hostname
HELO/EHLO命令中本地主机的FQDN。
- 输入格式可能具有以下形式
None
默认使用localhost服务器- 标准
smtplib.SMTP
对象 list
或tuple
包含host, [port, [username, password, [security, [timeout, [attempts, [delay, [local_hostname]]]]]]]
参数- 例如:
envelope --smtp localhost 125 me@example.com
将设置主机、端口和用户名参数
- 例如:
dict
指定 {"host": ..., "port": ...}- 例如:
envelope --smtp '{"host": "localhost"}'
将设置主机参数
- 例如:
str
主机名或INI文件的路径(现有文件,以.ini
结尾,具有[SMTP]部分)[SMTP] host = example.com port = 587
- 不要担心在循环中传递
smtp
,我们只与服务器建立一次连接。如果超时,我们尝试重新连接一次。
smtp = "localhost", 25 for mail in mails: Envelope(...).smtp(smtp).send()
-
附件
- --attach:附件路径,后面跟可选的文件名和/或MIME类型。此参数可以多次使用。
envelope --attach "/tmp/file.txt" "displayed-name.txt" "text/plain" --attach "/tmp/another-file.txt"
- .attach(attachment=, mimetype=, name=, path=, inline=):
Envelope().attach(path="/tmp/file.txt").attach(path="/tmp/another-file.txt")
- 指定内容时的三种不同用法
- .attach(attachment=, mimetype=, name=):可以将单个附件的任何可获取内容放入attachment中,并可选地添加MIME类型或显示的文件名。
- .attach(mimetype=, name=, path=):可以指定路径和可选的MIME类型或显示的文件名。
- .attach(attachment=):可以将附件列表放入其中。该列表可以包含元组:
contents [,mime type] [,file name] [, True for inline]
。
- .attach(inline=True|str):指定内容ID(CID)以从HTML消息正文中引用图像。
- True:文件名、附件或路径文件名设置为CID。
- str:附件将获得此CID。
from pathlib import Path Envelope().attach(Path("file.jpg"), inline=True) # <img src='cid:file.jpg' /> Envelope().attach(b"GIF89a\x03\x00\x03...", name="file.gif", inline=True) # <img src='cid:file.gif' /> Envelope().attach(Path("file.jpg"), inline="foo") # <img src='cid:foo' /> # Reference it like: .message("Hey, this is an inline image: <img src='cid:foo' />")
- 指定内容时的三种不同用法
- 信封(附件=):附件或其列表。附件由 任何可获取的内容 定义,可选地与用于电子邮件的文件名、MIME 类型以及/或用于内联的 True 结合:
内容[, MIME 类型] [, 文件名] [, 内联 True]
Envelope(attachments=[(Path("/tmp/file.txt"), "displayed-name.txt", "text/plain"), Path("/tmp/another-file.txt")])
-
MIME:设置内容的 MIME 子类型:“auto”(默认)、“html” 或 “plain” 用于纯文本。主类型始终设置为“text”。如果一行超过 1000 个字符,则通过字节安全地传输消息(否则这些非标准的长行可能会导致传输 SMTP 服务器包含行断和多余的空格,这可能会破坏 DKIM 签名)。如果消息中放置了
Content-Type
头部,则 MIME 部分功能将 跳过。- --mime SUBTYPE
- .mime(subtype="auto", nl2br="auto")
- nl2br:如果为 True,将在 HTML 消息中的每一行断处追加
<br>
。如果为 "auto",则只有在 HTML 消息中没有<br
或<p
时才会更改行断。
- nl2br:如果为 True,将在 HTML 消息中的每一行断处追加
- Envelope(mime=)
-
headers:任何自定义头部(这些将不会与 GPG 或 S/MIME 加密)
- --header name value(可多次使用)
- .header(name, value=None, replace=False)
value
如果为 None,则返回头部或其列表(如果该头部被多次使用)。(注意,To、Cc、Bcc 和 Reply-To 头部始终返回列表。)replace
如果为 True,则先删除任何具有key
名称的头部,如果val
为 None,则删除该头部。否则,将追加具有相同名称的另一个头部。
Envelope().header("X-Mailer", "my-app").header("X-Mailer") # "my-app" Envelope().header("Generic-Header", "1") \ .header("Generic-Header", "2") \ .header("Generic-Header") # ["1", "2"]
- Envelope(headers=[(name, value)])
等效头部
envelope --header X-Mailer my-app
Envelope(headers=[("X-Mailer", "my-app")]) Envelope().header("X-Mailer", "my-app")
特定头信息
这些辅助功能通过流畅接口提供。
-
.list_unsubscribe(uri=None, one_click=False, web=None, email=None):您可以指定 URL、电子邮件或两者都指定。
- .list_unsubscribe(uri):我们尝试确定这是否是电子邮件,并在需要时添加方括号和 'https:'/'mailto:'。例如:
me@example.com?subject=unsubscribe
、example.com/unsubscribe
、<https://example.com/unsubscribe>
- .list_unsubscribe(email=):电子邮件地址。例如:
me@example.com
、mailto:me@example.com
- .list_unsubscribe(web=, one_click=False):指定 URL。例如:
example.com/unsubscribe
、http://example.com/unsubscribe
。如果one_click=True
,则添加 rfc8058 List-Unsubscribe-Post 头部。这表示用户可以通过单个点击来取消订阅,该点击是通过 POST 请求实现的,以防止电子邮件扫描器错误地访问取消订阅页面。必须存在 'https' URL。
# These will produce: # List-Unsubscribe: <https://example.com/unsubscribe> Envelope().list_unsubscribe("example.com/unsubscribe") Envelope().list_unsubscribe(web="example.com/unsubscribe") Envelope().list_unsubscribe("<https://example.com/unsubscribe>") # This will produce: # List-Unsubscribe: <https://example.com/unsubscribe>, <mailto:me@example.com?subject=unsubscribe> Envelope().list_unsubscribe("example.com/unsubscribe", mail="me@example.com?subject=unsubscribe")
- .list_unsubscribe(uri):我们尝试确定这是否是电子邮件,并在需要时添加方括号和 'https:'/'mailto:'。例如:
-
.auto_submitted:
- .auto_submitted(val="auto-replied"):由自动进程直接对另一条消息进行响应。
- .auto_submitted.auto_generated():自动(通常是周期性)进程(如 UNIX “cron jobs”),这些进程不是对其他消息的直接响应
- .auto_submitted.no():消息是由人类发起的
Envelope().auto_submitted() # mark message as automatic
Envelope().auto_submitted.no() # mark message as human produced
加密标准方法
注意,如果没有指定 gpg 或 smime,我们将尝试自动确定方法。
- gpg:如果为 True,则优先选择 GPG 而不是 S/MIME 或 GNUPG 环的 home 路径(否则使用默认的 ~/.gnupg)
- --gpg [path]
- .gpg(gnugp_home=True)
- Envelope(gpg=True)
- .smime:优先选择 S/MIME 而不是 GPG
- --smime
- .smime()
- Envelope(smime=True)
签名
- sign:签名消息。
key
参数- –sign key:(对于
key
见上文) - –sign-path:包含发送者私钥的文件名。(
sign
参数的替代方案。) - –passphrase:如果需要,为密钥提供密码。
- –attach-key:GPG:在发送时将公钥附加到附件(留空)。
- –cert:S/MIME:如果未包含在密钥中,证书内容。
- –cert-path:S/MIME:如果密钥中未包含证书,包含发送者私钥的文件名。(
cert
参数的替代方案。) - .sign(key=True, passphrase=, attach_key=False, cert=None, key_path=None):现在签名(可以指定参数)。(对于
key
见上文。) - .signature(key=True, passphrase=, attach_key=False, cert=None, key_path=None):稍后签名(当使用 .sign()、.encrypt() 或 .send() 函数时)
- Envelope(sign=key):(对于
key
见上文) - Envelope(passphrase=):如果需要,为签名密钥提供密码。
- Envelope(attach_key=):如果为真,在发送时将 GPG 公钥作为附件附加。
- Envelope(cert=):S/MIME:任何可获得的内 容
加密
-
encrypt:要加密的接收者 GPG 公钥或 S/MIME 证书。
key
参数- –encrypt [key]:(对于
key
见上文) 将 0/false/no 输入以禁用encrypt-path
。 - –encrypt-path (CLI only):包含接收者公钥的文件名。(
encrypt
参数的替代方案。) - .encrypt(key=True, sign=, key_path=):
sign
参见签名,例如:您可以指定布尔值或默认签名密钥 ID/指纹或 "auto" 用于 GPG 或包含 S/MIME 密钥 + 签名证书的 任何可获得的内 容。key_path
:密钥/证书内容(key
参数的替代方案)
- .encryption(key=True, key_path=):稍后加密(当使用 .sign()、.encrypt() 或 .send() 函数时。如果需要,在参数中指定包含 GPG 加密密钥或 S/MIME 加密证书的 任何可获得的内 容。)
- Envelope(encrypt=key):(对于
key
见上文)
# message gets encrypted for multiple S/MIME certificates envelope --smime --encrypt-path recipient1.pem recipient2.pem --message "Hello" # message gets encrypted with the default GPG key envelope --message "Encrypted GPG message!" --subject "Secret subject will not be shown" --encrypt --from person@example.com --to person@example.com # message not encrypted for the sender (from Bash) envelope --message "Encrypted GPG message!" --subject "Secret subject will not be shown" --encrypt receiver@example.com receiver2@example.com --from person@example.com --to receiver@example.com receiver2@example.com
# message not encrypted for the sender (from Python) Envelope() .message("Encrypted GPG message!") .subject("Secret subject will not be shown") .from_("person@example.com") .to(("receiver@example.com", "receiver2@example.com")) .encrypt(("receiver@example.com", "receiver2@example.com"))
GPG 注意事项
- 如果 GPG 加密失败,它将尝试确定哪个接收者缺少密钥。
- 默认情况下,GPG 使用 from 标头接收者的密钥进行加密。
- 当前内部忽略密钥 ID/指纹,GPG 自己决定使用哪个密钥。
支持
- .recipients():返回所有接收者的集合 –
To
、Cc
、Bcc
- .recipients(clear=True):删除所有
To
、Cc
和Bcc
接收者,并返回Envelope
对象。
- .recipients(clear=True):删除所有
- attachments:访问附件列表。
- –attachments [NAME] 获取附件列表或指定
NAME
的附件内容。 - .attachments(name=None, inline=None)
- name (str):要返回的唯一附件名称。
- inline (bool):仅过滤内联/嵌入附件。
- Attachment 对象具有 .name 文件名、.mimetype、.data 原始数据属性
- 如果转换为 str/bytes,则返回其原始 .data。
- –attachments [NAME] 获取附件列表或指定
- .copy():返回实例的深度副本,可用于独立使用。
factory = Envelope().cc("original@example.com").copy
e1 = factory().to("to-1@example.com")
e2 = factory().to("to-2@example.com").cc("additional@example.com") #
print(e1.recipients()) # {'to-1@example.com', 'original@example.com'}
print(e2.recipients()) # {'to-2@example.com', 'original@example.com', 'additional@example.com'}
-
通过 .message() 和 .subject() 读取消息和主题
-
预览:将消息或数据作为可读文本返回字符串。例如:虽然我们必须使用 quoted-printable(如 str 中所示),但这里输出将是纯文本。
- --preview
- .preview()
-
检查:检查所有电子邮件地址和SMTP连接,如果成功则返回 True/False。根据发件人的域名尝试查找 SPF、DKIM 和 DMARC DNS 记录并打印出来。
- --check
- .check(check_mx=True, check_smtp=True)
check_mx
可以检查电子邮件地址的 MX 记录,而不仅仅是它们的格式。check_smtp
我们尝试连接到 SMTP 服务器。
$ envelope --smtp localhost 25 --from me@example.com --check SPF found on the domain example.com: v=spf1 -all See: dig -t SPF example.com && dig -t TXT example.com DKIM found: ['v=DKIM1; g=*; k=rsa; p=...'] Could not spot DMARC. Trying to connect to the SMTP... Check succeeded.
-
.as_message():生成 email.message.Message 对象。
e = Envelope("hello").as_message() print(type(e), e.get_payload()) # <class 'email.message.EmailMessage'> hello\n
注意:由于标准 Python 库中的一个错误(https://github.com/python/cpython/issues/99533 和 #19),当您使用这种方式访问消息并使用名称超过 34 个字符的附件进行签名时,您将丢失 GPG。
-
加载:解析 任何可获取的内容(包括 email.message.Message),如 EML 文件,以构建 Envelope 对象。
- 它可以解密消息并解析其(内联或附件)附件。
- 注意:如果您将此重构的消息发送出去,您可能因为 Message-ID 重复而无法接收。在重新发送之前,至少删除 Message-ID 报头。
- (静态) .load(message, *, path=None, key=None, cert=None, gnupg_home=None)
- message: 任何可获取的内容
- path:文件路径,是
message
的替代 - key,cert:在解密 S/MIME 消息时指定(可以捆绑在一起作为
key
) - gnupg_home:GNUPG_HOME 的路径或 None(如果应使用环境默认值)。
Envelope.load("Subject: testing message").subject() # "testing message"
- bash
- 允许使用空白
--subject
或--message
标志来显示 - --load FILE
$ envelope --load email.eml Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit MIME-Version: 1.0 Subject: testing message Message body $ envelope --load email.eml --subject testing message
- (bash) 管道中的内容,envelope 可执行文件在没有参数的情况下使用
$ echo "Subject: testing message" | envelope Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit MIME-Version: 1.0 Subject: testing message $ cat email.eml | envelope $ envelope < email.eml
- 允许使用空白
-
smtp_quit():由于 Envelope 倾向于重用所有 SMTP 实例,您可能需要显式退出它们。要么调用 Envelope 类的此方法关闭所有缓存的连接,要么调用 Envelope 对象以仅关闭当前使用的连接。
e = Envelope().smtp(server1).smtp(server2) e.smtp_quit() # called on an instance → closes connection to `server2` only Envelope.smtp_quit() # called on the class → closes both connections
地址
遇到的任何电子邮件地址都会内部转换为可以从中导入的 Address(str)
对象。您可以安全地访问以下 str
属性
.name
– 真实名称.address
– 电子邮件地址.host
– 其域名.user
– 电子邮件的用户名部分
from envelope import Address
a = Address("John <person@example.com>")
a.name == "John", a.address == "person@example.com", a.host == "example.com", a.user == "person"
空对象也行。例如,如果 From
报头未设置,我们将得到一个空的 Address 对象。尽管如此,仍然可以安全地访问其属性。
a = Envelope.load("Empty message").from_()
bool(a) is False, a.host == ""
Address() == Address("") == "", Address().address == ""
方法 .casefold()
返回 casefolded Address
对象,这对于与字符串进行比较很有用,而与其他 Address
对象的比较会自动进行 casefold。
a = Address("John <person@example.com>")
c = a.casefold()
a is not c, a == c, a.name == "john", a.name != c.name
方法 .is_valid(check_mx=False)
如果格式有效则返回布尔值。当 check_mx
设置为 True
时,也会查询 MX 服务器。
由于 Address
是 str
的子类,因此您可以安全地连接此类对象。
", ".join([a, a]) # "John <person@example.com>, "John <person@example.com>"
a + " hello" # "John <person@example.com> hello"
如果它们的电子邮件地址相同,则 Address 对象相等。(它们的真实名称可能不同。)如果字符串包含其电子邮件地址或整个表示,则 Address 对象等于字符串。
"person@example.com" == Address("John <person@example.com>") == "John <person@example.com>" # True
关于 to
、cc
、bcc
和 reply-to
,可以在字符串中提供多个地址,用逗号(或分号)分隔。可以在 Address
对象上调用 .get(address:bool, name:bool)
方法来过滤所需信息。
e = (Envelope()
.to("person1@example.com")
.to("person1@example.com, John <person2@example.com>")
.to(["person3@example.com"]))
[str(x) for x in e.to()] # ["person1@example.com", "John <person2@example.com>", "person3@example.com"]
[x.get(address=False) for x in e.to()] # ["", "John", ""]
[x.get(name=True) for x in e.to()] # ["person1@example.com", "John", "person3@example.com"]
# return an address if no name given
[x.get(address=True) for x in e.to()] # ["person1@example.com", "person2@example.com", "person3@example.com"]
# addresses only
对于一些异乎寻常的情况,Address 通常比底层的标准库更好地执行解析任务(参见 2004 年的 错误报告)。
from email.utils import parseaddr
from envelope import Address
parseaddr("alice@example.com <bob@example.malware>")
# ('', 'alice@example.com') -> empty name and wrong address
Address("alice@example.com <bob@example.malware>").address
# 'bob@example.malware' -> the right address
实验性
由于我们倾向于保持API简单并尽可能减少向后不兼容的更改,因此很难确定正确的方法。欢迎您提出建议!以下方法没有稳定的API,因此它们的名称以下划线开头。
_report()
:访问multipart/report
。
目前仅支持 XARF。您可以直接访问字段,无需任何额外的 json
解析。
if xarf := Envelope.load(path="xarf.eml")._report():
print(xarf['SourceIp']) # '192.0.2.1'
信封对象
将对象转换为字符串或布尔值
在成功签名、加密或发送后,对象解析为 True,签名文本/生成的电子邮件可以通过 str() 获取。
o = Envelope("message", sign=True)
str(o) # signed text
bool(o) # True
对象相等性
信封对象等于一个 str
、bytes
或另一个 Envelope
,如果它们的 bytes
相同。
# Envelope objects are equal
sign = {"message": "message", "sign": True}
Envelope(**sign) == Envelope(**sign) # True
bytes(Envelope(**sign)) # because their bytes are the same
# b'-----BEGIN PGP SIGNED MESSAGE-----\nHash: SHA512\n\nmessage\n-----BEGIN PGP SIGNATURE-----\n\niQEzBAEBCgAdFiE...\n-----END PGP SIGNATURE-----\n'
# however, result of a PGP encrypting produces always a different output
encrypt = {"message": "message", "encrypt": True, "from_": False, "to": "person@example.com"}
Envelope(**encrypt) != Envelope(**encrypt) # Envelope objects are not equal
示例
签名和加密
签名消息。
Envelope(message="Hello world", sign=True)
使用标准 pathlib 库从文件中加载消息并签名。
from pathlib import Path
Envelope(message=Path("/tmp/message.txt"), sign=True)
从文件流中获取消息并签名。
with open("/tmp/message.txt") as f:
Envelope(message=f, sign=True)
签名并加密消息,以便可以使用 me@example.com 和 remote_person@example.com 的密钥(应已加载到密钥环中)进行解密。
Envelope(message="Hello world", sign=True,
encrypt=True,
from_="me@example.com",
to="remote_person@example.com")
签名并加密消息,以便可以使用 me@example.com 和 remote_person@example.com 的密钥(从文件中导入到密钥环)进行解密。
Envelope(message="Hello world", sign=True,
encrypt=Path("/tmp/remote_key.asc"),
from_="me@example.com",
to="remote_person@example.com")
通过不同的密钥环签名消息。
Envelope(message="Hello world", sign=True, gnupg="/tmp/my-keyring/")
使用需要密码的密钥签名消息。
Envelope(message="Hello world", sign=True, passphrase="my-password")
使用默认启用签名并具有默认密钥环路径的密钥签名消息。每次 factory
调用都将遵守这些默认值。
factory = Envelope().signature(True).gpg("/tmp/my-keyring").copy
factory().(message="Hello world")
发送
通过模块调用发送电子邮件。
Envelope(message="Hello world", send=True)
通过 CLI 和默认 SMTP 服务器 localhost 的端口 25 发送电子邮件。
envelope --to "user@example.org" --message "Hello world" --send
指定 SMTP 服务器的主机、端口、用户名和密码时发送。
envelope --to "user@example.org" message "Hello world" --send --smtp localhost 123 username password
通过字典指定 SMTP 服务器时发送。
envelope --to "user@example.org" --message "Hello world" --send --smtp '{"host": "localhost", "port": "123"}'
通过模块调用指定 SMTP 服务器时发送。
Envelope(message="Hello world", to="user@example.org", send=True, smtp={"host":"localhost"})
附件
您可以通过许多不同的方式附加文件。选择最适合您的方法。
Envelope(attachment=Path("/tmp/file.txt")) # file name will be 'file.txt'
with open("/tmp/file.txt") as f:
Envelope(attachment=f) # file name will be 'file.txt'
with open("/tmp/file.txt") as f:
Envelope(attachment=(f, "filename.txt"))
Envelope().attach(path="/tmp/file.txt", name="filename.txt")
内联图片
您需要做的唯一事情是将附件的 inline=True
参数设置。然后,您可以在消息内部使用 cid
关键字引用图像。有关更多详细信息,请参阅 发送 部分的 附件。
(Envelope()
.attach(path="/tmp/file.jpg", inline=True)
.message("Hey, this is an inline image: <img src='cid:file.jpg' />"))
复杂示例
通过默认 SMTP 服务器,通过所有三个接口发送加密并签名的消息(GPG)。
# CLI interface
envelope --message "Hello world" --from "me@example.org" --to "user@example.org" --subject "Test" --sign --encrypt -a /tmp/file.txt -a /tmp/file2 application/gzip zipped-file.zip --send
from pathlib import Path
from envelope import Envelope
# fluent interface
Envelope().message("Hello world").from_("me@example.org").to("user@example.org").subject("Test").signature().encryption().attach(path="/tmp/file.txt").attach(Path("/tmp/file2"), "application/gzip", "zipped-file.zip").send()
# one-liner interface
Envelope("Hello world", "me@example.org", "user@example.org", "Test", sign=True, encrypt=True, attachments=[(Path("/tmp/file.txt"), (Path("/tmp/file2"), "application/gzip", "zipped-file.zip")], send=True)
在 me@example.com 签名私钥、user@example.com 加密公钥和本地主机 localhost:25 上的开放 SMTP 服务器都可用的情况下,将 --send
更改为 --send 0
(或将 .send()
更改为 .send(False)
或将 send=True
更改为 send=False
),以调查可能类似于以下输出的生成消息
****************************************************************************************************
Have not been sent from me@example.org to user@example.org
Encrypted subject: Test
Encrypted message: b'Hello world'
Subject: Encrypted message
MIME-Version: 1.0
Content-Type: multipart/encrypted; protocol="application/pgp-encrypted";
boundary="===============8462917939563016793=="
From: me@example.org
To: user@example.org
Date: Tue, 08 Oct 2019 16:16:18 +0200
Message-ID: <157054417817.4405.938581433237601455@promyka>
--===============8462917939563016793==
Content-Type: application/pgp-encrypted
Version: 1
--===============8462917939563016793==
Content-Type: application/octet-stream; name="encrypted.asc"
Content-Description: OpenPGP encrypted message
Content-Disposition: inline; filename="encrypted.asc"
-----BEGIN PGP MESSAGE-----
hQMOAyx1c9zl1h4wEAv+PmtwjQDt+4XCn8YQJ6d7kyrp2R7xzS3PQwOZ7e+HWJjY
(...)
RQ8QtLLEza+rs+1lgcPgdBZEHFpYpgDb0AUvYg9d
=YuqI
-----END PGP MESSAGE-----
--===============8462917939563016793==--
相关事项
发送电子邮件并不意味着它会被接收。通过您的本地域成功发送它并不意味着公共邮箱会接受它。如果您不够可靠,您的电子邮件甚至可能不会出现在收件人的垃圾邮件中,它可能未经通知就被丢弃。
配置您的SMTP
如果您在应用程序能够发送电子邮件的 SMTP 服务器上有账户,这总是更容易。如果不是这种情况,存在各种 SMTP 服务器,但作为一个快速且不安全的解决方案,我测试了 bytemark/smtp Docker 映像,它允许您通过单行启动 SMTP 服务器。
docker run --network=host --restart always -d bytemark/smtp # starts open port 25 on localhost
envelope --message "SMTP test" --from [your e-mail] --to [your e-mail] --smtp localhost 25 --send
选择加密方法
配置您的GPG
为了签名消息,您需要一个私钥。让我们假设一个用例,当您的应用程序在 www-data
用户下运行时,并通过位于:/var/www/.gnupg
的密钥进行 GPG 签名。您有一个 SMTP 服务器,应用程序可以使用电子邮件账户。
ls -l $(tty) # see current TTY owner
sudo chown www-data $(tty) # if creating the key for a different user and generation fails, changing temporarily the ownership of the terminal might help (when handling passphrase, the agent opens the controlling terminal rather than using stdin/stdout for security purposes)
GNUPGHOME=/var/www/.gnupg sudo -H -u www-data gpg --full-generate-key # put application e-mail you are able to send e-mails from
# sudo chown [USER] $(tty) # you may set back the TTY owner
GNUPGHOME=/var/www/.gnupg sudo -H -u www-data gpg --list-secret-keys # get key ID
GNUPGHOME=/var/www/.gnupg sudo -H -u www-data gpg --send-keys [key ID] # now the world is able to pull the key from a global webserver when they receive an e-mail from you
GNUPGHOME=/var/www/.gnupg sudo -H -u www-data gpg --export [APPLICATION_EMAIL] | curl -T - https://keys.openpgp.org # prints out the link you can verify your key with on `keys.openpgp.org` (ex: used by default by Thunderbird Enigmail; standard --send-keys method will not verify the identity information here, hence your e-mail would not be searchable)
GNUPGHOME=/var/www/.gnupg sudo -H -u www-data envelope --message "Hello world" --subject "GPG signing test" --sign [key ID] --from [application e-mail] --to [your e-mail] --send # you now receive e-mail and may import the key and set the trust to the key
密钥传播需要数小时。如果密钥因服务器上找不到而无法导入您的电子邮件客户端,请早上再次尝试或检查在线搜索表单:http://hkps.pool.sks-keyservers.net。将您的指纹放在网页或名片上,以便每个人都可检查您的签名是否有效。
配置您的S/MIME
如果您应该使用S/MIME,您可能会被告知从哪里获取您的密钥和证书。如果您计划自己尝试,请生成您的certificate.pem
。
- 要么:您有私钥吗?
openssl req -key YOUR-KEY.pem -nodes -x509 -days 365 -out certificate.pem # will generate privkey.pem alongside
- 要么:您没有私钥吗?
openssl req -newkey rsa:1024 -nodes -x509 -days 365 -out certificate.pem # will generate privkey.pem alongside
现在,您可以使用您的密钥和证书签名一条消息。(然而,消息将不可信,因为没有权威机构签署了证书。)将证书给您的朋友,以便他们可以验证消息是否来自您。从朋友那里接收证书,以便使用它加密消息。
envelope --message "Hello world" --subject "S/MIME signing test" --sign-path [key file] --cert-path [certificate file] --from [application e-mail] --to [your e-mail] --send # you now receive e-mail
DNS验证工具
这只是一个关于这些反垃圾邮件机制的简要说明,以便您了解基本情况。
每次,接收者都应该通过DNS向发件人的域提出这些问题。
SPF
接收者询问发件人的域:您是否允许发送者的IP/域名代表您发送电子邮件?邮件起源的IP/域名是否在SMTP信封MAIL FROM地址域的DNS中列为有效?
检查您的域名是否在SPF上
dig -t TXT example.com
SPF技术与指定的.from_addr
方法的SMTP信封MAIL FROM地址相关联,然后由接收服务器存储到Return-Path头中,它与像From .from_
、Reply-To .reply_to
或Sender .header("Sender")
这样的头没有任何共同之处。
DKIM
接收者询问发件人的域:给我公钥,以便我可以检查电子邮件头中的哈希值,该哈希值声称消息是由您的私钥编写的。这样,电子邮件就会从您那里可信地发出,并且在途中没有人修改过它。
检查您的域名是否在DKIM上
dig -t TXT [selector]._domainkey.example.com
您可以从收到的电子邮件消息中获取selector
。检查DKIM-Signature
行及其s
参数的值。
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=example.com; s=default;
DMARC
您关于SPF和DKIM的政策是什么?您有哪些滥用地址?
检查您的域名是否在DMARC上
dig -t TXT _dmarc.example.com
项目详情
envelope-2.0.5.tar.gz 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | edf76018285412b5ced2d81c2b60725e4f83262998827cce8adf32ed4c1cdb91 |
|
MD5 | d3b1fd5865e9528a04672e0b987ff402 |
|
BLAKE2b-256 | 8b150ec2303a63ebbcaaa73b86431fb0b476adfe2bc244a6be13db559e36eb29 |