从(Python)代码实际发送邮件的正确方法是什么?

时间:2018-06-05 08:08:26

标签: python email networking smtp gmail

免责声明:由于此问题的广泛性(见下文;-),我对标题犹豫不决,其他选项包括:

  • 如何使用Python代码从localhost发送邮件?
  • 如何在不使用外部SMTP服务器的情况下从Python代码发送电子邮件?
  • 是否可以使用localhost和Python直接向其目的地发送电子邮件?

首先,一点上下文
为了学习,我正在建立一个具有用户注册功能的网站。这个想法是,注册后用户将收到一封带有激活链接的电子邮件。我想写一下&从Python代码发送电子邮件,这是我想要求澄清的部分。

我的理解,在我开始之前(显然天真=)可以这样说明(假设有合法的电子邮件地址 user@example.com ):< / p>

My naive understanding

在搜索了一些例子后,我碰到了一些问题&amp; stackoverflow上的答案(1234)。从这些我已经提炼出以下片段,撰写并发送来自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'的内容。从评论到this answer,我发现本地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命令的地方),足以进行测试 - 这不是我想要的(我的目标是 - 发送邮件到真实的能力) -world&#39;来自本地机器的电子邮件地址,仅使用Python代码。)

因此,下一步是设置本地SMTP服务器,能够向外部真实世界发送电子邮件。电子邮件地址(因为我想从Python代码中完成所有操作,所以服务器本身也最好用Python实现)。我回忆起一些杂志(2000年初)的读物,垃圾邮件发送者使用本地服务器发送邮件(特定文章讨论的是Sambar,其开发已于2007年结束,而且不是用Python编写的: - 我认为应该有一些具有类似功能的现今解决方案。所以我开始搜索,我希望找到(在stackoverflow或其他地方)一个相当短的代码片段,它将做我想要的。我还没有找到这样的代码段,但是我遇到了一个标题为(Python) Send Email without Mail Server的片段(使用chilkat API),尽管我需要(据说)所有内容都在那里,在评论中代码,第一行明确说明:

  

是否真的可以在不连接邮件服务器的情况下发送电子邮件?不是真的。

以下几行:

  

以下是声称不需要邮件服务器的其他组件内部发生的情况:该组件使用预期收件人的电子邮件地址执行DNS MX查找以查找邮件服务器(即SMTP服务器)对于该域名。然后它连接到该服务器并发送电子邮件。您仍然连接到SMTP服务器 - 而不是您的服务器。

读到这一点,让我理解 - 显然,我对这个过程的理解(在上图中反映出来)缺乏一些细节。为了纠正这个问题,我已经阅读了整个RFC on SMTP

阅读完RFC后,我对该过程的理解可能会如下图所示: My improved understanding

根据这种理解,来了实际问题我想澄清一下:

  1. 我的&#34; 能否改善理解&#34;被认为是正确的,作为一般情况?
  2. 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.
      
    • official docs中没有提到这些内容(smtp-relay.gmail.comsmtp.gmail.comaspmx.l.google.com
  3. 是否始终需要身份验证才能将电子邮件传递给已建立的邮件服务(例如gmail)的SMTP服务器?

    • 我知道要使用smtp.gmail.com来提交邮件,无论收件人是否有@gmail地址,您都需要(如user@gmail.com中所述3}}):

        

      验证时需要完整的Gmail或G Suite电子邮件地址。

    • 但是,如果将aspmx.l.google.com的电子邮件提交给不属于gmail的SMTP服务器,则会将其重定向到其中一个gmail服务器(直接或通过网关/中继) 。在这种情况下(我假设)电子邮件的发件人只需要在邮件提交上进行身份验证,那么之后gmail服务器将接受邮件而不进行身份验证?

      • 如果是的话,是什么阻止了我&#34;假装&#34;是这样的网关/中继并直接将电子邮件移交给指定的SMTP?然后它也应该很容易编写一个&#34; proxy-SMTP&#34;,它将只通过MX查找搜索适当的服务器,并直接向其发送电子邮件。
  4. docs,也提到了ExistingUser@gmail.com服务器,但不需要身份验证:

      

    邮件只能发送给Gmail或G Suite用户。

    话虽如此,我假设以下代码段可以用于向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。我想在此确认的是,代码中正确处理了与{{1}}的通信。

2 个答案:

答案 0 :(得分:4)

您对邮件工作原理的理解大致正确。一些其他说明可能会清除问题:

  • SMTP用于两个不同的目的。您似乎混淆了这两个。

    • 第一种用法(通常称为“提交”)是将邮件从MUA(邮件用户代理,您的邮件程序,Outlook,Thunderbird等)发送到MTA(邮件传输代理,通常是称为“邮件服务器”)。 MTA由您的ISP或邮件提供商(例如GMail)运行。通常,它们的使用受IP地址(只有所述ISP的客户可以使用它)或用户名/密码的限制。

    • 第二个用途是将邮件从一个MTA发送到另一个MTA。通常,这部分是开放的,因为您可能愿意接受任何人的入站邮件。这也是采取反垃圾邮件措施的地方。

要发送邮件,至少需要SMTP的第二部分:与另一个MTA进行通信以传递邮件的功能。

发送邮件的典型方法是在应用程序中编写邮件,然后将其发送到MTA邮件服务器进行传递。根据您的设置,MTA可以与Python代码在(localhost)上运行时安装在同一台计算机上,也可以是“中央”邮件服务器(可能需要身份验证)。

“您的” MTA将处理传递邮件的所有令人讨厌的细节,例如:

  • 执行DNS查找,以查找要联系以中继邮件的MTA。这包括MX查找,还包括其他后备机制,例如A-records

  • 如果第一次尝试暂时失败,请重试发送

  • 如果退回消息永久失败,则生成退回消息

  • 在不同域中有多个收件人的情况下,制作邮件的多个副本

  • 使用DKIM签名邮件以减少将其标记为垃圾邮件的可能性。

  • ...

当然,您可以在自己的Python代码中重新实现所有这些功能,并有效地将MTA与您的应用程序结合在一起,但是我强烈建议您不要这样做。邮件出奇地难以正确处理...

底线:尝试通过SMTP将邮件发送到提供商的邮件服务器或其他邮件服务。如果这不可能:如果要运行自己的邮件服务器,请认真考虑。被标记为垃圾邮件制造者很容易发生;从垃圾邮件列表中删除要困难得多。不要在应用程序中重新实现SMTP代码。

答案 1 :(得分:0)

感谢这些回答,以及我的其他问题:123,以及其他人的以下两个问题(和答案):onetwo —我想现在可以独自回答自己发布的问题了。


我将一一解答这些问题:

  1. 是的,一般情况下,可以这样描述电子邮件的发送: My improved understanding

  2. MX查找返回服务器的地址,这些服务器接收发往指定域的电子邮件。

    • 关于“为什么smtp-relay.gmail.com命令未返回smtp.gmail.comaspmx.l.google.comhost -t mx gmail.com?”。 another answer涵盖了这个问题。这里要掌握的要点是:
        MX查找返回的
      • 服务器负责接收该域的电子邮件(在这种情况下为gmail)
      • gmail docs中列出的
      • 服务器用于发送发送邮件(即gmail用户想要发送给其他gmail用户或其他用户的邮件已提交给这些服务器)
  3. 对于接收电子邮件(即MX查找返回的电子邮件)的服务器,
  4. 不需要身份验证。

    • 有几件事可以防止此类服务器被滥用:
      • 许多ISP阻止到端口25(这是邮件接收服务器的默认端口)的出站连接,以阻止此类“ 直接”邮件发送
      • 接收服务器方面采取了许多措施,主要是为了防止垃圾邮件,但是结果很可能也将阻止此类“ 直接”邮件的发送。 (一些示例是:DNSBL-被阻止的IP列表,DKIM-是一种电子邮件身份验证方法,旨在检测电子邮件中的伪造发件人地址(如果您没有自己的合法邮件服务器,将在From字段中使用其他人的域,这就是DKIM可能会打击您的域)
  5. 代码段正常。该错误很可能是由于ISP一方的阻塞而产生的。


话虽如此,代码片段:

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:25')
smtp_server.send_message(message)
smtp_server.quit()
假设smtp.server.address:25是合法服务器,并且ISP和/或smtp.server.address方面没有阻塞,

实际上会发送一封电子邮件(有关实际示例,请参见this question