Reputation: 53950
I am sending emails from my Flask app with Flask-Mail extension. It runs send() method synchronously and I have to wait until it sends the message. How can I make it run in background?
Upvotes: 3
Views: 5091
Reputation: 5187
Use Flask-Executor. Full disclosure, I wrote it myself to solve this exact problem.
Why?
Here's what it looks like:
from flask import Flask, current_app
from flask_executor import Executor
from flask_mail import Mail, Message
app = Flask(__name__)
# Set email server/auth configuration in app.config[]
executor = Executor(app)
mail = Mail(app)
def send_email(to, subject, message_text, message_html):
msg = Message(subject, sender=current_app.config['MAIL_USERNAME'], recipients=[to])
msg.body = message_text
msg.html = message_html
mail.send(msg)
@app.route('/signup')
def signup():
# My signup form logic
future = executor.submit(send_email, '[email protected]', 'My subject', 'My text message', '<b>My HTML message</b>')
print(future.result())
return 'ok'
if __name__ == '__main__':
app.run()
Basically, you write your send_email
function as though you were running regular inline logic, and submit it to the executor. However many emails you send, only the max number of threads defined in the executor (5* number of CPU cores by default) will run, and any overflow in requests to send_email
will be queued.
Overall your code stays cleaner and you don't need to write a bunch of wrapper code for every async function you want to run.
Upvotes: 4
Reputation: 465
I would like to simplify Marboni's code so take a look here.
import threading
from flask import copy_current_request_context
from flask_mail import Message
from app import app, mail
def create_message(recipient, subject, body):
if not recipient:
raise ValueError('Target email not defined.')
subject = subject.encode('utf-8')
body = body.encode('utf-8')
return Message(subject, [recipient], body, sender=app.config['MAIL_USERNAME'] or "[email protected]")
def send_async(recipient, subject, body):
message = create_message(recipient, subject, body)
@copy_current_request_context
def send_message(message):
mail.send(message)
sender = threading.Thread(name='mail_sender', target=send_message, args=(message,))
sender.start()
Upvotes: 1
Reputation: 2459
It's not so complex - you need to send mail in another thread, so you will not block the main thread. But there is one trick.
Here is my code that renders template, creating mail body, and allows to send it both synchronously and asynchronously:
mail_sender.py
import threading
from flask import render_template, copy_current_request_context, current_app
from flask_mail import Mail, Message
mail = Mail()
def create_massege(to_email, subject, template, from_email=None, **kwargs):
if not from_email:
from_email = current_app.config['ROBOT_EMAIL']
if not to_email:
raise ValueError('Target email not defined.')
body = render_template(template, site_name=current_app.config['SITE_NAME'], **kwargs)
subject = subject.encode('utf-8')
body = body.encode('utf-8')
return Message(subject, [to_email], body, sender=from_email)
def send(to_email, subject, template, from_email=None, **kwargs):
message = create_massege(to_email, subject, template, from_email, **kwargs)
mail.send(message)
def send_async(to_email, subject, template, from_email=None, **kwargs):
message = create_massege(to_email, subject, template, from_email, **kwargs)
@copy_current_request_context
def send_message(message):
mail.send(message)
sender = threading.Thread(name='mail_sender', target=send_message, args=(message,))
sender.start()
Pay your attention to @copy_current_request_context
decorator. It's required because Flask-Mail inside uses request context. If we will run it in the new thread, context will be missed. We can prevent this decorating function with @copy_current_request_context
- Flask will push context when function will be called.
To use this code you also need to initialize mail
object with your Flask application:
run.py
app = Flask('app')
mail_sender.mail.init_app(app)
Upvotes: 9