电子邮件进阶实践

下面来学习构建邮件的HTML正文,并使用模板组织内容。

一封电子邮件的正文可以是纯文本(text/plain),也可以是HTML格式的文本(text/html)。处于全面的考虑,一封邮件应该既包含纯文本正文又包含HTML格式的正文。HTML格式的正文将被优先读取;加入收信人的邮件系统比较古老,无法读取HTML格式的邮件,则会读取纯文本格式的正文。

下面来学习如何编写HTML邮件正文,以及如何在Flask-Mail中同时提供这两种格式的邮件正文。

如果HTML正文非常简单,比如仅仅在纯文本的基础上添加链接和少量HTML标签,那么不用太在意编写方式。如果想创建更加丰富美观的证件正文,那么会有很多事情需要考虑。出去无法读取HTML正文的古董邮件客户端,大多数主流的邮箱客户端都会HTML邮件有着各种各样的限制。对于HTML邮件正文的编写,下面是一些常见的“最佳实践”:

1)  使用Tabel布局,而不是Div布局

2)  使用行内(inline)样式定义,比如:

<span style = “font-family;Arial, Helvetica, sans-serif; font-size:12px; color:#000000;”>Hello, Email!</span>

3)  尽量使用比较基础的CSS属性,避免使用快捷属性(如background)和定位属性(比如float、position)。

4)  邮件正文的宽度不应超过600px

5)  避免使用JavaScript代码

6)  避免使用背景图片

为了确保邮件显示符合预期,最好提前在各个主流的邮箱客户端以及不同尺寸的设备上进行测试。

在Flask-Mail中,我们使用Message类实例来构建邮件。和纯文本正文类似,HTML正文可以在实例化时传入html参数指定,比如:

message = Message(…, body = ‘纯文本正文’, html=<h1>HTML正文</h1>)

或是通过类属性message.html指定:

message = Message(…)

message.body = ‘纯文本正文’

message.html = ‘<h1>HTML正文</h1>’

使用Jinja2模板组织邮件正文

大多数情况下,我们需要动态构建邮件正文,比如,在周刊订阅程序中,当用户订阅成功后,我们发送一封确认邮件。对于不同的用户来说,邮件的内容基本相同,但同时邮件中又包含用户名称的动态部分,使用模板来组织邮件正文再合适不过。

templates/emails/subscribe.txt: 纯文本邮件模板

  1. Hello {{ name }},
  2. Thank you for subscribing Flask Weekly!
  3. Enjoy the reading :)
  4.  
  5. Visit this link ro unsubscribe: {{ url_for('unsubscribe', _external = True) }}

为了同时支持纯文本格式和HTML格式的邮件正文,每一类邮件我们都需要分别创建HTML和纯文本格式的模板。对应上面的纯文本模板的HTML格式模板如下:

templates/emails/subscribe.html: HTML邮件模板

  1. <div style="width: 580px; padding: 20px;">
  2. <h3>Hello {{ name }},</h3>
  3. <p>Thank you for subscribing Flask Weekly!</p>
  4. <p>Enjoy the reading :)</p>
  5. <small style="color: #868e96;">
  6. Click here to <a href="{{ url_for('unsubscribe', _external=True) }}"></a>.
  7. </small>
  8. </div>

以通过Flask-Mail创建的发信函数为例,我们在发送邮件的函数中使用render_template()函数渲染邮件正文,并传入相应的变量,如下所示:

  1. from flask import render_template
  2. from flask_mail import Mail, Message
  3.  
  4. def send_subscribe_mail(subject, to, **kwargs):
  5. message = Message(subject, recipients = [to], sender = 'Flask Weekly <%s>' % os.getenv('MAIL_USERNAME'))
  6. message.body = render_template('emails/subscribe.txt', **kwargs)
  7. message.html = render_template('emails/subscribe.html', **kwargs)
  8. mail.send(message)

为了支持在调用函数时传入模板中需要的关键字参数,我们在send_mail()中接收可变长关键字参数(**kwargs)并传入render_template()函数。

当邮件中需要加入URL时(比如链接和图片),注意要生成完整的外部URL,而不是内部URL。这可以通过在url_for()函数将关键字参数_external设为True实现。

大多数程序需要发送多种不同类型的邮件,我们可以使用模板继承技术来为所有邮件创建一个包含基本样式的基模板。

在模板中发送163邮件:

app.py:
  1. #encoding=utf-8
  2. from flask import Flask, flash, redirect, url_for, render_template
  3. from wtforms import StringField, TextAreaField, SubmitField
  4. from flask_wtf import FlaskForm
  5. from wtforms.validators import DataRequired, Email
  6. from flask import render_template
  7. from flask_mail import Mail, Message
  8. import os
  9.  
  10. app = Flask(__name__)
  11. app.jinja_env.trim_blocks = True
  12. app.jinja_env.lstrip_blocks = True
  13.  
  14. app.config.update(
  15. SECRET_KEY = "SECRET KEY",
  16. MAIL_SERVER = os.getenv('MAIL_SERVER'),
  17. MAIL_PORT = 465,
  18. #MAIL_PORT = 587,
  19. #MAIL_USE_TLS = True,
  20. MAIL_USE_SSL = True,
  21. MAIL_USERNAME = os.getenv('MAIL_USERNAME'),
  22. MAIL_PASSWORD = os.getenv('MAIL_PASSWORD'),
  23. MAIL_DEFAULT_SENDER = (os.getenv('MAIL_USERNAME'))
  24. )
  25.  
  26. mail = Mail(app)
  27.  
  28. def send_mail(subject, to, body):
  29. message = Message(subject, recipients = [to], body = body)
  30. mail.send(message)
  31.  
  32. class SubscribeForm(FlaskForm):
  33. name = StringField('Name', validators=[DataRequired()])
  34. email = StringField('Email', validators = [DataRequired(),Email()]) #Email(): Validates an email address
  35. submit = SubmitField('Subscribe')
  36.  
  37. def send_subscribe_mail(subject, to, **kwargs):
  38. message = Message(subject, recipients = [to], sender = 'Flask Weekly <%s>' % os.getenv('MAIL_USERNAME'))
  39. message.body = render_template('emails/subscribe.txt', **kwargs)
  40. message.html = render_template('emails/subscribe.html', **kwargs)
  41. mail.send(message)
  42.  
  43. @app.route('/subscribe', methods = ['GET', 'POST'])
  44. def subscribe():
  45. form = SubscribeForm()
  46. if form.validate_on_submit():
  47. name = form.name.data
  48. email = form.email.data
  49. send_subscribe_mail('Subscribe Success!', email, name = name)
  50. flash('Confirmation email have been sent! Check your inbox.')
  51. return redirect(url_for('subscribe'))
  52. return render_template('subscribe.html', form = form)
  53.  
  54. if __name__ == '__main__':
  55. print app.config
  56. app.run(debug = True)
macros.html:
  1. {% macro form_field(field) %}
  2. {{ field.label }}<br>
  3. {% if field.flags.required -%}
  4. {{ field(required = 'required', **kwargs) }}<br>
  5. {%- else -%}
  6. {{ field(**kwargs) }}<br>
  7. {%- endif %}
  8. {% if field.errors -%}
  9. {% for error in field.errors -%}
  10. <small class="error">{{ error }}</small><br>
  11. {%- endfor %}
  12. {%- endif %}
  13. {% endmacro %}
base.html:
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. {% block head %}
  5. {% block metas %}
  6. <meta charset="utf-8">
  7. {% endblock metas %}
  8. <title>{% block title %} Form - HelloFlask {% endblock title %}</title>
  9. <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='favicon.ico') }}">
  10. {% block styles %}
  11. <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
  12. {% endblock styles %}
  13. {% endblock head %}
  14. </head>
  15. <body>
  16. <nav>
  17. {% block nav %}
  18. <ul>
  19. <li><a href="{{ url_for('basic') }}">Home</a></li>
  20. </ul>
  21. {% endblock %}
  22. </nav>
  23.  
  24. <main>
  25. {% for message in get_flashed_messages() %}
  26. <div class="alert">
  27. {{ message }}
  28. </div>
  29. {% endfor %}
  30. {% block content %}{% endblock %}
  31. </main>
  32. <footer>
  33. {% block footer %}
  34. <small> &copy; 2019 <a href="https://www.cnblogs.com/xiaxiaoxu/" title="xiaxiaoxu's blog">夏晓旭的博客</a> /
  35. <a href="https://github.com/xiaxiaoxu/hybridDrivenTestFramework" title="Contact me on GitHub">GitHub</a> /
  36. <a href="http://helloflask.com" title="A HelloFlask project">Learning from GreyLi's HelloFlask</a>
  37. </small>
  38. {% endblock %}
  39. </footer>
  40. {% block scripts %}{% endblock %}
  41. </body>
  42. </html>
subscribe.html
  1. {% macro form_field(field) %}
  2. {{ field.label }}<br>
  3. {% if field.flags.required -%}
  4. {{ field(required = 'required', **kwargs) }}<br>
  5. {%- else -%}
  6. {{ field(**kwargs) }}<br>
  7. {%- endif %}
  8. {% if field.errors -%}
  9. {% for error in field.errors -%}
  10. <small class="error">{{ error }}</small><br>
  11. {%- endfor %}
  12. {%- endif %}
  13. {% endmacro %}
templates/emails/subscribe.html:
  1. <div style="width: 580px; padding: 20px;">
  2. <h3>Hello {{ name }},</h3>
  3. <p>Thank you for subscribing Flask Weekly!</p>
  4. <p>Enjoy the reading :)</p>
  5. <small style="color: #868e96;">
  6. Click here to <a href="{{ url_for('subscribe', _external=True) }}"></a>.
  7. </small>
  8. </div>
templates/emails/subscribe.txt:

Hello {{ name }},
Thank you for subscribing Flask Weekly!
Enjoy the reading :)

Visit this link ro unsubscribe: {{ url_for('subscribe', _external = True) }}

static/style.css:
  1. body {
  2. margin: auto;
  3. width: 750px;
  4. }
  5.  
  6. nav ul {
  7. list-style-type: none;
  8. margin:;
  9. padding:;
  10. overflow: hidden;
  11. background-color: #333;
  12. }
  13.  
  14. nav li {
  15. float: left;
  16. }
  17.  
  18. nav li a {
  19. display: block;
  20. color: white;
  21. text-align: center;
  22. padding: 14px 16px;
  23. text-decoration: none;
  24. }
  25.  
  26. nav li a:hover {
  27. background-color: #111;
  28. }
  29.  
  30. main {
  31. padding: 10px 20px;
  32. }
  33.  
  34. footer {
  35. font-size: 13px;
  36. color: #888;
  37. border-top: 1px solid #eee;
  38. margin-top: 25px;
  39. text-align: center;
  40. padding: 20px;
  41.  
  42. }
  43.  
  44. .alert {
  45. position: relative;
  46. padding: 0.75rem 1.25rem;
  47. margin-bottom: 1rem;
  48. border: 1px solid #b8daff;
  49. border-radius: 0.25rem;
  50. color: #004085;
  51. background-color: #cce5ff;
  52. }
  53.  
  54. .btn {
  55. font-size: 14px;
  56. padding: 5px 10px;
  57. text-decoration: none;
  58. cursor: pointer;
  59. background-color: white;
  60. color: black;
  61. border: 2px solid #555555;
  62. }
  63.  
  64. .btn:hover {
  65. text-decoration: none;
  66. background-color: black;
  67. color: white;
  68. border: 2px solid black;
  69. }
  1.  
浏览器访问127.0.0.1:5000/subscribe

点击按钮后,稍等几秒钟,会有flash消息提示已经发了邮件了

到邮箱中查看,已经收到

flask 电子邮件进阶实践-用模板发送163邮件的更多相关文章

  1. flask_mail发送163邮件,报553错误的原因

    最近在练习用flask_mail发送163邮件时报错: reply: '553 authentication is required,163 smtp9,DcCowAD3eEQZ561caRiaBA- ...

  2. 使用python发送163邮件 qq邮箱

    使用python发送163邮件 def send_email(title, content): import smtplib from email.mime.multipart import MIME ...

  3. ABP 用SMTP 发送163邮件

    /// <summary> /// 发送 /// </summary> /// <param name="Subject">邮件标题</p ...

  4. python 发送163邮件

    可能还需要使用 邮箱第三方客户端的授权码. 网易163免费邮箱相关服务器信息 from email import encoders from email.header import Header fr ...

  5. java发送163邮件

    在服务挂掉后,可以采用发送邮件的方式来通知开发人员进行异常处理 import java.io.IOException; import java.util.Properties; import java ...

  6. 模板发送java邮件

    Creating email content using a templating library The code in the previous examples explicitly creat ...

  7. flask 电子邮件Flask-Mail

    电子邮件 在web程序中,经常会需要发送电子邮件.比如,在用户注册账户时发送确认邮件:定期向用户发送热门内容或是促销信息等等.在Web程序中发送电子邮件并不复杂,借助扩展Flask-Mail或是第三方 ...

  8. Selenium自动化发送163邮箱

    自动化发送163邮件 方法一: import time import datetime from selenium import webdriver from selenium.webdriver.s ...

  9. SpringBoot集成Thymeleaf发送Html邮件报错

    由于业务需求需要使用Thymeleaf作为模板发送Html邮件,开发调试过程中发生以下错误 org.thymeleaf.exceptions.TemplateInputException: Error ...

随机推荐

  1. webpack实现开发、测试、生产等环境的打包切换

    使用webpack构建的工程,在开发过程中不同环境的配置不同,在各种环境的打包切换过程中需要手动修改相关配置达到预期目的.但是每次都手动修改会比较麻烦,本文简单介绍如何通过对webpack进行配置,实 ...

  2. (87)Wangdao.com第二十天_JavaScript document 节点对象

    document 节点对象, 代表整个文档,每张网页都有自己的 document 对象. window.document 当浏览器开始加载文档时就存在了 正常的网页使用 document 或者 win ...

  3. php 识别二维码(转载)

    近段需要写一个通过PHP来识别二维码的功能,在网上查了很久才解决问题.以此来记录下来解决问题的方法. 最开始找的方法是一个叫 php-zbarcode 的扩展,自己照着网上的安装步骤安装了 Image ...

  4. textarea 中的换行符

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  5. px,em,rem的区别与用法

    别人总结的.个人觉得特别的好: http://www.w3cplus.com/css/when-to-use-em-vs-rem.html

  6. java.lang.NoClassDefFoundError: com/sun/image/codec/jpeg/JPEGCodec

    java.lang.NoClassDefFoundError: com/sun/image/codec/jpeg/JPEGCodec 这个类在 rt.jar 里面 本地开发,jre里有这个包,所以不会 ...

  7. 微信小程序填坑之page[pages/XXX/XXX] not found.May be caused by

    当页面出现   page[pages/XXX/XXX] not found.May be caused by :1. Forgot to add page route in app.json.2. I ...

  8. .NET Core 中基于 IHostedService 实现后台定时任务

    .NET Core 2.0 引入了 IHostedService ,基于它可以很方便地执行后台任务,.NET Core 2.1 则锦上添花地提供了 IHostedService 的默认实现基类 Bac ...

  9. Django学习之django自带的contentType表 GenericRelation GenericForeignKey

    Django学习之django自带的contentType表   通过django的contentType表来搞定一个表里面有多个外键的简单处理: 摘自:https://blog.csdn.net/a ...

  10. Vs Code 中文包设置

    首先打开Vs Code 然后点击扩展 下载中文包 安装中文包 在没打开任何文件的时候我们可以看到一些提示 这个时候使用快捷键 Ctrl + Shift + P  (显示所有命令),然后选择" ...