首页 > 解决方案 > 从(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 之后,我对这个过程的理解有所提高,可能是这样描绘的: 我的理解提高了


根据这种理解,我想澄清的实际问题是:

  1. 作为一般情况,我的“改进的理解”可以被认为是正确的吗?
  2. MX 查找究竟返回了哪些地址?
- 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`)
  1. 是否总是需要身份验证才能将电子邮件传递到已建立的邮件服务(比如 gmail)的 SMTP 服务器?
  1. 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在代码中正确处理了与的通信。

标签: pythonemailnetworkingsmtpgmail

解决方案


您对邮件工作原理的理解大致正确。一些可以解决问题的附加说明:

  • 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 代码。


推荐阅读