Google API通过Python发送的邮件中的内联/嵌入附件也显示在附件中



在过去的几天里,我一直为这个问题而心烦意乱。为了澄清我的问题,我正在尝试发送嵌入图像的电子邮件。我主要使用这些嵌入图像作为页脚或社交媒体图标。问题是这些嵌入的图像随后会显示在收件箱中,如下所示。奇怪的是,当电子邮件打开时,没有附件可见,但图像嵌入如图所示。虽然每个电子邮件提供商的问题表现略有不同,但所有大型电子邮件提供商似乎都有同样的问题,即在收件箱中显示附件,但在打开特定电子邮件时却没有。

我使用Python的标准MIME库,与Python的(相对)新的EmailMessage MIME对象。我的过程看起来有点像这样:

html_text = MIMEText(message_str, 'html')
html_part = MIMEMultipart('related')
html_part.attach(html_text)
for attachment in attachments:
mime_attachment = MIMEImage(base64.b64decode(attachment.get('data')),
_subtype=attachment.get('type'))
mime_attachment.add_header('Content-ID', f'<{attachment.get("name")}>')
mime_attachment.add_header('Content-Disposition', 'inline',
filename=f'{attachment.get("name")}')
mime_attachment.add_header('Content-Transfer-Encoding', 'base64')
html_part.attach(mime_attachment)
root_message = EmailMessage()
root_message.make_mixed()
root_message.attach(html_part)

这意味着我的MIME层次结构看起来像这样:

  • 多部分/混合
    • 多部分/相关
      • text/html
    • 图像/png

我已经使用了许多不同的MIME类型和层次结构,但我现在使用这个网站作为参考。

在整个MIME过程之后,我只是将其转换为字节,base64编码并通过GMail API发送。

Gmail显示给我的原始消息看起来很好,所有类型都是正确的,如上面的MIME层次结构所示。图像部分显示为:

--00000000000064aaa005efdc1b1b
Content-Type: image/png; name="9a57c8bb80784228ae77975a659f83c6.png"
Content-Disposition: inline; filename="9a57c8bb80784228ae77975a659f83c6.png"
Content-Transfer-Encoding: base64
Content-ID: <9a57c8bb80784228ae77975a659f83c6>
X-Attachment-Id: cb9a6d0733ed31c7_0.0.2

我尝试过许多不同的MIME层次结构,也尝试过使用EmailMessage的add_attachment,但无济于事。我有时会让内联图像工作,而收件箱中没有附件,但是当我打开电子邮件时,整个图像都在message/rfc822类型的附件中,采用base64编码。这时,我随便往墙上扔东西,看看粘在墙上的是什么,但每当有东西朝正确的方向迈出一步时,东西就变得奇怪了。

使用外部托管的图像对我来说不是一个选择,也不是将整个图像编码为base64并将其放在html中。

我希望有人能给我提供一些见解。这对我以前是有效的,所以我对这一切都很困惑。我提前感谢你。

编辑:我已经继续并重构了代码-我在此期间尝试了一些事情,但没有结果。我尝试手工创建一个mimpart有效载荷,并使用EmailMessage的add_attachment,但无济于事。我也尝试过混合/替代/相对的任何组合,没有任何改善。下面是我更新后的代码:

# Creating container for HTML and adding pixel and message (alternative part and images go here)
related_part = MIMEMultipart('related')
# Creating container for html_part and images (HTML and plain go here)
alternative_part = MIMEMultipart('alternative')
# Creating a MIMEText object to hold our HTML
html_text = MIMEText(message_str, 'html')
alternative_part.attach(html_text)
# Adding content to the related part, making the hierarchy like so:
#   * multipart/related
#       * multipart/alternative
#           * html/text
related_part.attach(alternative_part)
# Adding attachments to the container, to look like so:
#   * multipart/related
#       * multipart/alternative
#           * html/text
#       * image / png
for attachment in attachments:
mime_attachment = MIMEImage(base64.b64decode(attachment.get('data')), _subtype=attachment.get('type'))
mime_attachment.add_header('Content-ID', f'<{attachment.get("name")}>')
mime_attachment.add_header('Content-Disposition', 'inline')
#filename=f'{attachment.get("name")}.{attachment.get("type")}')
mime_attachment.add_header('Content-Transfer-Encoding', 'base64')
related_part.attach(mime_attachment)
# Creating the root EmailMessage
root_message = EmailMessage(EmailPolicy(utf8=True))
root_message.make_mixed()
# Attaching the actual message to the root, to look like this:
#   * multipart/mixed
#       * multipart/related
#           * multipart/alternative
#               * html/text
#           * image / png
root_message.attach(related_part)
root_message['To'] = f"{recipient_name} <{recipient_email}>"
root_message['From'] = f"{self.sender_name} <{self.sender_email}>"
root_message['Subject'] = subject_str
return {
'raw': base64.urlsafe_b64encode(root_message.as_bytes()).decode()
}

返回的数据像这样通过Google的API客户端发送:

service.users().messages().send(userId="me", body=mail_data).execute()

我已经把这个问题升级到谷歌的跟踪器:https://issuetracker.google.com/issues/263427102

问题是文件大小。看起来,大多数邮件提供程序都有内联图像的最大文件大小。这些内联图像在大多数设备和客户端上仍将以内联方式显示,但它们仍将同时作为附件显示。

Gmail, Gsuite, Office和其他网络邮件都有不同的最大文件大小。当我将最大容量设置为15kB时,这些邮件提供商都没有抱怨,我所有的文件都很好,并且都是内联的。

当然,由于有数百封电子邮件,我不打算手工操作,所以我使用PIL/Pillow编写了一个小脚本来减小图像大小,直到达到一定的大小。

img_out = io.BytesIO(image_data)
img = Image.open(img_out)
while getsizeof(img_out) > 15000:
img_out = io.BytesIO()
width, height = img.size
new_width, new_height = int(width * 0.95), int(height*0.95)
img.thumbnail((new_width, new_height), Image.Resampling.LANCZOS)
img.save(img_out, 'jpeg')
image_data = img_out.getvalue()
with open('downscaled_image.jpeg', 'wb') as writefile:
writefile.write(image_data)

最新更新