带有 html 的电子邮件附件在某些设备上不可见

Posted

技术标签:

【中文标题】带有 html 的电子邮件附件在某些设备上不可见【英文标题】:Email attachments with html not visible on some devices 【发布时间】:2016-01-24 08:03:57 【问题描述】:

我想发送一封带有 content-ID 附件(直接在消息中的图像)的 html 电子邮件,并且 Content-Disposition 附件是一个 xls 文件。

根据我使用的客户端邮件,xls 文件不可见(例如在 iPhone、iPad 或三星上)。

我想我没有在邮件的正确部分包含 xls 文件,但是当我尝试在 MIMEMultipart('alternative') 之前包含它时,它不起作用。

有人知道以下代码中问题的根源吗?

import base64
import httplib2

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

from apiclient.discovery import build
from oauth2client.client import flow_from_clientsecrets
from oauth2client.file import Storage
from oauth2client.tools import run

html = """\
<html>
  <head></head>
  <body>
    <p>Hi!<br>
       XXX <br><br>
        Hereunder a short overview of market evolution :<br>
        <img src="cid:image1"  >
    </p>
  </body>
</html>
"""

to='test@gmail.com';
subject='XXX';
message_main = html;
attachment_paths = [('test.png'), '<image1>'),
                    ('2910e0f.xls', False)]


# Check https://developers.google.com/gmail/api/auth/scopes for all available scopes
OAUTH_SCOPE = 'https://www.googleapis.com/auth/gmail.compose'

# Location of the gmail.storage
STORAGE = Storage('gmail.storage')

# Start the OAuth flow to retrieve credentials
flow = flow_from_clientsecrets(CLIENT_SECRET_FILE, scope=OAUTH_SCOPE)
http = httplib2.Http()

# Try to retrieve credentials from storage or run the flow to generate them
credentials = STORAGE.get()
if credentials is None or credentials.invalid:
  credentials = run(flow, STORAGE, http=http)

# Authorize the httplib2.Http object with our credentials
http = credentials.authorize(http)

## Save credentials to storage
# STORAGE.put(credentials)
# Build the Gmail service from discovery
gmail_service = build('gmail', 'v1', http=http)

message = MIMEMultipart('related') # 'alternative', 'multipart', 'mixed', related
message['to'] = to
message['from'] = "YYY@gmail.com"
message['subject'] = subject
message.preamble = 'This is a multi-part message in MIME format.'

# Encapsulate the plain and HTML versions of the message body in an
# 'alternative' part, so message agents can decide which they want to     display.
msgAlternative = MIMEMultipart('alternative')
message.attach(msgAlternative)

msg1 = MIMEText(message_main, 'plain')
msg2 = MIMEText(message_main, 'html')
msgAlternative.attach(msg1)
msgAlternative.attach(msg2)

for filename, link in attachment_paths:
    print '----'
    print filename
    print link
    content_type, encoding = mimetypes.guess_type(filename)

    if content_type is None or encoding is not None:
        content_type = 'application/octet-stream'
    main_type, sub_type = content_type.split('/', 1)

    if main_type == 'text':
        fp = open(filename, 'rb')
        msg = MIMEText(fp.read(), _subtype=sub_type)
        fp.close()
    elif main_type == 'image':
        fp = open(filename, 'rb')
        msg = MIMEImage(fp.read(), _subtype=sub_type)
        fp.close()
    elif main_type == 'audio':
        fp = open(filename, 'rb')
        msg = MIMEAudio(fp.read(), _subtype=sub_type)
        fp.close()
    else:
        fp = open(filename, 'rb')
        msg = MIMEBase(main_type, sub_type)
        msg.set_payload(fp.read())
        fp.close()

    if not link:
        # msg.add_header('Content-Disposition', 'attachment', filename=filename)
        # message.attach(msg)
        # pdf = open(filename, 'rb').read()
        # msgPdf = MIMEApplication(pdf, 'pdf') # pdf for exemple
        # msgPdf.add_header('Content-ID', '<pdf1>') # if no cid, client like MAil.app (only one?) don't show the attachment
        msg.add_header('Content-Disposition', 'attachment', filename=filename)
        # msg.add_header('Content-Disposition', 'inline', filename=filename)
    else:
        # Define the image's ID as referenced above
        msg.add_header('Content-ID', link)
        msg.add_header('Content-Disposition', 'attachment', filename=filename)

    message.attach(msg)

body = 'raw': base64.urlsafe_b64encode(message.as_string())

# body = 'raw': base64.b64encode(message.as_string())

# send it
try:
  message = (gmail_service.users().messages().send(userId="me", body=body).execute())
  print('Message Id: %s' % message['id'])
  print(message)
except Exception as error:
  print('An error occurred: %s' % error)

【问题讨论】:

【参考方案1】:

解决方案包括将初始多部分容器更改为混合并在两步过程中附加文件:

简单附件的MIMEMultipart('alternative') 声明之前 在MIMEMultipart('alternative')声明后与html正文相关的附件(主要是邮件内的图片)

您可以在下面找到正确的代码:

import base64
import httplib2

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

from apiclient.discovery import build
from oauth2client.client import flow_from_clientsecrets
from oauth2client.file import Storage
from oauth2client.tools import run

html = """\
<html>
  <head></head>
  <body>
    <p>Hi!<br>
       XXX <br><br>
        Hereunder a short overview of market evolution :<br>
        <img src="cid:image1"  >
    </p>
  </body>
</html>
"""

to='test@gmail.com';
subject='XXX';
message_main = html;
attachment_paths_html = [('test.png'), '<image1>')]
attachment_paths_mixed = [('2910e0f.xls', '2910e0f.xls')]

# Check https://developers.google.com/gmail/api/auth/scopes for all available scopes
OAUTH_SCOPE = 'https://www.googleapis.com/auth/gmail.compose'

# Location of the gmail.storage
STORAGE = Storage('gmail.storage')

# Start the OAuth flow to retrieve credentials
flow = flow_from_clientsecrets(CLIENT_SECRET_FILE, scope=OAUTH_SCOPE)
http = httplib2.Http()

# Try to retrieve credentials from storage or run the flow to generate them
credentials = STORAGE.get()
if credentials is None or credentials.invalid:
  credentials = run(flow, STORAGE, http=http)

# Authorize the httplib2.Http object with our credentials
http = credentials.authorize(http)

## Save credentials to storage
# STORAGE.put(credentials)
# Build the Gmail service from discovery
gmail_service = build('gmail', 'v1', http=http)

message = MIMEMultipart('mixed')
message['to'] = to
message['from'] = "YYY@gmail.com"
message['subject'] = subject
message.preamble = 'This is a multi-part message in MIME format.'


for filename, name in attachment_paths_mixed:
    print '----'
    print filename
    print name
    content_type, encoding = mimetypes.guess_type(filename)

    if content_type is None or encoding is not None:
        content_type = 'application/octet-stream'
    main_type, sub_type = content_type.split('/', 1)

    if main_type == 'text':
        fp = open(filename, 'rb')
        msg = MIMEText(fp.read(), _subtype=sub_type)
        fp.close()
    elif main_type == 'image':
        fp = open(filename, 'rb')
        msg = MIMEImage(fp.read(), _subtype=sub_type)
        fp.close()
    elif main_type == 'audio':
        fp = open(filename, 'rb')
        msg = MIMEAudio(fp.read(), _subtype=sub_type)
        fp.close()
    else:
        fp = open(filename, 'rb')
        msg = MIMEBase(main_type, sub_type)
        msg.set_payload(fp.read())
        fp.close()

        msg.add_header('Content-Disposition', 'attachment', filename=name)
        message.attach(msg)

# Encapsulate the plain and HTML versions of the message body in an
# 'alternative' part, so message agents can decide which they want to     display.
msgAlternative = MIMEMultipart('alternative')
message.attach(msgAlternative)

msg1 = MIMEText(message_main, 'plain')
msg2 = MIMEText(message_main, 'html')
msgAlternative.attach(msg1)
msgAlternative.attach(msg2)

for filename, link in attachment_paths_html:
    print '----'
    print filename
    print link
    content_type, encoding = mimetypes.guess_type(filename)

    if content_type is None or encoding is not None:
        content_type = 'application/octet-stream'
    main_type, sub_type = content_type.split('/', 1)

    if main_type == 'text':
        fp = open(filename, 'rb')
        msg = MIMEText(fp.read(), _subtype=sub_type)
        fp.close()
    elif main_type == 'image':
        fp = open(filename, 'rb')
        msg = MIMEImage(fp.read(), _subtype=sub_type)
        fp.close()
    elif main_type == 'audio':
        fp = open(filename, 'rb')
        msg = MIMEAudio(fp.read(), _subtype=sub_type)
        fp.close()
    else:
        fp = open(filename, 'rb')
        msg = MIMEBase(main_type, sub_type)
        msg.set_payload(fp.read())
        fp.close()

        # Define the image's ID as referenced above
        msg.add_header('Content-ID', link)
        message.attach(msg)

body = 'raw': base64.urlsafe_b64encode(message.as_string())

# body = 'raw': base64.b64encode(message.as_string())

# send it
try:
  message = (gmail_service.users().messages().send(userId="me", body=body).execute())
  print('Message Id: %s' % message['id'])
  print(message)
except Exception as error:
  print('An error occurred: %s' % error)

【讨论】:

以上是关于带有 html 的电子邮件附件在某些设备上不可见的主要内容,如果未能解决你的问题,请参考以下文章

UIButtons 在某些设备上不起作用

使用 HTML 编码时,为啥视频在某些 Android 设备上加载,而在其他设备上不加载?

发送带有附件的 HTML 电子邮件

通过 SMTP 发送带有附件、纯文本/文本和文本/html 的电子邮件

带有pdf附件和html消息的php邮件功能

带有多个附件 + html 的 SMTP 邮件 Mime