用Python/Django创建带图像的MIME邮件模板

15 投票
3 回答
22655 浏览
提问于 2025-04-15 15:25

在我的网页应用中,我偶尔会使用一个可以重复使用的邮件发送工具来发送邮件,像这样:

user - self.user
subject = ("My subject")
from = "me@mydomain.com"
message = render_to_string("welcomeEmail/welcome.eml", { 
                "user" : user,
                })
send_mail(subject, message, from, [email], priority="high" )

我想发送一封包含嵌入图片的邮件,所以我尝试在邮件客户端中制作邮件,查看源代码,然后把它放到我的模板(welcome.eml)里,但我发现发送出去后,在邮件客户端中显示得不对。

有没有人知道一个简单的方法,可以让我创建带有内嵌图片的邮件模板,并且发送后能正确显示?

3 个回答

0

使用文本替代的版本

Jarret Hardie 的回答提到:“你可以传递纯文本来创建这个消息的替代部分。”不过,我发现这样做会导致 Gmail(虽然 Outlook 不会)将电子邮件的两个部分一个接一个地显示出来,就像这里描述的那样

正如那里和其他地方推荐的,我简单地使用了 EmailMultiAlternatives。最终的代码大致如下:

from django.core.mail import EmailMultiAlternatives
from email.mime.image import MIMEImage

# Load the image you want to send as bytes
img_data = open('logo.jpg', 'rb').read()

# Create the body with HTML. Note that the image, since it is inline, is 
# referenced with the URL cid:myimage... you should take care to make
# "myimage" unique
html_content = '<p>Hello <img src="cid:myimage" /></p>'
text_content = 'Hello'

# Configure an EmailMultiAlternatives
msg = EmailMultiAlternatives('Subject Line', text_content, 'foo@bar.com', ['bar@foo.com'])
msg.attach_alternative(html_content)

# Now create the MIME container for the image
img = MIMEImage(img_data, 'jpeg')
img.add_header('Content-Id', '<myimage>')  # angle brackets are important
img.add_header("Content-Disposition", "inline", filename="myimage") # David Hess recommended this edit
msg.attach(img)

# Finally, send the whole thing.
msg.send()
2

我在使用Jarret的配方时遇到了问题,特别是在Django 1.10上,出现了MIME和编码错误,这些错误与不同的方式附加MIME数据有关。

这里有一个简单的多部分事务模板,用于发送电子邮件,并且里面嵌入了一个coupon_image文件对象,这个模板在Django 1.10上可以正常工作:

from django.core.mail import EmailMultiAlternatives
from email.mime.image import MIMEImage

def send_mail(coupon_image):
    params = {'foo':'bar'} # create a template context
    text_body = render_to_string('coupon_email.txt', params)
    html_body = render_to_string('coupon_email.html', params)
    img_data = coupon_image.read() #should be a file object, or ImageField
    img = MIMEImage(img_data)
    img.add_header('Content-ID', '<coupon_image>')
    img.add_header('Content-Disposition', 'inline', filename="coupon_image")

    email = EmailMultiAlternatives(
        subject="Here's your coupon!",
        body=text_body,
        from_email='noreply@example.com',
        to=['someone@example.com',]
    )

    email.attach_alternative(html_body, "text/html")
    email.mixed_subtype = 'related'
    email.attach(img)

    email.send(fail_silently=False)
35

更新

非常感谢 Saqib Ali,在我回复的近五年后重新提起了这个老问题。

我当时给出的指示现在已经不再有效。我猜这几年Django有了一些改进,导致 send_mail() 现在只支持纯文本。不管你在内容里写什么,最终都会以纯文本的形式发送。

最新的 Django文档 解释说, send_mail() 其实只是用来方便地创建一个 django.core.mail.EmailMessage 类的实例,然后调用这个实例的 send() 方法。 EmailMessage 对于内容部分有这样的说明,这也解释了我们在2014年看到的结果:

内容:正文文本。这应该是一个纯文本消息。

... 文档稍后提到 ...

默认情况下,EmailMessage中内容部分的MIME类型是“text/plain”。最好保持这个设置。

说得不错(我承认我没有花时间去调查为什么2009年的指示有效——我在2009年测试过——或者是什么时候改变的)。Django确实提供了一个 django.core.mail.EmailMultiAlternatives 类,并且有相关的 文档,可以更方便地发送同一消息的纯文本和HTML版本。

这个问题的情况稍微不同。我们并不是想要添加一个替代内容,而是想要在其中一个替代内容中添加相关部分。在HTML版本中(无论你是否有纯文本版本),我们想要嵌入一个图片数据部分。不是内容的替代视图,而是HTML正文中引用的相关内容。

发送嵌入的图片仍然是可能的,但我没有看到使用 send_mail 的简单方法。是时候放弃这个方便的函数,直接实例化一个 EmailMessage 了。

这是对之前示例的更新:

from django.core.mail import EmailMessage
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

# Load the image you want to send as bytes
img_data = open('logo.jpg', 'rb').read()

# Create a "related" message container that will hold the HTML 
# message and the image. These are "related" (not "alternative")
# because they are different, unique parts of the HTML message,
# not alternative (html vs. plain text) views of the same content.
html_part = MIMEMultipart(_subtype='related')

# Create the body with HTML. Note that the image, since it is inline, is 
# referenced with the URL cid:myimage... you should take care to make
# "myimage" unique
body = MIMEText('<p>Hello <img src="cid:myimage" /></p>', _subtype='html')
html_part.attach(body)

# Now create the MIME container for the image
img = MIMEImage(img_data, 'jpeg')
img.add_header('Content-Id', '<myimage>')  # angle brackets are important
img.add_header("Content-Disposition", "inline", filename="myimage") # David Hess recommended this edit
html_part.attach(img)

# Configure and send an EmailMessage
# Note we are passing None for the body (the 2nd parameter). You could pass plain text
# to create an alternative part for this message
msg = EmailMessage('Subject Line', None, 'foo@bar.com', ['bar@foo.com'])
msg.attach(html_part) # Attach the raw MIMEBase descendant. This is a public method on EmailMessage
msg.send()

2009年的原始回复:

要发送带有嵌入图片的电子邮件,可以使用Python内置的电子邮件模块来构建MIME部分。

以下代码应该可以实现:

from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

# Load the image you want to send at bytes
img_data = open('logo.jpg', 'rb').read()

# Create a "related" message container that will hold the HTML 
# message and the image
msg = MIMEMultipart(_subtype='related')

# Create the body with HTML. Note that the image, since it is inline, is 
# referenced with the URL cid:myimage... you should take care to make
# "myimage" unique
body = MIMEText('<p>Hello <img src="cid:myimage" /></p>', _subtype='html')
msg.attach(body)

# Now create the MIME container for the image
img = MIMEImage(img_data, 'jpeg')
img.add_header('Content-Id', '<myimage>')  # angle brackets are important
msg.attach(img)

send_mail(subject, msg.as_string(), from, [to], priority="high")

实际上,你可能想要同时发送HTML和纯文本的替代内容。在这种情况下,使用MIMEMultipart创建“相关”的MIME类型容器作为根。然后创建另一个子类型为“alternative”的MIMEMultipart,并将一个MIMEText(子类型为html)和一个MIMEText(子类型为plain)附加到替代部分。最后,将图片附加到相关的根部分。

撰写回答