python - 从(Python)代码实际发送邮件的正确方法是什么?
问题描述
免责声明:由于这个问题的广泛性(见下文),我对标题犹豫不决,其他选项包括:
- 如何仅使用 Python 代码从本地主机发送邮件?
- 如何在不使用外部 SMTP 服务器的情况下从 Python 代码发送电子邮件?
- 是否可以仅使用 localhost 和 Python 直接向其目的地发送电子邮件?
首先,一点背景:
为了学习,我正在建立一个具有用户注册功能的网站。这个想法是,注册后用户将收到一封带有激活链接的电子邮件。我想用 Python 代码编写和发送电子邮件,这就是我想要求澄清的部分。
我的理解,在我开始之前(显然天真=)可以这样说明(假设有一个合法的电子邮件地址user@example.com):
在搜索示例后,我在 stackoverflow ( 1 , 2 , 3 , 4 ) 上遇到了一些问题和答案。从这些我提炼出以下片段,以编写和发送来自 Python 代码的电子邮件:
import smtplib
from email.message import EmailMessage
message = EmailMessage()
message.set_content('Message content here')
message['Subject'] = 'Your subject here'
message['From'] = 'me@example.com'
message['To'] = 'user@example.com'
smtp_server = smtplib.SMTP('smtp.server.address:587')
smtp_server.send_message(message)
smtp_server.quit()
下一个(显而易见的)问题是传递给什么smtplib.SMTP()
而不是'smtp.server.address:587'
. 从评论到这个答案,我发现本地 SMTP 服务器(只是为了测试目的)可以通过 启动python3 -m smtpd -c DebuggingServer -n localhost:1025
,然后smtp_server = smtplib.SMTP('smtp.server.address:587')
可以更改为smtp_server = smtplib.SMTP('localhost:1025')
并且所有发送的电子邮件都将显示在控制台中(从python3 -m smtpd -c DebuggingServer -n localhost:1025
执行命令的位置),是足以进行测试——这不是我想要的(我的目标是——能够从本地机器向“真实世界”的电子邮件地址发送邮件,仅使用 Python 代码)。
因此,下一步是设置一个本地 SMTP 服务器,能够将电子邮件发送到外部“真实世界”电子邮件地址(因为我想用 Python 代码完成这一切,所以服务器本身最好在Python也是)。我记得在一些杂志上读到(2000 年初),垃圾邮件发送者使用本地服务器发送邮件(那篇特定的文章正在谈论Sambar,其开发已于 2007 年结束,并且不是用 Python 编写的 :-) 我想那里应该是一些具有类似功能的当今解决方案。所以我开始搜索,我希望(在 stackoverflow 或其他地方)找到一个相当短的代码片段,它会做我想要的。我还没有找到这样的代码片段,但我遇到了一个标题为(Python) Send Email without Mail Server(它使用chilkat API),虽然我需要的(据说)就在那里,但在代码注释中,第一行明确指出:
真的可以在不连接邮件服务器的情况下发送电子邮件吗?并不真地。
和下面的几行:
以下是那些声称不需要邮件服务器的其他组件内部发生的情况:该组件使用预期收件人的电子邮件地址进行 DNS MX 查找,以查找该域的邮件服务器(即 SMTP 服务器)。然后它连接到该服务器并传递电子邮件。您仍在连接到 SMTP 服务器——只是不是您的服务器。
读到这些,让我明白了——很明显,我对这个过程的理解(如上图所示)缺乏一些细节。为了纠正这个问题,我已经阅读了 SMTP 上的整个RFC。
在阅读了 RFC 之后,我对这个过程的理解有所提高,可能是这样描绘的:
根据这种理解,我想澄清的实际问题是:
- 作为一般情况,我的“改进的理解”可以被认为是正确的吗?
- MX 查找究竟返回了哪些地址?
使用
host -t mx gmail.com
命令(由this answer建议),我能够检索以下内容:gmail.com mail is handled by 10 alt1.gmail-smtp-in.l.google.com. gmail.com mail is handled by 20 alt2.gmail-smtp-in.l.google.com. gmail.com mail is handled by 40 alt4.gmail-smtp-in.l.google.com. gmail.com mail is handled by 30 alt3.gmail-smtp-in.l.google.com. gmail.com mail is handled by 5 gmail-smtp-in.l.google.com.
- but none of these are mentioned in the [official docs][13] (ones that are there: `smtp-relay.gmail.com`, `smtp.gmail.com`, `aspmx.l.google.com`)
- 是否总是需要身份验证才能将电子邮件传递到已建立的邮件服务(比如 gmail)的 SMTP 服务器?
我知道要使用,例如邮件提交,无论收件人是否有地址(如docs
smtp.gmail.com
中所述) ,您都需要:@gmail
身份验证需要您的完整 Gmail 或 G Suite 电子邮件地址。
但是,如果将电子邮件
user@gmail.com
提交给不属于 gmail 的 SMTP 服务器,那么它将被重定向到其中一个 gmail 服务器(直接或通过网关/中继)。在这种情况下(我假设)电子邮件的发件人只需要在邮件提交时进行身份验证,那么在那之后 gmail 服务器将接受邮件而不进行身份验证?- 如果是,是什么阻止我“假装”成为这样的网关/中继并将电子邮件直接交给他们指定的 SMTP?然后,编写“代理 SMTP”也应该很容易,它只会通过 MX 查找搜索合适的服务器,然后直接将电子邮件发送给它。
- gmail SMTP 上的文档也提到
aspmx.l.google.com
了不需要身份验证的服务器,但是:
邮件只能发送给 Gmail 或 G Suite 用户。
话虽如此,我假设以下代码段应该可以用于将邮件提交到ExistingUser@gmail.com
邮箱:
<!-- language: lang-python -->
import smtplib
from email.message import EmailMessage
message = EmailMessage()
message.set_content('Message test content')
message['Subject'] = 'Test mail!'
message['From'] = 'me@whatever.com'
message['To'] = 'ExistingUser@gmail.com'
smtp_server = smtplib.SMTP('aspmx.l.google.com:25')
smtp_server.send_message(message)
smtp_server.quit()
运行时,上面的代码(ExistingUser@gmail.com
替换为有效邮件)会抛出OSError: [Errno 65] No route to host
. 我想在这里确认的是,aspmx.l.google.com
在代码中正确处理了与的通信。
解决方案
您对邮件工作原理的理解大致正确。一些可以解决问题的附加说明:
SMTP 用于两个不同的目的。你似乎混淆了这两个:
第一种用途,通常称为“提交”,是将邮件从 MUA(邮件用户代理、您的邮件程序、Outlook、Thunderbird,...)发送到 MTA(邮件传输代理,通常称为“邮件服务器”) . MTA 由您的ISP或 GMail 等邮件提供商运行。通常,它们的使用受到 IP 地址(只有该 ISP 的客户可以使用)或用户名/密码的限制。
第二种用途是将邮件从一个 MTA 发送到另一个 MTA。这部分通常是完全开放的,因为您可能愿意接受来自任何人的入站邮件。这也是采取反垃圾邮件措施的地方。
为了发送邮件,您至少需要 SMTP 的第二部分:与另一个 MTA 交谈以传递邮件的能力。
发送邮件的典型方法是在应用程序中编写邮件,然后将其发送到 MTA 邮件服务器进行传递。根据您的设置,该 MTA 可以安装在与您的 Python 代码在 (localhost) 上运行的同一台机器上,也可以是更“中央”的邮件服务器(可能需要身份验证)。
“您的”MTA 将负责处理邮件递送过程中的所有令人讨厌的细节,例如:
进行 DNS 查找以找出要联系的 MTA 以中继邮件。这包括 MX-lookup,但也包括其他回退机制,例如A-records。
重试传递,如果第一次尝试暂时失败
如果消息永久失败,则生成退回消息
制作多个邮件副本,以防不同域中的多个收件人
使用DKIM对邮件进行签名,以减少被标记为垃圾邮件的机会。
...
当然,您可以在自己的 Python 代码中重新实现所有这些功能,并将 MTA 与您的应用程序有效地结合起来,但我强烈建议不要这样做。邮件出奇地难以正确处理……
底线:尝试通过 SMTP 将邮件发送到您的提供商的邮件服务器或其他邮件服务。如果这是不可能的:如果您想运行自己的邮件服务器,请认真考虑。被标记为垃圾邮件发送者很容易发生;从垃圾邮件列表中删除要困难得多。不要在您的应用程序中重新实现 SMTP 代码。
推荐阅读
- ios - 在 info.plist 上放置一个资源字符串(来自 infoPlist.strings)
- wordpress - 如何为 Wordpress 设置 qTranslate 以自动为我提供德语,尽管我的网站语言是英语
- python - Python函数:f是什么意思?
- java - 如何使用 Spring Boot 设置 Spring JDBC 连接池?
- c# - 添加后无法使用ac#引用
- python - 在 python 中使用 selenium 创建一个用于网络抓取的循环
- syntax - 函数名和左括号之间的 Coffeescript 空格
- jquery - 下拉按钮不起作用也没有消失
- python-3.x - 如何用自定义类替换 h5py.AttributeManager?
- swift - 无法访问公共枚举 Swift 4.2