Flask-Mail - Sending email asynchronously, based on Flask-Cookiecutter

后端 未结 3 666
耶瑟儿~
耶瑟儿~ 2021-02-06 10:47

My flask project is based on Flask-Cookiecutter and I need to send emails asynchronously.

Function for sending email was configured by Miguel’s Tutorial and sending sync

相关标签:
3条回答
  • 2021-02-06 10:48

    Okay, i found solution for my question i posting it here for others developers:

    I create file: email.py with code:

    from threading import Thread
    from flask import current_app, render_template
    from flask_mail import Message
    from .extensions import mail
    from time import sleep    
    
    def send_async_email(app, msg):
        with app.app_context():
            # block only for testing parallel thread
            for i in range(10, -1, -1):
                sleep(2)
                print('time:', i)
            print('====> sending async')
            mail.send(msg)
    
    def send_email(to, subject, template, **kwargs):
        app = current_app._get_current_object()
        msg = Message(subject, recipients=[to])
        msg.html = render_template('emails/' + template, **kwargs)
        thr = Thread(target=send_async_email, args=[app, msg])
        thr.start()
        return thr
    

    my view.py:

    ...
    from app.email import send_email
    ...
    
    @blueprint.route('/mailer', methods=['GET', 'POST'])
    def mailer():
        user = current_user.full_name
        send_email(('name@gmail.com'),
                   'New mail', 'test.html',
                   user=user)
        return "Mail has been send."
    

    And when i call http://localhost:5000/mailer it starts countdown and after few seconds is mail sent.

    0 讨论(0)
  • 2021-02-06 10:58

    Move email send function to a background thread:

    from threading import Thread
    
    def send_async_email(app,msg):
           with current_app.app_context():
                   mail.send(msg)
    
    def send_email(to, subject, template, **kwargs):
           msg = Message(subject, recipients=[to])
           msg.html = render_template('emails/' + template, **kwargs)
           thr = Thread(target=send_async_email,args=[app,msg])
           thr.start()
           return thr
    
    0 讨论(0)
  • 2021-02-06 11:12

    You can move app = Flask(__name__) out of the application factory and place it at the module level. This allows you to pass the app instance with it's application context into your thread for sending the email. You'll likely need to change some imports in other areas to prevent circular dependencies, but it shouldn't be too bad.

    Here is an example of how you can do this using Flask-Mail and Flask-RESTful. It also shows how to use pytest for testing this.

    from flask import Flask
    
    from .extensions import mail
    from .endpoints import register_endpoints
    from .settings import ProdConfig
    
    # app context needs to be accessible at the module level
    # for the send_message.send_
    app = Flask(__name__)
    
    
    def create_app(config=ProdConfig):
        """ configures and returns the the flask app """
        app.config.from_object(config)
    
        register_extensions()
        register_endpoints(app)
    
        return app
    
    
    def register_extensions():
        """ connects flask extensions to the app """
        mail.init_app(app)
    

    And in your module for sending emails you would have something like this:

    from flask_mail import Message
    
    from app import app
    from app import mail
    from utils.decorators import async_task
    
    
    def send_email(subject, sender, recipients, text_body, html_body=None, **kwargs):
        app.logger.info("send_email(subject='{subject}', recipients=['{recp}'], text_body='{txt}')".format(sender=sender, subject=subject, recp=recipients, txt=text_body))
        msg = Message(subject, sender=sender, recipients=recipients, **kwargs)
        msg.body = text_body
        msg.html = html_body
    
        app.logger.info("Message(to=[{m.recipients}], from='{m.sender}')".format(m=msg))
        _send_async_email(app, msg)
    
    
    @async_task
    def _send_async_email(flask_app, msg):
        """ Sends an send_email asynchronously
        Args:
            flask_app (flask.Flask): Current flask instance
            msg (Message): Message to send
        Returns:
            None
        """
        with flask_app.app_context():
            mail.send(msg)
    


    (2019 comments)

    Note: I posted this years ago and I feel instantiating the flask object outside of the application factory is not ideal. The send_email function will need a flask instance to work, but you can instantiate a new flask app in the function (don't forget your config).

    I would guess that current_app may also work but I feel that might have side effects given that it would need to create a new app context with-in a current app context, seems wrong, but might work.

    A good option to consider: Look into celery and use RabbitMQ for your backend.
    For larger apps or volumes you may look into decoupling the mailing of emails into a different app via message broker like RabbitMQ. Look into Event Driven Design patterns. This may be attractive if you have multiple apps that will need a mailing service. This could be nice if your service needs to support audit logs, delivery recovery, etc.

    0 讨论(0)
提交回复
热议问题