如何发送电子邮件附件?

How to send email attachments?

提问人:Richard 提问时间:7/29/2010 最后编辑:martineauRichard 更新时间:4/13/2023 访问量:578266

问:

我在了解如何使用 Python 通过电子邮件发送附件时遇到问题。我已成功通过电子邮件发送了带有 .有人可以解释一下如何在电子邮件中发送附件。我知道网上还有其他帖子,但作为 Python 初学者,我发现它们很难理解。smtplib

Python 电子邮件

评论

6赞 AdrianBR 2/9/2016
这是一个简单的实现,可以附加多个文件,甚至可以在要嵌入的图像的情况下引用它们。datamakessense.com/......
0赞 Hinchy 11/29/2019
我发现这很有用 drupal.org/project/mimemail/issues/911612 事实证明图像附件需要附加到相关类型的子部件。如果将图像附加到根 MIME 部件,则图像可以显示在附加的项目列表中,并在 outlook365 等客户端中预览。

答:

29赞 Oli 7/29/2010 #1
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
from email.MIMEImage import MIMEImage
import smtplib

msg = MIMEMultipart()
msg.attach(MIMEText(file("text.txt").read()))
msg.attach(MIMEImage(file("image.png").read()))

# to send
mailer = smtplib.SMTP()
mailer.connect()
mailer.sendmail(from_, to, msg.as_string())
mailer.close()

改编自这里

评论

0赞 Richard 7/29/2010
不完全是我要找的。该文件作为电子邮件的正文发送。第 6 行和第 7 行也缺少括号。不过,我觉得我们越来越近了
2赞 Katriel 7/29/2010
电子邮件是纯文本,这就是支持。若要发送附件,请将其编码为 MIME 邮件,然后以纯文本电子邮件的形式发送。不过,有一个新的 python 电子邮件模块:docs.python.org/library/email.mime.htmlsmtplib
0赞 Richard 7/29/2010
@katrienlalex一个工作的例子将大大有助于我的理解
1赞 Katriel 7/29/2010
你确定上面的例子不起作用吗?我手边没有 SMTP 服务器,但我看了一下,它看起来确实像是 MIME 多部分电子邮件的正文。维基百科解释MIME:en.wikipedia.org/wiki/MIMEmsg.as_string()
1赞 Benjamin2002 8/10/2018
Line 6, in <module> msg.attach(MIMEText(file("text.txt").read())) NameError: name 'file' is not defined
531赞 Oli 7/29/2010 #2

这是另一个:

import smtplib
from os.path import basename
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.utils import COMMASPACE, formatdate


def send_mail(send_from, send_to, subject, text, files=None,
              server="127.0.0.1"):
    assert isinstance(send_to, list)

    msg = MIMEMultipart()
    msg['From'] = send_from
    msg['To'] = COMMASPACE.join(send_to)
    msg['Date'] = formatdate(localtime=True)
    msg['Subject'] = subject

    msg.attach(MIMEText(text))

    for f in files or []:
        with open(f, "rb") as fil:
            part = MIMEApplication(
                fil.read(),
                Name=basename(f)
            )
        # After the file is closed
        part['Content-Disposition'] = 'attachment; filename="%s"' % basename(f)
        msg.attach(part)


    smtp = smtplib.SMTP(server)
    smtp.sendmail(send_from, send_to, msg.as_string())
    smtp.close()

这与第一个示例大致相同......但它应该更容易进入。

评论

10赞 Gringo Suave 3/22/2011
小心可变默认值:stackoverflow.com/questions/101268/hidden-features-of-python/...
11赞 Oli 5/17/2011
@user589983 为什么不像这里的任何其他用户一样建议编辑呢?我已将剩余的引用更改为 .filef
12赞 gecco 11/11/2011
Python3 开发者须知:模块“email.Utils“已重命名为”email.utils”
8赞 mata 7/3/2013
对于 python2.5+,改用 MIMEApplication 更容易 - 将循环的前三行减少为:part = MIMEApplication(open(f, 'rb').read())
5赞 Luke 10/30/2015
主题未显示在发送的电子邮件中。仅在将行更改为 msg['Subject']=subject 后才有效。我使用 python 2.7。
76赞 Richard 7/29/2010 #3

这是我最终使用的代码:

import smtplib
from email.MIMEMultipart import MIMEMultipart
from email.MIMEBase import MIMEBase
from email import Encoders


SUBJECT = "Email Data"

msg = MIMEMultipart()
msg['Subject'] = SUBJECT 
msg['From'] = self.EMAIL_FROM
msg['To'] = ', '.join(self.EMAIL_TO)

part = MIMEBase('application', "octet-stream")
part.set_payload(open("text.txt", "rb").read())
Encoders.encode_base64(part)
    
part.add_header('Content-Disposition', 'attachment; filename="text.txt"')

msg.attach(part)

server = smtplib.SMTP(self.EMAIL_SERVER)
server.sendmail(self.EMAIL_FROM, self.EMAIL_TO, msg.as_string())

代码与 Oli 的帖子大致相同。

基于二进制文件电子邮件附件问题的代码帖子。

评论

3赞 Steven Bluen 3/18/2015
好答案。如果它还包含添加示例正文文本的代码,那就太好了。
8赞 Varun Balupuri 5/10/2019
请注意,在电子邮件库的现代版本中 - 模块导入是不同的。例如:from email.mime.base import MIMEBase
0赞 nam 3/31/2022
在 Windows 上使用 python3.10 我有这个错误AttributeError: 'MIMEMultipart' object has no attribute 'encode'
0赞 tripleee 9/2/2022
不要将其用于 Python 3.10 或通常高于 3.6 的任何版本。
111赞 Ehsan Iran-Nejad 5/13/2013 #4

这是 python 3 的修改版本Oli

import smtplib
from pathlib import Path
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email.utils import COMMASPACE, formatdate
from email import encoders


def send_mail(send_from, send_to, subject, message, files=[],
              server="localhost", port=587, username='', password='',
              use_tls=True):
    """Compose and send email with provided info and attachments.

    Args:
        send_from (str): from name
        send_to (list[str]): to name(s)
        subject (str): message title
        message (str): message body
        files (list[str]): list of file paths to be attached to email
        server (str): mail server host name
        port (int): port number
        username (str): server auth username
        password (str): server auth password
        use_tls (bool): use TLS mode
    """
    msg = MIMEMultipart()
    msg['From'] = send_from
    msg['To'] = COMMASPACE.join(send_to)
    msg['Date'] = formatdate(localtime=True)
    msg['Subject'] = subject

    msg.attach(MIMEText(message))

    for path in files:
        part = MIMEBase('application', "octet-stream")
        with open(path, 'rb') as file:
            part.set_payload(file.read())
        encoders.encode_base64(part)
        part.add_header('Content-Disposition',
                        'attachment; filename={}'.format(Path(path).name))
        msg.attach(part)

    smtp = smtplib.SMTP(server, port)
    if use_tls:
        smtp.starttls()
    smtp.login(username, password)
    smtp.sendmail(send_from, send_to, msg.as_string())
    smtp.quit()

评论

1赞 Subin 12/10/2018
send_to应该是 list[str]
2赞 AleAve81 4/30/2020
对我来说最好的答案,但有一个小错误:替换为import pathlibfrom pathlib import Path
1赞 ChiPlusPlus 6/20/2021
到目前为止最好的答案。这应该是问题的解决方案。
1赞 Ehsan Iran-Nejad 7/22/2021
@RubenFlam牧羊人谢谢。我更新了答案并删除了引号
1赞 tripleee 5/1/2022
Pyhon 标准库中的模块在 Python 3.6 中进行了大修,使其更加合乎逻辑、通用且简洁;新代码应该以(不再是)新 API 为目标。可能扔掉这些代码,然后从 Python 电子邮件示例文档中的新式代码重新开始。emailEmailMessage
6赞 Andrade 2/2/2017 #5

我能得到的最简单的代码是:

#for attachment email
from django.core.mail import EmailMessage

    def attachment_email(request):
            email = EmailMessage(
            'Hello', #subject
            'Body goes here', #body
            '[email protected]', #from
            ['[email protected]'], #to
            ['[email protected]'], #bcc
            reply_to=['[email protected]'],
            headers={'Message-ID': 'foo'},
            )

            email.attach_file('/my/path/file')
            email.send()

它基于官方的 Django 文档

评论

3赞 comte 5/30/2017
在你的情况下,你必须安装Django来发送电子邮件......它没有正确回答问题
1赞 Auspex 11/1/2017
@comte '因为 python 只用于 Django,对吧?
6赞 comte 11/2/2017
@Auspex这就是我的观点;-)这就像安装 LibreOffice 来编辑配置文件一样......
1赞 3pitt 9/19/2019
我觉得这很有帮助和信息量很大。只导入了一个模块,与其他人跳过的 MIME 箍相比,它的使用非常简单和优雅。相比之下,在您的示例中,LibreOffice 比记事本更难使用。
0赞 Ian Smith 11/30/2021
这太愚蠢了,这不是本地的做法。
5赞 Antony Fuentes 4/28/2018 #6

其他答案也很好,但我仍然想分享一种不同的方法,以防有人正在寻找替代方案。

这里的主要区别在于,使用这种方法,您可以使用 HTML/CSS 来格式化您的消息,因此您可以发挥创意并为您的电子邮件提供一些样式。虽然没有强制使用 HTML,但您仍然可以只使用纯文本。

请注意,此函数接受将电子邮件发送给多个收件人,并且还允许附加多个文件。

我只在 Python 2 上尝试过这个,但我认为它应该在 3 上也能正常工作:

import os.path
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication

def send_email(subject, message, from_email, to_email=[], attachment=[]):
    """
    :param subject: email subject
    :param message: Body content of the email (string), can be HTML/CSS or plain text
    :param from_email: Email address from where the email is sent
    :param to_email: List of email recipients, example: ["[email protected]", "[email protected]"]
    :param attachment: List of attachments, exmaple: ["file1.txt", "file2.txt"]
    """
    msg = MIMEMultipart()
    msg['Subject'] = subject
    msg['From'] = from_email
    msg['To'] = ", ".join(to_email)
    msg.attach(MIMEText(message, 'html'))

    for f in attachment:
        with open(f, 'rb') as a_file:
            basename = os.path.basename(f)
            part = MIMEApplication(a_file.read(), Name=basename)

        part['Content-Disposition'] = 'attachment; filename="%s"' % basename
        msg.attach(part)

    email = smtplib.SMTP('your-smtp-host-name.com')
    email.sendmail(from_email, to_email, msg.as_string())

我希望这会有所帮助!:-)

11赞 Ferrarezi 6/15/2018 #7

Gmail 版本,使用 Python 3.6(请注意,您需要更改 Gmail 设置才能通过 smtp 发送电子邮件:

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
from os.path import basename


def send_mail(send_from: str, subject: str, text: str, 
send_to: list, files= None):

    send_to= default_address if not send_to else send_to

    msg = MIMEMultipart()
    msg['From'] = send_from
    msg['To'] = ', '.join(send_to)  
    msg['Subject'] = subject

    msg.attach(MIMEText(text))

    for f in files or []:
        with open(f, "rb") as fil: 
            ext = f.split('.')[-1:]
            attachedfile = MIMEApplication(fil.read(), _subtype = ext)
            attachedfile.add_header(
                'content-disposition', 'attachment', filename=basename(f) )
        msg.attach(attachedfile)


    smtp = smtplib.SMTP(host="smtp.gmail.com", port= 587) 
    smtp.starttls()
    smtp.login(username,password)
    smtp.sendmail(send_from, send_to, msg.as_string())
    smtp.close()

用法:

username = '[email protected]'
password = 'top-secret'
default_address = ['[email protected]'] 

send_mail(send_from= username,
subject="test",
text="text",
send_to= None,
files= # pass a list with the full filepaths here...
)

要与任何其他电子邮件提供商一起使用,只需更改 smtp 配置即可。

13赞 Sudarshan 6/25/2018 #8

python 3 的另一种方式(如果有人在搜索):

import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders

fromaddr = "sender mail address"
toaddr = "receiver mail address"

msg = MIMEMultipart()

msg['From'] = fromaddr
msg['To'] = toaddr
msg['Subject'] = "SUBJECT OF THE EMAIL"

body = "TEXT YOU WANT TO SEND"

msg.attach(MIMEText(body, 'plain'))

filename = "fileName"
attachment = open("path of file", "rb")

part = MIMEBase('application', 'octet-stream')
part.set_payload((attachment).read())
encoders.encode_base64(part)
part.add_header('Content-Disposition', "attachment; filename= %s" % filename)

msg.attach(part)

server = smtplib.SMTP('smtp.gmail.com', 587)
server.starttls()
server.login(fromaddr, "sender mail password")
text = msg.as_string()
server.sendmail(fromaddr, toaddr, text)
server.quit()

确保允许在您的 Gmail 帐户中使用“安全性较低的应用

评论

0赞 ApJo 9/14/2023
感谢您@Sudarshan回答,但是可以是相对的/绝对的?它需要遵循一种模式吗?有人可以提供有关此的更多信息吗?"path of file"
0赞 TonyRyan 10/14/2018 #9

以下是我从 SoccerPlayer 的帖子中找到的内容的组合 这里 和以下链接,使我更容易附加 xlsx 文件。在这里找到

file = 'File.xlsx'
username=''
password=''
send_from = ''
send_to = 'recipient1 , recipient2'
Cc = 'recipient'
msg = MIMEMultipart()
msg['From'] = send_from
msg['To'] = send_to
msg['Cc'] = Cc
msg['Date'] = formatdate(localtime = True)
msg['Subject'] = ''
server = smtplib.SMTP('smtp.gmail.com')
port = '587'
fp = open(file, 'rb')
part = MIMEBase('application','vnd.ms-excel')
part.set_payload(fp.read())
fp.close()
encoders.encode_base64(part)
part.add_header('Content-Disposition', 'attachment', filename='Name File Here')
msg.attach(part)
smtp = smtplib.SMTP('smtp.gmail.com')
smtp.ehlo()
smtp.starttls()
smtp.login(username,password)
smtp.sendmail(send_from, send_to.split(',') + msg['Cc'].split(','), msg.as_string())
smtp.quit()
2赞 sdoshi 10/21/2018 #10
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import smtplib
import mimetypes
import email.mime.application

smtp_ssl_host = 'smtp.gmail.com'  # smtp.mail.yahoo.com
smtp_ssl_port = 465
s = smtplib.SMTP_SSL(smtp_ssl_host, smtp_ssl_port)
s.login(email_user, email_pass)


msg = MIMEMultipart()
msg['Subject'] = 'I have a picture'
msg['From'] = email_user
msg['To'] = email_user

txt = MIMEText('I just bought a new camera.')
msg.attach(txt)

filename = 'introduction-to-algorithms-3rd-edition-sep-2010.pdf' #path to file
fo=open(filename,'rb')
attach = email.mime.application.MIMEApplication(fo.read(),_subtype="pdf")
fo.close()
attach.add_header('Content-Disposition','attachment',filename=filename)
msg.attach(attach)
s.send_message(msg)
s.quit()

对于解释,您可以使用此链接进行正确解释 https://medium.com/@sdoshi579/to-send-an-email-along-with-attachment-using-smtp-7852e77623

2赞 Abdul Haseeb 1/3/2019 #11
from email.mime.multipart import MIMEMultipart
from email.mime.image import MIMEImage
from email.mime.text import MIMEText
import smtplib

msg = MIMEMultipart()

password = "password"
msg['From'] = "from_address"
msg['To'] = "to_address"
msg['Subject'] = "Attached Photo"
msg.attach(MIMEImage(file("abc.jpg").read()))
file = "file path"
fp = open(file, 'rb')
img = MIMEImage(fp.read())
fp.close()
msg.attach(img)
server = smtplib.SMTP('smtp.gmail.com: 587')
server.starttls()
server.login(msg['From'], password)
server.sendmail(msg['From'], msg['To'], msg.as_string())
server.quit()

评论

4赞 Ali 1/3/2019
嗨,欢迎,请在回答问题时始终发布您的答案解释,以便更好地理解
0赞 John Rua 12/12/2019 #12

使用我的代码,您可以使用 gmail 发送电子邮件附件,您需要:

设置您的 gmail 地址 设置您的 gmail 帐户密码
在 在您需要
设置目标电子邮件地址的部分中。
是主题。
是身体。
是图像附件。
___YOUR SMTP EMAIL HERE_____YOUR SMTP PASSWORD HERE______EMAIL TO RECEIVE THE MESSAGE__Alarm notificationSomeone has entered the room, picture attached["/home/pi/webcam.jpg"]

以下是完整代码:

#!/usr/bin/env python
import smtplib
from email.MIMEMultipart import MIMEMultipart
from email.MIMEBase import MIMEBase
from email.MIMEText import MIMEText
from email.Utils import COMMASPACE, formatdate
from email import Encoders
import os

USERNAME = "___YOUR SMTP EMAIL HERE___"
PASSWORD = "__YOUR SMTP PASSWORD HERE___"

def sendMail(to, subject, text, files=[]):
    assert type(to)==list
    assert type(files)==list

    msg = MIMEMultipart()
    msg['From'] = USERNAME
    msg['To'] = COMMASPACE.join(to)
    msg['Date'] = formatdate(localtime=True)
    msg['Subject'] = subject

    msg.attach( MIMEText(text) )

    for file in files:
        part = MIMEBase('application', "octet-stream")
        part.set_payload( open(file,"rb").read() )
        Encoders.encode_base64(part)
        part.add_header('Content-Disposition', 'attachment; filename="%s"'
                       % os.path.basename(file))
        msg.attach(part)

    server = smtplib.SMTP('smtp.gmail.com:587')
    server.ehlo_or_helo_if_needed()
    server.starttls()
    server.ehlo_or_helo_if_needed()
    server.login(USERNAME,PASSWORD)
    server.sendmail(USERNAME, to, msg.as_string())
    server.quit()

sendMail( ["___EMAIL TO RECEIVE THE MESSAGE__"],
        "Alarm notification",
        "Someone has entered the room, picture attached",
        ["/home/pi/webcam.jpg"] )

评论

0赞 Das_Geek 12/12/2019
好久不见!很高兴看到你正确地归因了你的代码,并将其直接包含在答案中。但是,在多个问题上复制粘贴相同的答案代码通常是不受欢迎的。如果它们真的可以用相同的解决方案解决,您应该将问题标记为重复
0赞 Timothy C. Quinn 4/13/2023
我会避免使用,而是使用 server.send_message(msg)。首先,它更简单,其次,我发现了我无法解释的 sendmail 功能问题。server.sendmail()
0赞 Charlie Parker 4/25/2020 #13

您还可以在电子邮件中指定所需的附件类型,例如我使用的pdf:

def send_email_pdf_figs(path_to_pdf, subject, message, destination, password_path=None):
    ## credits: http://linuxcursor.com/python-programming/06-how-to-send-pdf-ppt-attachment-with-html-body-in-python-script
    from socket import gethostname
    #import email
    from email.mime.application import MIMEApplication
    from email.mime.multipart import MIMEMultipart
    from email.mime.text import MIMEText
    import smtplib
    import json

    server = smtplib.SMTP('smtp.gmail.com', 587)
    server.starttls()
    with open(password_path) as f:
        config = json.load(f)
        server.login('[email protected]', config['password'])
        # Craft message (obj)
        msg = MIMEMultipart()

        message = f'{message}\nSend from Hostname: {gethostname()}'
        msg['Subject'] = subject
        msg['From'] = '[email protected]'
        msg['To'] = destination
        # Insert the text to the msg going by e-mail
        msg.attach(MIMEText(message, "plain"))
        # Attach the pdf to the msg going by e-mail
        with open(path_to_pdf, "rb") as f:
            #attach = email.mime.application.MIMEApplication(f.read(),_subtype="pdf")
            attach = MIMEApplication(f.read(),_subtype="pdf")
        attach.add_header('Content-Disposition','attachment',filename=str(path_to_pdf))
        msg.attach(attach)
        # send msg
        server.send_message(msg)

灵感/致谢:http://linuxcursor.com/python-programming/06-how-to-send-pdf-ppt-attachment-with-html-body-in-python-script

1赞 Alex Potapenko 6/8/2021 #14

此处当前给出的答案都无法正确处理 GMail、Outlook 2016 和其他不支持 RFC 2231 的客户端(例如,请参阅此处)的文件名中的非 ASCII 符号。下面的 Python 3 代码改编自其他一些 stackoverflow 答案(对不起,没有保存原始链接)和 Python 2.7 的 odoo/openerp 代码(参见 ir_mail_server.py)。它与 GMail 和其他设备一起正常工作,并且还使用 SSL。

import smtplib, ssl
from os.path import basename
from email.mime.base import MIMEBase
from mimetypes import guess_type
from email.encoders import encode_base64
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.utils import COMMASPACE, formatdate
from email.charset import Charset


def try_coerce_ascii(string_utf8):
    """Attempts to decode the given utf8-encoded string
       as ASCII after coercing it to UTF-8, then return
       the confirmed 7-bit ASCII string.
 
       If the process fails (because the string
       contains non-ASCII characters) returns ``None``.
    """
    try:
        string_utf8.encode('ascii')
    except UnicodeEncodeError:
        return
    return string_utf8


def encode_header_param(param_text):
    """Returns an appropriate RFC 2047 encoded representation of the given
       header parameter value, suitable for direct assignation as the
       param value (e.g. via Message.set_param() or Message.add_header())
       RFC 2822 assumes that headers contain only 7-bit characters,
       so we ensure it is the case, using RFC 2047 encoding when needed.
 
       :param param_text: unicode or utf-8 encoded string with header value
       :rtype: string
       :return: if ``param_text`` represents a plain ASCII string,
                return the same 7-bit string, otherwise returns an
                ASCII string containing the RFC2047 encoded text.
    """
    if not param_text: return ""
    param_text_ascii = try_coerce_ascii(param_text)
    return param_text_ascii if param_text_ascii\
         else Charset('utf8').header_encode(param_text)


smtp_server = '<someserver.com>'
smtp_port = 465  # Default port for SSL
sender_email = '<[email protected]>'
sender_password = '<PASSWORD>'
receiver_emails = ['<[email protected]>', '<[email protected]>']
subject = 'Test message'
message = """\
Hello! This is a test message with attachments.

This message is sent from Python."""

files = ['<path1>/файл1.pdf', '<path2>/файл2.png']


# Create a secure SSL context
context = ssl.create_default_context()

msg = MIMEMultipart()
msg['From'] = sender_email
msg['To'] = COMMASPACE.join(receiver_emails)
msg['Date'] = formatdate(localtime=True)
msg['Subject'] = subject

msg.attach(MIMEText(message))

for f in files:
    mimetype, _ = guess_type(f)
    mimetype = mimetype.split('/', 1)
    with open(f, "rb") as fil:
        part = MIMEBase(mimetype[0], mimetype[1])
        part.set_payload(fil.read())
        encode_base64(part)
    filename_rfc2047 = encode_header_param(basename(f))

    # The default RFC 2231 encoding of Message.add_header() works in Thunderbird but not GMail
    # so we fix it by using RFC 2047 encoding for the filename instead.
    part.set_param('name', filename_rfc2047)
    part.add_header('Content-Disposition', 'attachment', filename=filename_rfc2047)
    msg.attach(part)

with smtplib.SMTP_SSL(smtp_server, smtp_port, context=context) as server:
    server.login(sender_email, sender_password)
    server.sendmail(sender_email, receiver_emails, msg.as_string())

评论

0赞 Cow 8/3/2021
我决定使用这个版本。特别是因为文件名,在我的情况下,它可以包含非 ASCII 字符。
0赞 tripleee 5/23/2022
Python 3.6+ 中更新的库在这些方面更加透明和健壮。它应该在没有您参与的情况下处理大部分细节;这是从您仍然在此处使用的旧 API 迁移的主要且相当令人信服的原因之一。email
0赞 Saisaaketh Sambana 6/16/2021 #15

试试这个,希望这可能会有所帮助

import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
   
fromaddr = "youremailhere"
toaddr = input("Enter The Email Adress You want to send to: ")
   
# instance of MIMEMultipart
msg = MIMEMultipart()
  
# storing the senders email address  
msg['From'] = fromaddr
  
# storing the receivers email address 
msg['To'] = toaddr
  
# storing the subject 
msg['Subject'] = input("What is the Subject:\t")
# string to store the body of the mail
body = input("What is the body:\t")
  
# attach the body with the msg instance
msg.attach(MIMEText(body, 'plain'))
  
# open the file to be sent 
filename = input("filename:")
attachment = open(filename, "rb")
  
# instance of MIMEBase and named as p
p = MIMEBase('application', 'octet-stream')
  
# To change the payload into encoded form
p.set_payload((attachment).read())
  
# encode into base64
encoders.encode_base64(p)
   
p.add_header('Content-Disposition', "attachment; filename= %s" % filename)
  
# attach the instance 'p' to instance 'msg'
msg.attach(p)
  
# creates SMTP session
s = smtplib.SMTP('smtp.gmail.com', 587)
  
# start TLS for security
s.starttls()
  
# Authentication
s.login(fromaddr, "yourpaswordhere)
  
# Converts the Multipart msg into a string
text = msg.as_string()
  
# sending the mail
s.sendmail(fromaddr, toaddr, text)
  
# terminating the session
s.quit()
0赞 Titus Cheserem 9/16/2021 #16

在让我的脚本发送通用附件时有点麻烦,但经过一些研究和浏览这篇文章的文章,我终于想出了以下内容

# to query:
import sys
import ast
from datetime import datetime

import smtplib
import mimetypes
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from email import encoders
from email.message import Message
from email.mime.audio import MIMEAudio
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
from email.mime.text import MIMEText

from dotenv import load_dotenv, dotenv_values

load_dotenv()  # load environment variables from .env

'''
sample .env file
# .env file
SECRET_KEY="gnhfpsjxxxxxxxx"
DOMAIN="GMAIL"
TOP_LEVEL_DOMAIN="COM"
EMAIL="CHESERExxxxxx@${DOMAIN}.${TOP_LEVEL_DOMAIN}"
TO_ADDRESS = ("[email protected]","[email protected]")#didn't use this in the code but you can load recipients from here
'''

import smtplib

tls_port = 587
ssl_port = 465
smtp_server_domain_names = {'GMAIL': ('smtp.gmail.com', tls_port, ssl_port),
                            'OUTLOOK': ('smtp-mail.outlook.com', tls_port, ssl_port),
                            'YAHOO': ('smtp.mail.yahoo.com', tls_port, ssl_port),
                            'AT&T': ('smtp.mail.att.net', tls_port, ssl_port),
                            }


# todo: Ability to choose mail server provider
# auto read in from the dictionary the respective mail server address and the tls and ssl ports

class Bimail:
    def __init__(self, subject, recipients):
        self.subject = subject
        self.recipients = recipients
        self.htmlbody = ''
        self.mail_username = 'will be loaded from .env file'
        self.mail_password = 'loaded from .env file as well'
        self.attachments = []

    # Creating an smtp object
    # todo: if gmail passed in use gmail's dictionary values

    def setup_mail_client(self, domain_key_to_use="GMAIL",
                          email_servers_domains_dict=smtp_server_domain_names):
        """

        :param report_pdf:
        :type to_address: str
        """
        smtpObj = None
        encryption_status = True
        config = dotenv_values(".env")
        # check if the domain_key exists from within the available email-servers-domains dict file passed in
        # else throw an error

        # read environment file to get the Domain to be used
        if f"{domain_key_to_use}" in email_servers_domains_dict.keys():
            # if the key is found do the following
            # 1.extract the domain,tls,ssl ports from email_servers dict for use in program
            try:
                values_tuple = email_servers_domains_dict.get(f"{domain_key_to_use}")
                ssl_port = values_tuple[2]
                tls_port = values_tuple[1]
                smtp_server = values_tuple[0]

                smtpObj = smtplib.SMTP(smtp_server, tls_port)
                print(f"Success connect with tls on {tls_port}")
                print('Awaiting for connection encryption via startttls()')
                encryption_status = False

            except:
                print(f"Failed connection via tls on port {tls_port}")
                try:
                    smtpObj = smtplib.SMTP_SSL(smtp_server, ssl_port)
                    print(f"Success connect with ssl on {ssl_port}")
                    encryption_status = True
                except:
                    print(f"Failed connection via ssl on port {ssl_port}")
            finally:
                print("Within Finally block")
                if not smtpObj:
                    print("Failed!!!  no Internet connection")
                else:
                    # if connection channel is unencrypted via the use of tls encrypt it
                    if not encryption_status:
                        status = smtpObj.starttls()
                        if status[0] == 220:
                            print("Successfully Encrypted tls channel")

                    print("Successfully Connected!!!! Requesting Login")
                    # Loading .env file values to config variable
                    #load Login Creds from ENV File
                    self.mail_username = f'{config.get("EMAIL")}'
                    self.mail_password = f'{cofig.get("SECRET_KEY")}'


                    status = smtpObj.login(self.mail_usernam,self.mail_password) 

                    if status[0] == 235:
                        print("Successfully Authenticated User to xxx account")
                        success = self.send(smtpObj, f'{config.get("EMAIL")}')
                        if not bool(success):
                            print(f"Success in Sending Mail to  {success}")
                            print("Disconnecting from Server INstance")
                            quit_result = smtpObj.quit()

                        else:
                            print(f"Failed to Post {success}!!!")
                            print(f"Quiting anyway !!!")
                            quit_result = smtpObj.quit()
                    else:
                        print("Application Specific Password is Required")
        else:

            print("World")

    def send(self,smtpObj,from_address):
        msg = MIMEMultipart('alternative')
        msg['From'] = from_address
        msg['Subject'] = self.subject
        msg['To'] = ", ".join(self.recipients)  # to must be array of the form ['[email protected]']
        msg.preamble = "preamble goes here"
        # check if there are attachments if yes, add them
        if self.attachments:
            self.attach(msg)
        # add html body after attachments
        msg.attach(MIMEText(self.htmlbody, 'html'))
        # send
        print(f"Attempting Email send to the following addresses {self.recipients}")
        result = smtpObj.sendmail(from_address, self.recipients,msg.as_string())
        return result
        

    def htmladd(self, html):
        self.htmlbody = self.htmlbody + '<p></p>' + html

    def attach(self, msg):
        for f in self.attachments:

            ctype, encoding = mimetypes.guess_type(f)
            if ctype is None or encoding is not None:
                ctype = "application/octet-stream"

            maintype, subtype = ctype.split("/", 1)

            if maintype == "text":
                fp = open(f)
                # Note: we should handle calculating the charset
                attachment = MIMEText(fp.read(), _subtype=subtype)
                fp.close()
            elif maintype == "image":
                fp = open(f, "rb")
                attachment = MIMEImage(fp.read(), _subtype=subtype)
                fp.close()

            elif maintype == "ppt":
                fp = open(f, "rb")
                attachment = MIMEApplication(fp.read(), _subtype=subtype)
                fp.close()

            elif maintype == "audio":
                fp = open(f, "rb")
                attachment = MIMEAudio(fp.read(), _subtype=subtype)
                fp.close()
            else:
                fp = open(f, "rb")
                attachment = MIMEBase(maintype, subtype)
                attachment.set_payload(fp.read())
                fp.close()
                encoders.encode_base64(attachment)
            attachment.add_header("Content-Disposition", "attachment", filename=f)
            attachment.add_header('Content-ID', '<{}>'.format(f))
            msg.attach(attachment)

    def addattach(self, files):
        self.attachments = self.attachments + files


# example below
if __name__ == '__main__':
    # subject and recipients
    mymail = Bimail('Sales email ' + datetime.now().strftime('%Y/%m/%d'),
                    ['[email protected]', '[email protected]'])
    # start html body. Here we add a greeting.
    mymail.htmladd('Good morning, find the daily summary below.')
    # Further things added to body are separated by a paragraph, so you do not need to worry about newlines for new sentences
    # here we add a line of text and an html table previously stored in the variable
    mymail.htmladd('Daily sales')
    mymail.addattach(['htmlsalestable.xlsx'])
    # another table name + table
    mymail.htmladd('Daily bestsellers')
    mymail.addattach(['htmlbestsellertable.xlsx'])
    # add image chart title
    mymail.htmladd('Weekly sales chart')
    # attach image chart
    mymail.addattach(['saleschartweekly.png'])
    # refer to image chart in html
    mymail.htmladd('<img src="cid:saleschartweekly.png"/>')
    # attach another file
    mymail.addattach(['MailSend.py'])
    # send!
    
    mymail.setup_mail_client( domain_key_to_use="GMAIL",email_servers_domains_dict=smtp_server_domain_names)

评论

0赞 tripleee 5/23/2022
此代码似乎是为 Python 3.5 或更早版本编写的。该库在 3.6 中进行了大修,现在更加通用和合乎逻辑。可能会扔掉你所拥有的,然后从电子邮件文档中的示例重新开始。email
0赞 Timothy C. Quinn 4/13/2023
我会避免使用,而是使用 .首先,它更简单,其次,我发现了我无法解释的 sendmail 功能问题。smtpObj.sendmail()smtpObj.send_message(msg)
3赞 miksus 1/4/2022 #17

我知道这是一个老问题,但我认为一定有一种比其他示例更简单的方法,因此我制作了一个库,可以在不污染您的代码库的情况下干净地解决这个问题。包括附件非常简单:

from redmail import EmailSender
from pathlib import Path

# Configure an email sender
email = EmailSender(
    host="<SMTP HOST>", port=0,
    user_name="[email protected]", password="<PASSWORD>"
)

# Send an email
email.send(
    sender="[email protected]",
    receivers=["[email protected]"],
    subject="An example email"
    attachments={
        "myfile.txt": Path("path/to/a_file.txt"),
        "myfile.html": "<h1>Content of a HTML attachment</h1>"
    }
)

您也可以直接附加 、 Pandas(根据密钥中的文件扩展名转换为格式)、Matplotlib 或 Pillow 。该库很可能是电子邮件发件人所需的所有功能(比附件要多得多)。bytesDataFrameFigureImage

要安装:

pip install redmail

以任何您喜欢的方式使用它。我还编写了大量文档:https://red-mail.readthedocs.io/en/latest/

评论

0赞 tripleee 5/23/2022
port=0看起来像一个蹩脚的笑话。您应该期望找到 587、465 或 25 之一;但可能会查阅您要使用的服务的电子邮件管理员或公共文档。
0赞 miksus 5/24/2022
@tripleee这甚至不是开玩笑。默认情况下,Smtplib 的 port=0。无论您对端口的看法如何,如何连接到特定的 SMTP 服务器都超出了本问题的范围。
0赞 tripleee 5/24/2022
呵呵,TIL,谢谢你让我直截了当地说。我想知道文档中“操作系统默认”是什么意思;我猜到处一定是 25 个?smtplib
1赞 DrWhat 6/13/2022
简直太棒了。这简化了多个附件,基本上简化了整个电子邮件的混乱。在 Anaconda 中可用。太棒了。
11赞 tripleee 5/23/2022 #18

因为这里有很多关于 Python 3 的答案,但没有一个显示如何使用 Python 3.6 中经过大修的库,所以这里是从当前的电子邮件示例文档中快速复制+粘贴的。 (我稍微删节了它,以删除诸如猜测正确的MIME类型之类的装饰。email

面向 Python >3.5 的现代代码不应再使用 API(包括各种 、 等类)或更老的 mumbo jumbo。email.message.MessageMIMETextMIMEMultipartMIMEBasemimetypes

from email.message import EmailMessage
import smtplib

msg = EmailMessage()
msg["Subject"] = "Our family reunion"
msg["From"] = "me <[email protected]>"
msg["To"] = "recipient <[email protected]>"
# definitely don't mess with the .preamble

msg.set_content("Hello, victim! Look at these pictures")

with open("path/to/attachment.png", "rb") as fp:
    msg.add_attachment(
        fp.read(), maintype="image", subtype="png")

# Notice how smtplib now includes a send_message() method
with smtplib.SMTP("localhost") as s:
    s.send_message(msg)

现代 API 现在比旧版本的库更加通用和合乎逻辑。文档中的演示文稿仍然存在一些问题(例如,如何更改附件并不明显;对于大多数新手来说,对模块的讨论可能太晦涩难懂了),从根本上说,您仍然需要对 MIME 结构应该是什么样子有某种想法(尽管库现在终于处理了很多细节)。也许请参阅多部分电子邮件中的“部分”是什么? 进行简要介绍。email.message.EmailMessageContent-Disposition:policy

显然,只有当您实际上在本地计算机上运行了 SMTP 服务器时,才可以使用 SMTP 服务器。正确地将电子邮件从系统中移除是一个相当复杂的独立问题。对于简单的要求,可以使用您现有的电子邮件帐户和提供商的电子邮件服务器(搜索将端口 587 与 Google、Yahoo 或您拥有的任何东西一起使用的示例 - 确切的效果在某种程度上取决于提供商;有些仅支持端口 465 或旧端口 25,但由于垃圾邮件过滤,现在基本上无法在面向公众的服务器上使用)。localhost

评论

0赞 DrWhat 6/13/2022
我非常喜欢只使用 email.message.EmailMessage API 的想法。您能否为具有多个具有不同子类型的附件的函数提供一个代码示例。
2赞 tripleee 6/13/2022
链接的文档有几个示例。但简而言之,(懒得为 XLSX 查找正确的 MIME 类型;朋友无论如何都不让朋友使用 Excel)。for attachment, main, sub in (("path/to/image.png", "image", "png"), ("another/directory/revenge.pdf", "application", "pdf"), ("somewhere/else/abyss.xlsx", "application", "octet-stream")): with open(attachment, "rb") as fp: msg.add_attachment(fp.read(), maintype=main, subtype=sub)
0赞 LWC 12/2/2022
我建议编辑它,也具有 因此,将使用正确的名称下载附件并正确预览。filename=...with open("path/to/attachment.png", "rb") as fp: msg.add_attachment( fp.read(), maintype="image", subtype="png", filename="attachment.png")
1赞 toowboga 10/10/2022 #19

以下是 Python 3.6 及更高版本的更新版本,使用 Python 标准库中大修模块的类。EmailMessageemail

import mimetypes
import os
import smtplib
from email.message import EmailMessage

username = "[email protected]"
password = "password"
smtp_url = "smtp.example.com"
port = 587


def send_mail(subject: str, send_from: str, send_to: str, message: str, directory: str, filename: str):
    # Create the email message
    msg = EmailMessage()
    msg['Subject'] = subject
    msg['From'] = send_from
    msg['To'] = send_to
    # Set email content
    msg.set_content(message)

    path = directory + filename

    if os.path.exists(path):
        ctype, encoding = mimetypes.guess_type(path)
        if ctype is None or encoding is not None:
            # No guess could be made, or the file is encoded (compressed), so
            # use a generic bag-of-bits type.
            ctype = 'application/octet-stream'
        maintype, subtype = ctype.split('/', 1)
        # Add email attachment
        with open(path, 'rb') as fp:
            msg.add_attachment(fp.read(),
                           maintype=maintype,
                           subtype=subtype,
                           filename=filename)

    smtp = smtplib.SMTP(smtp_url, port)
    smtp.starttls() # for using port 587
    smtp.login(username, password)
    smtp.send_message(msg)
    smtp.quit()

您可以在此处找到更多示例。

评论

0赞 Eduardo Lucio 1/3/2023
我相信格式中的“文件名”看起来会好得多。我错过了参数。🤗list[str]msg['Date']
0赞 tripleee 2/9/2023
这基本上只是从链接页面中复制/粘贴其中一个示例。
1赞 Timothy C. Quinn 4/13/2023 #20

正如@toowboga所建议的那样,如果您使用的是 Python >= 3.6,则应用于所有电子邮件。email.message.EmailMessage

这是我的版本:

import os
import smtplib
from pathlib import Path as PathLib
from email.message import EmailMessage
from email.utils import formatdate as email_formatdate

class Attachment():
    Path:str = None
    Name:str = None
    MIME:str = None

    def __init__(self, path:str, mime:str, name:str=None):
        self.Path = path
        assert os.path.isfile(path), f"Attachment path not found: '{path}'"
        assert isinstance(mime, str)
        a = mime.split('/')
        assert len(a) == 2, f"Invalid mime `{mime}`. Expecting <maintype>/<subtype>"
        self.MIME = mime
        if name is None:
            self.Name = PathLib(path).name
        else:
            self.Name = name
        
    def append_to(self, msg:EmailMessage):
        assert isinstance(msg, EmailMessage)
        (_maintype, _subtype) = self.MIME.split('/')
        with open(self.Path, "rb") as f:
            msg.add_attachment(f.read(), maintype=_maintype, subtype=_subtype, filename=self.Name)
        
        

def send_mail(send_from : str, 
              to_list : list, 
              subject : str, 
              body : str, 
              cc_list : list = None,
              bcc_list : list = None, 
              attachments : list = None, 
              as_html : bool = False, 
              server : str = "127.0.0.1"):
    assert isinstance(to_list, list)

    msg = EmailMessage()
    msg['From'] = send_from
    msg['To'] = ', '.join(to_list)
    msg['Date'] = email_formatdate(localtime=True)
    msg['Subject'] = subject

    if as_html:
        msg.set_content(body, subtype='html')
    else:
        msg.set_content(body)

    if cc_list and len(cc_list) > 0:
        msg['Cc'] = ', '.join(cc_list)
    if bcc_list and len(bcc_list) > 0:
        msg['Bcc'] = ', '.join(bcc_list)

    if isinstance(attachments, list):
        for attachment in attachments:
            assert isinstance(attachment, Attachment)
            attachment.append_to(msg)

    smtp = smtplib.SMTP(server)
    smtp.send_message(msg)
    smtp.close()


# Usage:
send_mail(send_from     = "[email protected]",
          to_list       = ["[email protected]"],
          subject       = "_email_subject_",
          body          = "_email_body_",
          attachments   =[Attachment("/tmp/attachment.pdf", 'application/pdf')])