Веб-приложения отправляют электронные письма постоянно, и в этом уроке речь пойдет о том, как добавить инструмент для отправки email в приложение Flask.
В стандартной библиотеке Python есть модуль smtplib
, который можно использовать для отправки сообщений. Хотя сам модуль smtplib
не является сложным, он все равно требует кое-какой работы. Для облегчения процесса работы с ним было создано расширение Flask-Mail. Flask-Mail построен на основе модуля Python smtplib
и предоставляет простой интерфейс для отправки электронных писем. Он также предоставляет возможности по массовой рассылке и прикрепленным к сообщениям файлам. Установить Flask-Mail можно с помощью следующей команды:
(env) gvido@vm:~/flask_app$ pip install flask-mail
Чтобы запустить расширение, нужно импортировать класс Mail
из пакета flask_mail
и создать экземпляр класса Mail
:
#...
from flask_mail import Mail, Message
app = Flask(__name__)
app.debug = True
app.config['SECRET_KEY'] = 'a really really really really long secret key'
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:pass@localhost/flask_app_db'
manager = Manager(app)
manager.add_command('db', MigrateCommand)
db = SQLAlchemy(app)
migrate = Migrate(app, db)
mail = Mail(app)
#...
Дальше нужно указать некоторые параметры настройки, чтобы Flask-Mail знал, к какому SMTP-серверу подключаться. Для этого в файл main2.py
нужно добавить следующий код:
#...
app.config['SECRET_KEY'] = 'a really really really really long secret key'
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:pass@localhost/flask_app_db'
app.config['MAIL_SERVER'] = 'smtp.googlemail.com'
app.config['MAIL_PORT'] = 587
app.config['MAIL_USE_TLS'] = True
app.config['MAIL_USERNAME'] = 'test@gmail.com' # введите свой адрес электронной почты здесь
app.config['MAIL_DEFAULT_SENDER'] = 'test@gmail.com' # и здесь
app.config['MAIL_PASSWORD'] = 'password' # введите пароль
manager = Manager(app)
manager.add_command('db', MigrateCommand)
db = SQLAlchemy(app)
mail = Mail(app)
#...
В данном случае используется SMTP-сервер Google. Стоит отметить, что Gmail позволяет отправлять только 100-150 сообщений в день. Если этого недостаточно, то стоит обратить внимание на альтернативы: SendGrid или MailChimp.
Вместо того чтобы напрямую указывать email и пароль в приложении, как это было сделано ранее, лучше хранить их в переменных среды. В таком случае, если почта или пароль поменяются, не будет необходимости обновлять код. О том, как это сделать, будет рассказано в следующих уроках.
Основы Flask-Mail
Для составления электронного письма, нужно создать экземпляр класса Message
:
msg = Message("Subject", sender="sender@example.com", recipients=['recipient_1@example.com'])
Если при настройке параметров конфигурации MAIL_DEFAULT_SENDER
был указан, то при создании экземпляра Message
передавать значение sender
не обязательно.
msg = Message("Subject", recipients=['recipient@example.com'])
Для указания тела письма необходимо использовать атрибут body
экземпляра Message
:
msg.body = "Mail body"
Если оно состоит из HTML, передавать его следует атрибуту html
.
msg.html = "<p>Mail body</p>"
Наконец, отправить сообщение можно, передав экземпляр Message
метод send()
экземпляра Mail
:
mail.send(msg)
Пришло время проверить настройки, отправив email с помощью командной строки.
Отправка тестового сообщения
Откроем терминал, чтобы ввести следующие команды:
(env) overiq@vm:~/flask_app$ python main2.py shell
>>>
>>> from main2 import mail, Message
>>> # введите свою почту
>>> msg = Message("Subject", recipients=["you@mail.com"])
>>> msg.html = "<h2>Email Heading</h2>\n<p>Email Body</p>"
>>>
>>> mail.send(msg)
>>>
Если операция прошла успешно, то на почту должно прийти следующее сообщение с темой “Subject”:
Email Heading
Email Body
Стоит заметить, что отправка через SMTP-сервер Gmail не сработает, если не отключить двухфакторную аутентификацию и не разрешить небезопасным приложениям получать доступ к аккаунту.
Интеграция email в приложение
Сейчас когда пользователь отправляет обратную связь, она сохраняется в базу данных, сам пользователь получает уведомление о том, что его сообщение было отправлено, и на этом все. Но в идеале приложение должно уведомлять администраторов о полученной обратной связи. Это можно сделать. Откроем main2.py
, чтобы изменить функцию представления contact()
так, чтобы она отправляла сообщения:
#...
@app.route('/contact/', methods=['get', 'post'])
def contact():
#...
db.session.commit()
msg = Message("Feedback", recipients=[app.config['MAIL_USERNAME']])
msg.body = "You have received a new feedback from {} <{}>.".format(name, email)
mail.send(msg)
print("\nData received. Now redirecting ...")
#...
Дальше нужно запустить сервер и зайти на https://localhost:5000/contact/
. Заполним и отправим форму. Если все прошло успешно, должен прийти email.
Можно было обратить внимание на задержку между отправкой обратной связи и появлением уведомления о том, что она была отправлена успешно. Проблема в том, что метод mail.send()
блокирует исполнение функции представления на несколько секунд. В результате, код с перенаправлением страницы не будет исполнен до тех пор, пока не вернется метод mail.send()
. Решить это можно с помощью потоков (threads).
Также прямо сейчас можно слегка изменить код отправки сообщений. На данный момент если email потребуется отправить в любом другом месте кода, нужно будет копировать и вставлять те самые строки. Но можно сохранить несколько строк, заключив логику отправки сообщений в функцию.
Откроем main2.py
, чтобы добавить следующий код перед index
:
#...
from threading import Thread
#...
def shell_context():
import os, sys
return dict(app=app, os=os, sys=sys)
manager.add_command("shell", Shell(make_context=shell_context))
def async_send_mail(app, msg):
with app.app_context():
mail.send(msg)
def send_mail(subject, recipient, template, **kwargs):
msg = Message(subject, sender=app.config['MAIL_DEFAULT_SENDER'], recipients=[recipient])
msg.html = render_template(template, **kwargs)
thr = Thread(target=async_send_mail, args=[app, msg])
thr.start()
return thr
@app.route('/')
def index():
return render_template('index.html', name='Jerry')
#...
Было сделано несколько изменений. Функция send_mail()
теперь включает в себя всю логику отправки email. Она принимает тему письма, получателя и шаблон сообщения. Ей также можно передать дополнительные аргументы в виде аргументов-ключевых слов. Почему именно так? Дополнительные аргументы представляют собой данные, которые нужно передать шаблону. На 17 строке рендерится шаблон, а его результат передается атрибуту msg.html
. На строке 18 создается объект Thread
. Это делается с помощью передачи названия функции и аргументов функции, с которыми она должна быть вызвана. Следующая строка запускает потоки. Когда поток запускается, вызывается async_send_mail()
. Теперь самое интересное. Впервые в коде работа происходит вне приложения (то есть, вне функции представления) в новом потоке. with app.app_context()
: создает контекст приложения, а mail.send()
отправляет email.
Дальше нужно создать шаблон для сообщения обратной связи. В папке templates
необходимо создать папку mail
. Она будет хранить шаблоны для электронных писем. Внутри папки необходимо создать шаблон feedback.html
со следующим кодом:
<p>You have received a new feedback from {{ name }} <{{ email }}> </p>
Теперь нужно изменить функцию представления contact()
, чтобы использовать функцию send_mail()
:
После этого нужно снова зайти на https://localhost:5000/contact
, заполнить форму и отправить ее. В этот раз задержки не будет.