如何使用Python发送电子邮件?

254

这段代码可以正常工作,并成功地向我发送电子邮件:

import smtplib
#SERVER = "localhost"

FROM = 'monty@python.com'

TO = ["jon@mycompany.com"] # must be a list

SUBJECT = "Hello!"

TEXT = "This message was sent with Python's smtplib."

# Prepare actual message

message = """\
From: %s
To: %s
Subject: %s

%s
""" % (FROM, ", ".join(TO), SUBJECT, TEXT)

# Send the mail

server = smtplib.SMTP('myserver')
server.sendmail(FROM, TO, message)
server.quit()

然而,如果我试图像这样将其包装在函数中:

def sendMail(FROM,TO,SUBJECT,TEXT,SERVER):
    import smtplib
    """this is some test documentation in the function"""
    message = """\
        From: %s
        To: %s
        Subject: %s
        %s
        """ % (FROM, ", ".join(TO), SUBJECT, TEXT)
    # Send the mail
    server = smtplib.SMTP(SERVER)
    server.sendmail(FROM, TO, message)
    server.quit()

当我调用它时,会得到以下错误:

 Traceback (most recent call last):
  File "C:/Python31/mailtest1.py", line 8, in <module>
    sendmail.sendMail(sender,recipients,subject,body,server)
  File "C:/Python31\sendmail.py", line 13, in sendMail
    server.sendmail(FROM, TO, message)
  File "C:\Python31\lib\smtplib.py", line 720, in sendmail
    self.rset()
  File "C:\Python31\lib\smtplib.py", line 444, in rset
    return self.docmd("rset")
  File "C:\Python31\lib\smtplib.py", line 368, in docmd
    return self.getreply()
  File "C:\Python31\lib\smtplib.py", line 345, in getreply
    raise SMTPServerDisconnected("Connection unexpectedly closed")
smtplib.SMTPServerDisconnected: Connection unexpectedly closed

有谁可以帮我理解为什么这样?


2
你怎么调用这个函数? - Jochen Ritzel
你发布的缩进是否与你文件中的相同? - g.d.d.c
@g.d.d.c 不是的,我确保正确缩进了,那只是我粘贴时的方式。 - cloud311
我通过将函数导入到我的主模块并将我定义的参数传递给它来调用该函数。 - cloud311
5
尽管 @Arrieta 建议使用邮件包是解决此问题的最佳方式,但你的方法也可以工作。你两个版本之间的区别在于字符串:(1) 如 @NickODell 所指出的,在函数版本中你有前导空格。头部不应该有前导空格(除非它们被换行)。 (2) 除非 TEXT 包括一个前导空行,否则您已经丢失了标题和正文之间的分隔符。 - Tony Meyer
gmail使用Python作为提供者发送电子邮件的方法:https://dev59.com/HWkw5IYBdhLWcg3wGGm8 - Ciro Santilli OurBigBook.com
19个回答

261
我建议您使用标准的包emailsmtplib来一起发送电子邮件。请参考以下示例(摘自Python文档)。请注意,如果您按照这种方法进行操作,“简单”的任务确实很简单,而更复杂的任务(例如附加二进制对象或发送纯文本/HTML多部分消息)可以非常快速地完成。
# Import smtplib for the actual sending function
import smtplib

# Import the email modules we'll need
from email.mime.text import MIMEText

# Open a plain text file for reading.  For this example, assume that
# the text file contains only ASCII characters.
with open(textfile, 'rb') as fp:
    # Create a text/plain message
    msg = MIMEText(fp.read())

# me == the sender's email address
# you == the recipient's email address
msg['Subject'] = 'The contents of %s' % textfile
msg['From'] = me
msg['To'] = you

# Send the message via our own SMTP server, but don't include the
# envelope header.
s = smtplib.SMTP('localhost')
s.sendmail(me, [you], msg.as_string())
s.quit()

如果要发送邮件给多个收件人,您也可以参考Python文档中的示例:

# Import smtplib for the actual sending function
import smtplib

# Here are the email package modules we'll need
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart

# Create the container (outer) email message.
msg = MIMEMultipart()
msg['Subject'] = 'Our family reunion'
# me == the sender's email address
# family = the list of all recipients' email addresses
msg['From'] = me
msg['To'] = ', '.join(family)
msg.preamble = 'Our family reunion'

# Assume we know that the image files are all in PNG format
for file in pngfiles:
    # Open the files in binary mode.  Let the MIMEImage class automatically
    # guess the specific image type.
    with open(file, 'rb') as fp:
        img = MIMEImage(fp.read())
    msg.attach(img)

# Send the email via our own SMTP server.
s = smtplib.SMTP('localhost')
s.sendmail(me, family, msg.as_string())
s.quit()

如您所见,MIMEText对象中的头部“To”必须是由逗号分隔的电子邮件地址组成的字符串。另一方面,sendmail函数的第二个参数必须是字符串列表(每个字符串都是一个电子邮件地址)。

因此,如果您有三个电子邮件地址:person1@example.comperson2@example.comperson3@example.com,则可以按照以下方式操作(显然的部分省略):

to = ["person1@example.com", "person2@example.com", "person3@example.com"]
msg['To'] = ",".join(to)
s.sendmail(me, to, msg.as_string())

",".join(to)这部分将列表中的元素拼接为一个字符串,每个元素用逗号分隔。

从您的问题中我了解到您还没有阅读过Python教程 - 这是必须要做的,如果您想在Python中有所进展,标准库的文档大多数都非常好。


1
谢谢,这个在函数内部非常好用。但是我该如何发送给多个收件人呢?因为msg[to]看起来像是一个字典键,我尝试用分号将msg[to]分开,但似乎不起作用。 - cloud311
5
请注意,To: 头部与信封收件人有不同的语义。例如,您可以在 To: 头部中使用 '"Tony Meyer" tony.meyer@gmail.com' 作为地址,但是信封收件人必须只能是 "tony.meyer@gmail.com"。为构建一个“良好”的 To: 地址,请使用 email.utils.formataddr,例如 email.utils.formataddr("Tony Meyer", "tony.meyer@gmail.com")。 - Tony Meyer
2
小改进:应该使用 with 打开文件:with open(textfile, 'rb') as fp:。显式关闭可以省略,因为即使在其中发生错误,with 块也会关闭文件。 - jpmc26
1
@cloud311 我不知道这是否有帮助,但我在一个类似的问题中看到 OP 的函数参数是 recipient,对应的变量看起来像这样:TO = recipient if type(recipient) is list else [recipient]... 这样,如果你选择将收件人列表放入参数中,它会将收件人列表作为收件人发送到服务器。 - Musixauce3000
1
不仅限于此回答,但当连接到任何您无法控制的SMTP服务器时,您应该考虑它可能无法访问、速度慢、拒绝连接或其他情况。从代码上来说,您会收到一个异常,但随后需要找到一种方法稍后重新尝试发送。如果与自己的sendmail/postfix通信,则它将为您处理重新发送。 - Ralph Bolton
显示剩余4条评论

109

当我需要在Python中发送电子邮件时,我使用Mailgun API,它可以解决许多发送邮件的麻烦。他们有一个很棒的应用程序/ API,允许您每月免费发送5,000封电子邮件。

发送电子邮件的方式如下:

def send_simple_message():
    return requests.post(
        "https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages",
        auth=("api", "YOUR_API_KEY"),
        data={"from": "Excited User <mailgun@YOUR_DOMAIN_NAME>",
              "to": ["bar@example.com", "YOU@YOUR_DOMAIN_NAME"],
              "subject": "Hello",
              "text": "Testing some Mailgun awesomness!"})

您还可以跟踪事件等等,详情请参阅快速入门指南


22
@PascalvKooten 经常为 yagmail 做广告,让人感到非常有趣(是的,先生,下次我会考虑使用它)。但是我发现几乎没有人关心提问者的问题,而是建议很多不同的解决方案。这就好像我在问如何更换我 2009 年款智能车的灯泡,但回答却是:买一辆真正的奔驰车... - flaschbier
1
很棒,我的任务对某些人来说具有一定的娱乐价值。 - PascalVKooten
10
没有人关注楼主的问题,是因为标题错误了。当人们点击这个问题时,实际上是想知道如何使用Python发送电子邮件,他们期望获得一些简洁明了的答案,例如yagmail提供的。以上就是更多的yagmail广告。 - PascalVKooten
啊,我明白了,现在无法建议任何编辑,因为问题被拒绝为“代码过多”:( 真遗憾。 - flaschbier
1
只是想说,对于Mailgun的客户来说,通过Web服务发送邮件比通过SMTP发送(尤其是使用任何附件)更加节省带宽。 - Ralph Bolton
显示剩余3条评论

60

我想通过建议使用yagmail程序包来帮助您发送电子邮件(我是维护者,很抱歉打广告,但我认为它可以真正帮助!)。

您需要的完整代码如下:

import yagmail
yag = yagmail.SMTP(FROM, 'pass')
yag.send(TO, SUBJECT, TEXT)
请注意,我为所有参数提供默认值,例如如果您想要发送给自己,可以省略TO,如果您不想要主题,也可以省略它。
此外,目标还是使附加HTML代码或图像(和其他文件)变得非常容易。
您可以在放置内容的位置执行以下操作:
contents = ['Body text, and here is an embedded image:', 'http://somedomain/image.png',            'You can also find an audio file attached.', '/local/path/song.mp3']

哇,使用yagmail发送附件太容易了!如果没有yagmail,需要写20行代码才能实现这个功能;)

而且,如果你成功设置一次,就再也不必输入密码了(并且可以安全地存储密码)。在你的情况下,你可以这样做:

import yagmail
yagmail.SMTP().send(contents = contents)

这样更加简洁!

我邀请您查看GitHub或直接使用pip install yagmail进行安装。


2
我可以在除了 Gmail 以外的邮件服务器上使用 yagmail 吗?我想要用它来连接我的 SMTP 服务器。 - Anirban Nag 'tintinmj'
@dtgq 感谢您的关注。就个人而言,我并没有看到攻击向量。如果有人要更改您想要发送的路径下的文件,那么是否具有 Attachment 类并不重要;这仍然是同样的事情。如果他们可以更改您的代码,则无论如何都可以为所欲为(具/不具备 root 权限,邮件发送方面都是相同的)。对我来说,这似乎是典型的“它很方便/神奇,因此它一定不太安全”。我很好奇您认为存在什么真正的威胁? - PascalVKooten
1
这仅适用于Gmail用户,也就是说对于几乎任何专业用途都毫无用处。 - bugmenot123

21

这里是一个关于Python 3.x 的例子,比2.x 简单得多:

import smtplib
from email.message import EmailMessage
def send_mail(to_email, subject, message, server='smtp.example.cn',
              from_email='xx@example.com'):
    # import smtplib
    msg = EmailMessage()
    msg['Subject'] = subject
    msg['From'] = from_email
    msg['To'] = ', '.join(to_email)
    msg.set_content(message)
    print(msg)
    server = smtplib.SMTP(server)
    server.set_debuglevel(1)
    server.login(from_email, 'password')  # user & password
    server.send_message(msg)
    server.quit()
    print('successfully sent the mail.')

调用这个函数:

send_mail(to_email=['12345@qq.com', '12345@126.com'],
          subject='hello', message='Your analysis has done!')

以下内容仅适用于中国用户:

如果您使用126/163网易邮箱,则需要设置“客户端授权密码”,如下所示:

enter image description here

参考资料:https://dev59.com/GZ3ha4cB1Zd3GeqPbObK#41470149 https://docs.python.org/3/library/email.examples.html#email-examples


2
未来的读者注意:还有一个名为SMTP_SSL的类,并且可能需要设置一个port关键字参数。如果服务器需要SSL,则未加密版本将会挂起。 - Oskar Skog
我喜欢这个版本。我根据这个版本建立了我的示例。 - undefined

18

存在缩进问题。下面的代码将起作用:

import textwrap

def sendMail(FROM,TO,SUBJECT,TEXT,SERVER):
    import smtplib
    """this is some test documentation in the function"""
    message = textwrap.dedent("""\
        From: %s
        To: %s
        Subject: %s
        %s
        """ % (FROM, ", ".join(TO), SUBJECT, TEXT))
    # Send the mail
    server = smtplib.SMTP(SERVER)
    server.sendmail(FROM, TO, message)
    server.quit()


3
@geotheory传递到函数中的SERVER变量是用户凭据。 - User
5
手动使用简单字符串插值拼接有效的SMTP消息不被推荐。你的答案有一个漏洞,这完美地说明了这一点。(为了使其成为有效的消息,正文需要以空行开头。)对于任何涉及字符集(如附件、国际化和其他MIME支持)的内容,而不仅仅是1980年代的7位纯文本英语-US-ASCII,你真的需要使用处理这些内容的库。Python的email库在这方面做得相当不错(特别是自3.6版发布以来),尽管它仍需要对你要做什么有一定的了解。 - tripleee

8
在函数中缩进代码是可以的,但您还缩进了原始消息字符串的行。然而,前导空格意味着折叠(连接)标题行,如RFC 2822 - Internet Message Format的2.2.3和3.2.3节所述:
每个头字段在逻辑上都是由字段名称、冒号和字段主体组成的单行字符。然而,为了方便处理每行998/78个字符的限制,头字段的主体部分可以拆分为多行表示; 这称为“折叠”。
在您的sendmail调用的函数形式中,所有行都以空格开头,因此被“展开”(连接),您正在尝试发送
From: monty@python.com    To: jon@mycompany.com    Subject: Hello!    This message was sent with Python's smtplib.

除了我们想象的那样,smtplib 不再支持理解 To:Subject: 头信息,因为这些名称只在行首被识别。相反,smtplib 会认为发送者电子邮件地址非常长:
monty@python.com    To: jon@mycompany.com    Subject: Hello!    This message was sent with Python's smtplib.

这样做是行不通的,因此会出现异常。

解决方案很简单:只需保留 message 字符串与其原本相同即可。可以通过函数(正如 Zeeshan 建议的那样)或直接在源代码中完成:

import smtplib

def sendMail(FROM,TO,SUBJECT,TEXT,SERVER):
    """this is some test documentation in the function"""
    message = """\
From: %s
To: %s
Subject: %s

%s
""" % (FROM, ", ".join(TO), SUBJECT, TEXT)
    # Send the mail
    server = smtplib.SMTP(SERVER)
    server.sendmail(FROM, TO, message)
    server.quit()

现在展开不会发生,您可以发送。
From: monty@python.com
To: jon@mycompany.com
Subject: Hello!

This message was sent with Python's smtplib.

这就是你的旧代码所实现的功能和操作。

请注意,我还保留了标题和正文之间的空白行,以适应RFC第3.5节的要求(必须保留),并根据Python风格指南PEP-0008(可选)将include函数放在了函数外面。


2
请注意:本帖子的价值之一在于对SMTP协议工作原理的细致解释。 - SunSplat

7

请确保您已经授权发件人和收件人在电子邮件账户中发送并接收来自未知来源(外部来源)的邮件。

import smtplib

#Ports 465 and 587 are intended for email client to email server communication - sending email
server = smtplib.SMTP('smtp.gmail.com', 587)

#starttls() is a way to take an existing insecure connection and upgrade it to a secure connection using SSL/TLS.
server.starttls()

#Next, log in to the server
server.login("#email", "#password")

msg = "Hello! This Message was sent by the help of Python"

#Send the mail
server.sendmail("#Sender", "#Reciever", msg)

1
“msg” 不是有效的 SMTP 消息,如果您的邮件服务器接受它,它将只会消失在虚无之中。 - tripleee
1
基本上请参见 https://stackoverflow.com/questions/55077603/getting-a-blank-email/55078455#55078455 - tripleee

3

可能是将制表符放入您的消息中。在将消息传递给sendMail之前,请将其打印出来。


3
值得注意的是,SMTP模块支持上下文管理器,因此无需手动调用quit()方法,这将确保即使出现异常也始终会被调用。
    with smtplib.SMTP_SSL('smtp.gmail.com', 465) as server:
        server.ehlo()
        server.login(user, password)
        server.sendmail(from, to, body)

2
我不满意现有的发送电子邮件的软件包选项,所以我决定自己制作并开源我的邮件发送器。它易于使用,可以处理高级用例。
安装步骤:
pip install redmail

使用方法:

from redmail import EmailSender
email = EmailSender(
    host="<SMTP HOST ADDRESS>",
    port=<PORT NUMBER>,
)

email.send(
    sender="me@example.com",
    receivers=["you@example.com"],
    subject="An example email",
    text="Hi, this is text body.",
    html="<h1>Hi,</h1><p>this is HTML body</p>"
)

如果您的服务器需要用户名和密码,请将user_namepassword传递给EmailSender
我在send方法中包含了许多功能:
  • 包括附件
  • 直接将图片包含在HTML正文中
  • Jinja模板
  • 开箱即用的美化HTML表格
文档: https://red-mail.readthedocs.io/en/latest/ 源代码:https://github.com/Miksus/red-mail

最终赢家。热门!! - Lenn Dolling
是否支持TLS? - Amit Naidu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接