问题描述:
用flask mail发送图片邮件(正文中的图片),在mac系统邮件客户端上,图片出现重复展示问题,即展示了正文图片,同时也把图片作为附件进行了展示,用原生smtp方式发送的图片邮件没有出现该问题;
(只在mac上出现,windows上没有出现)
先贴结果图:
待解决问题点:
发送的图片邮件,在mac上出现重复展示问题,一张是正文,一张是附件,需要了解flask mail 发送图片邮件,通过什么方式能在mac上不重复展示(mac自带邮箱客户端)
问题重现:
工程结构(flask工程):
app.py代码:
from flask import * from flask_mail import * app = Flask(__name__) app.config["MAIL_SERVER"] = 'smtp.testxxx.cn' app.config["MAIL_PORT"] = 465 app.config["MAIL_USERNAME"] = 'xiaxx@testxxx.cn' app.config['MAIL_PASSWORD'] = 'testxxx' app.config['MAIL_USE_TLS'] = False app.config['MAIL_USE_SSL'] = True mail = Mail(app) @app.route('/') def send_email(): send_email_func('emailTemplate', img="cid:image") return "sent ok" def send_async_email(app, msg): with app.app_context(): mail.send(msg) def send_email_func(template, **kwargs): app = current_app._get_current_object() msg = Message(subject="flask mail默认方式发送图片邮件", body="flask mail邮件测试", sender="xiaxx@testxxx.cn", recipients=["xiaxx@testxxx.cn"]) msg.html = render_template(template + '.html', **kwargs) with open("testtool.png", "rb") as fp: msg.attach("image.png", "image/png", fp.read(), 'inline', headers=[('Content-ID', 'image')]) send_async_email(app, msg) if __name__ == '__main__': app.run(debug=True)
模板内容:
<!DOCTYPE html> <html lang="en"> <body> <img src="{{ img }}"> <p>单独flask mail工程, 用flask mail默认方式发送图片邮件</p> </body> </html>
发送邮件截图,问题点:mac mail客户端重复展示图片
重写flask mail中Message类的_message()方法,修改msg的组装方式:
调整内容:
新增MailMessage类,继承flask mail的Message类,重写其_message()方法,在初始化msg对象时,在msg = MIMEMultipart()方法中指定"related"方式
app.py代码:
from flask import * from flask_mail import * import re import unicodedata from email.encoders import encode_base64 from email.mime.base import MIMEBase from email.mime.multipart import MIMEMultipart from email.utils import formatdate app = Flask(__name__) app.config["MAIL_SERVER"] = 'smtp.testxxx.cn' app.config["MAIL_PORT"] = 465 app.config["MAIL_USERNAME"] = 'xiaxx@testxxx.cn' app.config['MAIL_PASSWORD'] = 'testxxx' app.config['MAIL_USE_TLS'] = False app.config['MAIL_USE_SSL'] = True mail = Mail(app) class MailMessage(Message): def _message(self): """Creates the email""" ascii_attachments = current_app.extensions['mail'].ascii_attachments encoding = self.charset or 'utf-8' attachments = self.attachments or [] if len(attachments) == 0 and not self.html: # No html content and zero attachments means plain text msg = self._mimetext(self.body) elif len(attachments) > 0 and not self.html: # No html and at least one attachment means multipart msg = MIMEMultipart() msg.attach(self._mimetext(self.body)) else: # Anything else msg = MIMEMultipart('related') alternative = MIMEMultipart('alternative') alternative.attach(self._mimetext(self.body, 'plain')) alternative.attach(self._mimetext(self.html, 'html')) msg.attach(alternative) if self.subject: msg['Subject'] = sanitize_subject(force_text(self.subject), encoding) msg['From'] = sanitize_address(self.sender, encoding) msg['To'] = ', '.join(list(set(sanitize_addresses(self.recipients, encoding)))) msg['Date'] = formatdate(self.date, localtime=True) # see RFC 5322 section 3.6.4. msg['Message-ID'] = self.msgId if self.cc: msg['Cc'] = ', '.join(list(set(sanitize_addresses(self.cc, encoding)))) if self.reply_to: msg['Reply-To'] = sanitize_address(self.reply_to, encoding) if self.extra_headers: for k, v in self.extra_headers.items(): msg[k] = v SPACES = re.compile(r'[s]+', re.UNICODE) for attachment in attachments: f = MIMEBase(*attachment.content_type.split('/')) f.set_payload(attachment.data) encode_base64(f) filename = attachment.filename if filename and ascii_attachments: # force filename to ascii filename = unicodedata.normalize('NFKD', filename) filename = filename.encode('ascii', 'ignore').decode('ascii') filename = SPACES.sub(u' ', filename).strip() try: filename and filename.encode('ascii') except UnicodeEncodeError: if not PY3: filename = filename.encode('utf8') filename = ('UTF8', '', filename) f.add_header('Content-Disposition', attachment.disposition, filename=filename) for key, value in attachment.headers: f.add_header(key, value) msg.attach(f) if message_policy: msg.policy = message_policy return msg @app.route('/') def send_email(): send_email_func('emailTemplate', img="cid:image") return "sent ok" def send_async_email(app, msg): with app.app_context(): mail.send(msg) def send_email_func(template, **kwargs): app = current_app._get_current_object() msg = MailMessage(subject="修改flask mail _message()方法中组装msg方式", body="hello", sender="xiaxx@testxxx.cn", recipients=["xiaxx@testxxx.cn"]) msg.html = render_template(template + '.html', **kwargs) with open("testtool.png", "rb") as fp: msg.attach("image.png", "image/png", fp.read(), 'inline', headers=[('Content-ID', 'image')]) send_async_email(app, msg) if __name__ == '__main__': app.run(debug=True)
模板代码:
<!DOCTYPE html> <html lang="en"> <body> <img src="{{ img }}"> <p>单独flask mail工程测试邮件发送图片功能,重写原始_message方式,msg初始化时传入'related'</p> </body> </html>
发送邮件截图:没有出现重复展示图片问题
关于MIMEMultipart()方法参数的解释:
参考链接:https://blog.csdn.net/Winnycatty/article/details/84548381
MIMEMultipart类型
MIME邮件中各种不同类型的内容是分段存储的,各个段的排列方式、位置信息都通过Content-Type域的multipart类型来定义。multipart类型主要有三种子类型:mixed、alternative、related。
(1) MIMEMultipart类型基本格式
● MIMEMultipart(‘mixed’)类型
如果一封邮件中含有附件,那邮件的中必须定义multipart/mixed类型,邮件通过multipart/mixed类型中定义的boundary标识将附件内容同邮件其它内容分成不同的段。基本格式如下:
msg=MIMEMultipart(‘mixed’)
● MIMEMultipart(‘alternative’)类型
MIME邮件可以传送超文本内容,但出于兼容性的考虑,一般在发送超文本格式内容的同时会同时发送一个纯文本内容的副本,如果邮件中同时存在纯文本和超文本内容,则邮件需要在Content-Type域中定义multipart/alternative类型,邮件通过其boundary中的分段标识将纯文本、超文本和邮件的其它内容分成不同的段。基本格式如下:
msg=MIMEMultipart(‘alternative’)
● MIMEMultipart(‘related’)类型
MIME邮件中除了可以携带各种附件外,还可以将其它内容以内嵌资源的方式存储在邮件中。比如我们在发送html格式的邮件内容时,可能使用图像作为 html的背景,html文本会被存储在alternative段中,而作为背景的图像则会存储在multipart/related类型定义的段中。基本格式如下:
msg=MIMEMultipart(‘related’)
结论分析:
出现该问题的原因可以认为是在初始化msg对象时,msg=MIMEMultipart()没有指定其消息的存储方式,进而导致在mac上,消息的图片没有被隐藏,而是作为附件进行了展示
从MIMEMultipart(‘related’)的含义可知,这种方式可以让图片资源以内嵌的方式存储在邮件中,从结果来看,应该是这个属性导致消息中的图片在mac上的mail客户端作为内嵌资源展示在邮件中,没有作为附件展示
从mac上邮件的加载过程看,邮件中的图片并不是直接作为正文内容展示的,而是也作为附件进行了加载,可以看到图片附件的加载过程(体验上不是很理想),只不过这个附件的内容加载出来后,直接在正文位置中展示了,在正文最后,没有以附件形式再次展示,从结果来看,图片邮件是正常展示的,没有重复展示。满足当前需求。