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


169

这段代码有效,并向我发送了一封电子邮件:

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 2011年

您发布的缩进与文件中的缩进一样吗?
gddc 2011年

@gddc没有我就确定缩进得当,这只是我粘贴它的方式。
cloud311

我通过将函数导入主模块并传递已定义的参数来调用该函数。
cloud311 2011年

4
尽管@Arrieta建议使用电子邮件程序包是解决此问题的最佳方法,但您的方法可以奏效。两个版本之间的区别在字符串中:(1)如@NickODell所指出的,您在函数版本中具有领先的空格。标头不应有任何前导空格(除非它们被包装)。(2)除非TEXT包含前导空白行,否则您将丢失标题和正文之间的分隔符。
Tony Meyer

Answers:


191

我建议您使用标准软件包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()

正如你所看到的,头部ToMIMEText对象必须是由用逗号分隔的电子邮件地址的字符串。另一方面,该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 2011年

1
@ cloud311完全与代码中的相同。它想要用逗号分隔的字符串: ", ".join(["a@example.com", "b@example.net"])
Tim McNamara

3
还要注意,To:标头的语义与信封收件人的语义不同。例如,您可以使用““ Tony Meyer” <tony.meyer@gmail.com>作为To:标头中的地址,但信封收件人必须仅为“ tony.meyer@gmail.com”。要构建一个“不错的”收件人:地址,请使用email.utils.formataddr,例如email.utils.formataddr(“ Tony Meyer”,“ tony.meyer@gmail.com”)。
Tony Meyer

1
小的改进:该文件应该使用打开withwith open(textfile, 'rb') as fp:。可以删除显式关闭,因为with即使文件内部发生错误,该块也会关闭文件。
jpmc26 2014年

1
并非特定于此答案,但是当连接到您无法控制的任何SMTP服务器时,您应考虑其无法访问,速度慢,拒绝连接或其他任何可能性。在代码方面,您将获得一个异常,但是随后您需要找到一种方法来稍后重试发送。如果您使用自己的sendmail / postfix,那么它将为您进行重新发送。
拉尔夫·博尔顿

66

好吧,您想要一个最新的答案。

这是我的答案:

当我需要使用python进行邮件发送时,我会使用mailgun API,这让发送邮件整理得很头疼。他们有一个出色的app / api,可让您每月免费发送10,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!"})

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

希望这个对你有帮助!


3
确实,这是“迄今为止的”。虽然它使用外部API。我个人会使用yagmail之类的东西来保持内部。
PascalVKooten

6
@PascalvKooten绝对有趣的是,您会不断关注yagmail的广告(是的,先生,我下次会考虑的;先生)。但是我感到非常困惑的是,几乎没有人关心OP问题,而是提出了许多不同的解决方案。好像我在问如何在我的2009款智能车中更换灯泡,答案是:购买真正的梅赛德斯...
flaschbier

伟大的是,我的使命对某些人来说具有娱乐性。
PascalVKooten

4
@flaschbier没人关心OP问题的原因是标题错误。“如何使用Python发送电子邮件?” 是人们点击该问题时会看到的实际原因,他们期望yagmail可以提供一个答案:好而简短。你去。更多yagmail广告。
PascalVKooten

1
只是说,对于Mailgun客户,通过Web服务发送邮件比通过SMTP(特别是在使用任何附件的情况下)带宽友好得多。
拉尔夫·博尔顿

44

我想通过建议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']

哇,发送附件有多容易!这将需要20行而不使用yagmail;)

另外,如果只设置一次,则无需再次输入密码(并安全地存储密码)。在您的情况下,您可以执行以下操作:

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

更加简洁!

我邀请您浏览github或直接使用进行安装pip install yagmail


4
这应该是最佳解决方案。
理查德·里

1
OMG,这是一个非常简单的电子邮件模块。谢谢!
pepoluan

1
我可以使用yagmail除gmail以外的其他功能吗?我试图将其用于我自己的SMTP服务器。
Anirban Nag'tintinmj'17

@dtgq感谢您的关注。就个人而言,我没有看到攻击媒介。如果有人要在您要发送的路径下更改文件,那么您是否拥有Attachment课程并不重要;还是一样。如果他们可以更改您的代码,则他们无论如何都可以做他们想做的任何事情(使用root或不使用root,这与发送电子邮件一样)。在我看来,这就像典型的“方便/神奇,因此必须不太安全”。我很好奇您看到了什么真正的威胁?
PascalVKooten

14

存在压痕问题。下面的代码将起作用:

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传递给函数的变量是用户凭据。
用户

不建议通过简单的字符串插值将有效的SMTP消息手动拼凑在一起,并且您的答案中有一个错误可以完美地说明这一点。(主体必须以空行开头,这才是有效的消息。)对于任何涉及字符集的内容,除了1980年代的纯文本以外,其他所有其他内容都包含7位纯英文US-ASCII(附件,国际化和其他MIME支持),我真的想使用一个处理这些东西的库。尽管Python email库仍需要您在做什么,但它做得相当好(尤其是从3.6版开始)。
6

7

这是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网状邮箱,则需要设置“客户端授权密码”,如下所示:

在此处输入图片说明

参考:https : //stackoverflow.com/a/41470149/2803344 https://docs.python.org/3/library/email.examples.html#email-examples


4

在函数中缩进代码(可以)时,您也缩进了原始消息字符串的行。但是前导空格意味着标题行的折叠(串联),如RFC 2822-Internet消息格式的 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:and 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放在函数外部。


3

可能是在您的消息中添加了标签。打印出消息,然后再将其传递给sendMail。


1

确保您已授予发件人和收件人的权限,以发送电子邮件和接收来自电子邮件帐户中未知来源(外部来源)的电子邮件。

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)

在此处输入图片说明


msg不是有效的SMTP邮件,并且如果您的邮件服务器接受它,它将简单地消失在以太中。
6

0

自从我刚弄清楚这是怎么回事以来,我以为我在这里输入了两位。

看来您在SERVER连接设置上没有指定端口,当我尝试连接到不使用默认端口25的SMTP服务器时,这对我有一点影响。

根据smtplib.SMTP文档,应该自动处理您的ehlo或helo请求/响应,因此您不必担心这一点(但是可以用来确认其他所有操作是否失败)。

另一个要问自己的问题是,您是否允许SMTP服务器本身上的SMTP连接?对于某些网站,例如GMAIL和ZOHO,您必须实际进入并激活电子邮件帐户中的IMAP连接。您的邮件服务器可能不允许不来自“ localhost”的SMTP连接?需要研究的东西。

最后一件事是您可能想尝试在TLS上启动连接。现在,大多数服务器都需要这种身份验证。

您会看到我在电子邮件中塞入了两个“收件人”字段。msg ['TO']和msg ['FROM'] msg词典项目允许在电子邮件本身的标题中显示正确的信息,您可以在“收件人” /“发件人”字段中在电子邮件的接收端看到该标题(您可以甚至可以在此处添加“答复”字段。“收件人”和“发件人”字段本身就是服务器所需要的。

这是我在一个函数中使用的代码,它对我有用,它使用本地计算机和远程SMTP服务器(如所示的ZOHO)通过电子邮件发送* .txt文件的内容:

def emailResults(folder, filename):

    # body of the message
    doc = folder + filename + '.txt'
    with open(doc, 'r') as readText:
        msg = MIMEText(readText.read())

    # headers
    TO = 'to_user@domain.com'
    msg['To'] = TO
    FROM = 'from_user@domain.com'
    msg['From'] = FROM
    msg['Subject'] = 'email subject |' + filename

    # SMTP
    send = smtplib.SMTP('smtp.zoho.com', 587)
    send.starttls()
    send.login('from_user@domain.com', 'password')
    send.sendmail(FROM, TO, msg.as_string())
    send.quit()

0

值得注意的是,SMTP模块支持上下文管理器,因此不需要手动调用quit(),这将确保即使有异常也总是调用它。

    with smtplib.SMTP_SSL('smtp.gmail.com', 465) as server:
        server.ehlo()
        server.login(user, password)
        server.sendmail(from, to, body)

-1

就您的代码而言,似乎没有什么根本上的错误,只是不清楚,您实际上是如何调用该函数的。我能想到的是,当服务器没有响应时,您将收到此SMTPServerDisconnected错误。如果您在smtplib(以下摘录)中查找getreply()函数,您将有所了解。

def getreply(self):
    """Get a reply from the server.

    Returns a tuple consisting of:

      - server response code (e.g. '250', or such, if all goes well)
        Note: returns -1 if it can't read response code.

      - server response string corresponding to response code (multiline
        responses are converted to a single, multiline string).

    Raises SMTPServerDisconnected if end-of-file is reached.
    """

请查看https://github.com/rreddy80/sendEmails/blob/master/sendEmailAttachments.py上的示例,如果您正在尝试这样做,该示例也使用函数调用来发送电子邮件(DRY方法)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.