The Flask Mega-Tutorial, Part XI: Email Support (2012)

Posted by
on under

(Great news! There is a new version of this tutorial!)

This is the eleventh article in the series in which I document my experience writing web applications in Python using the Flask microframework.

The goal of the tutorial series is to develop a decently featured microblogging application that demonstrating total lack of originality I have decided to call microblog.

NOTE: This article was revised in September 2014 to be in sync with current versions of Python and Flask.

Here is an index of all the articles in the series that have been published to date:

Recap

In the most recent installments of this tutorial we've been looking at improvements that mostly had to do with our database.

Today we are letting our database rest for a bit, and instead we'll look at another important function that most web applications have: the ability to send emails to its users.

In our little microblog application we are going to implement one email related function, we will send an email to a user each time he/she gets a new follower. There are several more ways in which email support can be useful, so we'll make sure we design a generic framework for sending emails that can be reused.

Configuration

Luckily for us, Flask already has an extension that handles email called Flask-Mail, and while it will not take us 100% of the way, it gets us pretty close.

Back when we looked at unit testing, we added configuration for Flask to send us an email should an error occur in the production version of our application. That same information is used for sending application related emails.

Just as a reminder, what we need is two pieces of information:

  • the email server that will be used to send the emails, along with any required authentication
  • the email address(es) of the admins

This is what we did in the previous article (file config.py):

# email server
MAIL_SERVER = 'your.mailserver.com'
MAIL_PORT = 25
MAIL_USERNAME = None
MAIL_PASSWORD = None

# administrator list
ADMINS = ['you@example.com']

It goes without saying that you have enter the details of an actual email server and administrator above before the application can actually send emails. We are not going to enhance the server setup to allow those that require an encrypted communication through TLS or SSL. For example, if you want the application to send emails via your gmail account you would enter the following:

# email server
MAIL_SERVER = 'smtp.googlemail.com'
MAIL_PORT = 465
MAIL_USE_TLS = False
MAIL_USE_SSL = True
MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')

# administrator list
ADMINS = ['your-gmail-username@gmail.com']

Note that the username and password are read from environment variables. You will need to set MAIL_USERNAME and MAIL_PASSWORD to your Gmail login credentials. Putting sensitive information in environment variables is safer than writing down the information on a source file.

We also need to initialize a Mail object, as this will be the object that will connect to the SMTP server and send the emails for us (file app/__init__.py):

from flask_mail import Mail
mail = Mail(app)

Let's send an email!

To learn how Flask-Mail works we'll just send an email from the command line. So let's fire up Python from our virtual environment and run the following:

>>> from flask_mail import Message
>>> from app import app, mail
>>> from config import ADMINS
>>> msg = Message('test subject', sender=ADMINS[0], recipients=ADMINS)
>>> msg.body = 'text body'
>>> msg.html = '<b>HTML</b> body'
>>> with app.app_context():
...     mail.send(msg)
....

The snippet of code above will send an email to the list of admins that are configured in config.py. The sender will be the first admin in the list. The email will have text and HTML versions, so depending on how your email client is setup you may see one or the other. Note that we needed to create an app_context to send the email. Recent releases of Flask-Mail require this. An application context is created automatically when a request is handled by Flask. Since we are not inside a request we have to create the context by hand just so that Flask-Mail can do its job.

Pretty, neat. Now it's time to integrate this code into our application!

A simple email framework

We will now write a helper function that sends an email. This is just a generic version of the above test. We'll put this function in a new source file that will be dedicated to our email support functions (file app/emails.py):

from flask_mail import Message
from app import mail

def send_email(subject, sender, recipients, text_body, html_body):
    msg = Message(subject, sender=sender, recipients=recipients)
    msg.body = text_body
    msg.html = html_body
    mail.send(msg)

Note that Flask-Mail support goes beyond what we are using. Bcc lists and attachments are available, for example, but we won't use them in this application.

Follower notifications

Now that we have the basic framework to send an email in place, we can write the function that sends out the follower notification (file app/emails.py):

from flask import render_template
from config import ADMINS

def follower_notification(followed, follower):
    send_email("[microblog] %s is now following you!" % follower.nickname,
               ADMINS[0],
               [followed.email],
               render_template("follower_email.txt", 
                               user=followed, follower=follower),
               render_template("follower_email.html", 
                               user=followed, follower=follower))

Do you find any surprises in here? Our old friend the render_template function is making an appearance. If you recall, we used this function to render all the HTML templates from our views. Like the HTML from our views, the bodies of email messages are an ideal candidate for using templates. As much as possible we want to keep logic separate from presentation, so emails will also go into the templates folder along with our views.

So we now need to write the templates for the text and HTML versions of our follower notification email. Here is the text version (file app/templates/follower_email.txt):

Dear {{ user.nickname }},

{{ follower.nickname }} is now a follower. Click on the following link to visit {{ follower.nickname }}'s profile page:

{{ url_for('user', nickname=follower.nickname, _external=True) }}

Regards,

The microblog admin

For the HTML version we can do a little bit better and even show the follower's avatar and profile information (file app/templates/follower_email.html):

<p>Dear {{ user.nickname }},</p>
<p><a href="{{ url_for('user', nickname=follower.nickname, _external=True) }}">{{ follower.nickname }}</a> is now a follower.</p>
<table>
    <tr valign="top">
        <td><img src="{{ follower.avatar(50) }}"></td>
        <td>
            <a href="{{ url_for('user', nickname=follower.nickname, _external=True) }}">{{ follower.nickname }}</a><br />
            {{ follower.about_me }}
        </td>
    </tr>
</table>
<p>Regards,</p>
<p>The <code>microblog</code> admin</p>

Note the _external=True argument to url_for in the above templates. By default, the url_for function generates URLs that are relative to the domain from which the current page comes from. For example, the return value from url_for("index") will be /index, while in this case we want http://localhost:5000/index. In an email there is no domain context, so we have to force fully qualified URLs that include the domain, and the _external argument is just for that.

The final step is to hook up the sending of the email with the actual view function that processes the "follow" (file app/views.py):

from .emails import follower_notification

@app.route('/follow/<nickname>')
@login_required
def follow(nickname):
    user = User.query.filter_by(nickname=nickname).first()
    # ...
    follower_notification(user, g.user)
    return redirect(url_for('user', nickname=nickname))

Now you can create two users (if you haven't yet) and make one follow the other to see how the email notification works.

So that's it? Are we done?

We could now pat ourselves in the back for a job well done and take email notifications out of our list of features yet to implement.

But if you played with the application for some time and paid attention you may have noticed that now that we have email notifications when you click the follow link it takes 2 to 3 seconds for the browser to refresh the page, whereas before it was almost instantaneous.

So what happened?

The problem is that Flask-Mail sends emails synchronously. The web server blocks while the email is being sent and only returns its response back to the browser once the email has been delivered. Can you imagine what would happen if we try to send an email to a server that is slow, or even worse, temporarily offline? Not good.

This is a terrible limitation, sending an email should be a background task that does not interfere with the web server, so let's see how we can fix this.

Asynchronous calls in Python

What we really want is for the send_email function to return immediately, while the work of sending the email is moved to a background process.

Turns out Python already has support for running asynchronous tasks, actually in more than one way. The threading and multiprocessing modules can both do this.

Starting a thread each time we need to send an email is much less resource intensive than starting a brand new process, so let's move the mail.send(msg) call into thread (file app/emails.py):

from threading import Thread
from app import app

def send_async_email(app, msg):
    with app.app_context():
        mail.send(msg)

def send_email(subject, sender, recipients, text_body, html_body):
    msg = Message(subject, sender=sender, recipients=recipients)
    msg.body = text_body
    msg.html = html_body
    thr = Thread(target=send_async_email, args=[app, msg])
    thr.start()

The send_async_email function now runs in a background thread. Because it is a separate thread, the application context required by Flask-Mail will not be automatically set for us, so the app instance is passed to the thread, and the application context is set up manually, like we did above when we sent an email from the Python console.

If you test the 'follow' function of our application now you will notice that the web browser shows the refreshed page before the email is actually sent.

So now we have asynchronous emails implemented, but what if in the future we need to implement other asynchronous functions? The procedure would be identical, but we would need to duplicate the threading code for each particular case, which is not good.

We can improve our solution by implementing a decorator. With a decorator the above code would change to this:

from .decorators import async

@async
def send_async_email(app, msg):
    with app.app_context():
        mail.send(msg)

def send_email(subject, sender, recipients, text_body, html_body):
    msg = Message(subject, sender=sender, recipients=recipients)
    msg.body = text_body
    msg.html = html_body
    send_async_email(app, msg)

Much nicer, right?

The code that allows this magic is actually pretty simple. We will put it in a new source file (file app/decorators.py):

from threading import Thread

def async(f):
    def wrapper(*args, **kwargs):
        thr = Thread(target=f, args=args, kwargs=kwargs)
        thr.start()
    return wrapper

And now that we indirectly have created a useful framework for asynchronous tasks we can say we are done!

Just as an exercise, let's consider how this solution would look using processes instead of threads. We do not want a new process started for each email that we need to send, so instead we could use the Pool class from the multiprocessing module. This class creates a specified number of processes (which are forks of the main process) and all those processes wait to receive jobs to run, given to the pool via the apply_async method. This could be an interesting approach for a busy site, but we will stay with the threads for now.

Final words

The source code for the updated microblog application is available below:

Download microblog-0.11.zip.

I've got a few requests for putting this application up on github or similar, which I think is a pretty good idea. I will be working on that in the near future. Stay tuned.

Thank you again for following me on this tutorial series. I look forward to see you on the next chapter.

Miguel

Become a Patron!

Hello, and thank you for visiting my blog! If you enjoyed this article, please consider supporting my work on this blog on Patreon!

110 comments
  • #51 buptmuye said

    Hi Miguel, thanks for your tutorial.
    When I use flask-mail 0.9.1, I still got the error "RuntimeError: working outside of application context". I remembered to add " with app.app_context():"

  • #52 Andras Tim said

    @buptmuye: Please, see the #49 comment from Andrew. This will fixes your problem.

  • #53 Ellen said

    Hi Miguel!

    Thanks so much for putting this tutorial together! I'm learning a lot working through it.

    I'm trying to set up an email server using my Yahoo account, and I'm getting the following error:

    Traceback (most recent call last):
    File "<stdin>", line 2, in <module>
    File "/Users/username/microblog/flask/lib/python2.7/site-packages/flask_mail.py", line 491, in send
    with self.connect() as connection:
    File "/Users/username/microblog/flask/lib/python2.7/site-packages/flask_mail.py", line 144, in enter
    self.host = self.configure_host()
    File "/Users/username/microblog/flask/lib/python2.7/site-packages/flask_mail.py", line 156, in configure_host
    host = smtplib.SMTP_SSL(self.mail.server, self.mail.port)
    File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/smtplib.py", line 777, in init
    SMTP.init(self, host, port, local_hostname, timeout)
    File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/smtplib.py", line 250, in init
    (code, msg) = self.connect(host, port)
    File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/smtplib.py", line 310, in connect
    self.sock = self._get_socket(host, port, self.timeout)
    File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/smtplib.py", line 783, in _get_socket
    new_socket = ssl.wrap_socket(new_socket, self.keyfile, self.certfile)
    File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/ssl.py", line 387, in wrap_socket
    ciphers=ciphers)
    File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/ssl.py", line 143, in init
    self.do_handshake()
    File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/ssl.py", line 305, in do_handshake
    self._sslobj.do_handshake()
    ssl.SSLError: [Errno 1] _ssl.c:504: error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol

    I also tried with my Gmail and got an "Authentication Required" error. Any ideas as to how to fix this?

    Thanks for the help and happy New Year!
    Ellen

  • #54 Miguel Grinberg said

    @Ellen: are you using the correct SMTP server and port for yahoo? I believe you need to use smtp.mail.yahoo.com for the server, and 465 for the port.

  • #55 Thomas said

    Hi Miguel,

    I get the same Working outside of application context error. Do you plan on updating this page with a solution to the problem?

    Thanks for a great tutorial.
    Thomas

  • #56 Miguel Grinberg said

    @Thomas: Do you get it when you try to send an email from the console, or when running the application? If the latter, please post a stack trace.

  • #57 Micah said

    So even after I downloaded and ran your version I still get this error

    xception in thread Thread-3:
    Traceback (most recent call last):
    File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/threading.py", line 810, in bootstrap_inner
    self.run()
    File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/threading.py", line 763, in run
    self.__target(self.__args, *self.__kwargs)
    File "/Users/micahweitzman/Desktop/microblog/app/emails.py", line 10, in send_async_email
    with app.app_context():
    File "/Users/micahweitzman/Desktop/microblog/flask/lib/python2.7/site-packages/werkzeug/local.py", line 338, in __getattr

    return getattr(self._get_current_object(), name)
    File "/Users/micahweitzman/Desktop/microblog/flask/lib/python2.7/site-packages/werkzeug/local.py", line 297, in _get_current_object
    return self.__local()
    File "/Users/micahweitzman/Desktop/microblog/flask/lib/python2.7/site-packages/flask/globals.py", line 34, in _find_app
    raise RuntimeError('working outside of application context')
    RuntimeError: working outside of application context

    Any help?

  • #58 Miguel Grinberg said

    @Micah: I believe I have addressed this problem. Please refresh from Github and let me know if emails work now.

  • #59 Philippe said

    I am coming very late to this but really enjoying it. Thansk a lot Miguel

    I have found that under Ubuntu I can't use Python3 so I am using the standard python 2.7 instead

    it has all worked well except in this section. After some investigation I found that in config.py I had to use these 2 line to define the username/password rather than the one you propose

    MAIL_USERNAME = 'MAIL_USERNAME'
    MAIL_PASSWORD = 'MAIL_PASSWORD'

    If I don't I get that error :

    File "/usr/lib/python2.7/smtplib.py", line 724, in sendmail
    raise SMTPSenderRefused(code, resp, from_addr)
    smtplib.SMTPSenderRefused: (530, '5.5.1 Authentication Required. Learn more at\n5.5.1 http://support.google.com/mail/bin/answer.py?answer=14257 q10sm29608967wjr.41 - gsmtp', u'phlippe@xxx.xxx')

    I guess this must be a specific python3 syntax?

  • #60 Miguel Grinberg said

    @Philippe: the code I suggest you to use simply reads the username and password from environment variables, so that you don't have to type your credentials in the source file. If you use my code, then you have to set the environment variables to the same values you now have in the code.

  • #61 Andromeda said

    At this part, on the "Let's send an email!" section, I can't accessing my MAIL_USERNAME and MAIL_PASSWORD. Here is the result:

    from flask.ext.mail import Message
    from app import app, mail
    from config import ADMINS
    msg = Message('test subject', sender=ADMINS[0], recipients=ADMINS)
    msg.body = 'text body'
    msg.html = 'HTML body'
    with app.app_context():
    ... mail.send(msg)
    ...
    Traceback (most recent call last):
    File "<stdin>", line 2, in <module>
    File "/home/andromeda/Projekte/Python/Tutorial/Flask/R02/microblog/flask/local/lib/python2.7/site-packages/flask_mail.py", line 492, in send
    message.send(connection)
    File "/home/andromeda/Projekte/Python/Tutorial/Flask/R02/microblog/flask/local/lib/python2.7/site-packages/flask_mail.py", line 427, in send
    connection.send(self)
    File "/home/andromeda/Projekte/Python/Tutorial/Flask/R02/microblog/flask/local/lib/python2.7/site-packages/flask_mail.py", line 192, in send
    message.rcpt_options)
    File "/usr/lib/python2.7/smtplib.py", line 724, in sendmail
    raise SMTPSenderRefused(code, resp, from_addr)
    smtplib.SMTPSenderRefused: (530, '5.5.1 Authentication Required. Learn more at\n5.5.1 http://support.google.com/mail/bin/answer.py?answer=14257 rx1sm1501842pbc.5 - gsmtp', u'andromedarshc@gmail.com')

    What's going wrong here, sir?

    Thank a lot in advance.

  • #62 Alex said

    FYI, due to the way Google protects Gmail, for this to work I needed to enable "Allow Less Secure Apps". https://www.google.com/settings/security/lesssecureapps (temporarily).

  • #63 Miguel Grinberg said

    Andromeda: did you set MAIL_USERNAME and MAIL_PASSWORD environment variables before running the application?

  • #64 Andromeda said

    Yes sir. Here is my mail server setting on my config.py file:

    <h1>email server</h1>

    MAIL_SERVER = 'smtp.googlemail.com'
    MAIL_PORT = 465
    MAIL_USE_TLS = False
    MAIL_USE_SSL = True
    MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
    MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')

    Thanks in advance.

  • #65 Miguel Grinberg said

    Andromeda: these program variables are initialized from variables of the same name set in the environment. So you need to do something like "export MAIL_USERNAME=<your username>" and the same for the password, and only after doing that you can start the application.

  • #66 Vinayak said

    Hi Miguel,
    Your tutorials are fantastic! Thank you. With these Flask is a breeze.

    Just one question, for security reasons I'd not like to use gmail to send the email but my localhost. Is there a way to do that in Flask? If you could point me to sample code or settings that would really help.

    Thanks again and keep up the good work Miguel,
    Vinayak.

  • #67 Miguel Grinberg said

    @Vinayak: the gmail server is a regular SMTP server. If you replace the settings with your server everything should work.

  • #68 Andy Strain said

    Under python 3, at least, the above import in app/views.py should be changed from:

    from emails import follower_notification

    to

    from .emails import follower_notification

    As for some of the SMTP issues about the application context, it is important for the indented spaces to be there. It should be:

    with app.app_context():
    ... mail.send(msg)
    ...

    and not:

    with app.app_context():
    ... mail.send(msg)
    ...

    Hope this helps!

  • #69 Miguel Grinberg said

    @Andy: thanks, I corrected the bad import. Note that this import is correct in the actual Github example. I have not found the badly indented code though, that's probably a copy/paste error on your part.

  • #70 Steven said

    @Miguel @Alex
    I also had to switch the settings for gmail at https://www.google.com/settings/security/lesssecureapps for sending emails to work. Is there a way around this? Or am I doing something wrong? We can't ask all of our users to switch their settings of course

  • #71 Miguel Grinberg said

    @Steven: Google introduced some security changes in its gmail accounts a few months ago. Even before this change, the Gmail servers are not intended to be used in production, since there are quotas that restrict volume use. In a production server you have to use your own email agent, or use an email service.

  • #72 st_kijo said

    Hey Miguel, thanks for the great work really.

    I have the same problem as that presented by Andromeda.
    I can verify that my environment configurations are accurate using SET ... and i've changed relaxed my security settings for gmail to 'allow access for less secure apps'.
    However, i keep getting the same error message, pasted below.

    Any help regarding this issue is much appreciated.

    127.0.0.1 - - [25/Sep/2015 21:59:23] "GET /auth/confirm HTTP/1.1" 302 -
    127.0.0.1 - - [25/Sep/2015 21:59:23] "GET / HTTP/1.1" 302 -
    127.0.0.1 - - [25/Sep/2015 21:59:23] "GET /auth/unconfirmed HTTP/1.1" 200 -
    send: 'ehlo Steven-PC.mak.ac.ug\r\n'
    reply: b'250-smtp.gmail.com at your service, [196.43.135.145]\r\n'
    reply: b'250-SIZE 35882577\r\n'
    reply: b'250-8BITMIME\r\n'
    reply: b'250-AUTH LOGIN PLAIN XOAUTH2 PLAIN-CLIENTTOKEN XOAUTH OAUTHBEARER\r\n'
    reply: b'250-ENHANCEDSTATUSCODES\r\n'
    reply: b'250-PIPELINING\r\n'
    reply: b'250-CHUNKING\r\n'
    reply: b'250 SMTPUTF8\r\n'
    reply: retcode (250); Msg: b'smtp.gmail.com at your service, [196.43.135.145]\nSIZE 35882577\n8BITMIME\nAUTH LOGIN PLAIN XOAUTH2 PLAIN-CLIENTTOKEN XOAUTH OAUTHBEARER\nENHANCEDSTATU
    SCODES\nPIPELINING\nCHUNKING\nSMTPUTF8'
    send: 'mail FROM:&#115;&#116;&#118;&#110;&#107;&#106;&#109;&#56;&#64;&#103;&#109;&#97;&#105;&#108;&#46;&#99;&#111;&#109; size=1781\r\n'
    reply: b'530-5.5.1 Authentication Required. Learn more at\r\n'
    reply: b'530 5.5.1 https://support.google.com/mail/answer/14257 d8sm9313296wiy.1 - gsmtp\r\n'
    reply: retcode (530); Msg: b'5.5.1 Authentication Required. Learn more at\n5.5.1 https://support.google.com/mail/answer/14257 d8sm9313296wiy.1 - gsmtp'
    send: 'rset\r\n'
    reply: b'250 2.1.5 Flushed d8sm9313296wiy.1 - gsmtp\r\n'
    reply: retcode (250); Msg: b'2.1.5 Flushed d8sm9313296wiy.1 - gsmtp'
    send: 'quit\r\n'
    reply: b'221 2.0.0 closing connection d8sm9313296wiy.1 - gsmtp\r\n'
    reply: retcode (221); Msg: b'2.0.0 closing connection d8sm9313296wiy.1 - gsmtp'
    Exception in thread Thread-2:
    Traceback (most recent call last):
    File "C:\Python34\Lib\threading.py", line 920, in _bootstrap_inner
    self.run()
    File "C:\Python34\Lib\threading.py", line 868, in run
    self._target(self._args, *self._kwargs)
    File "C:\Users\Steven\xamcode\flasky\app\email.py", line 9, in send_async_email
    mail.send(msg)
    File "C:\Users\Steven\xamcode\flasky\venv\lib\site-packages\flask_mail.py", line 492, in send
    message.send(connection)
    File "C:\Users\Steven\xamcode\flasky\venv\lib\site-packages\flask_mail.py", line 427, in send
    connection.send(self)
    File "C:\Users\Steven\xamcode\flasky\venv\lib\site-packages\flask_mail.py", line 192, in send
    message.rcpt_options)
    File "C:\Python34\Lib\smtplib.py", line 778, in sendmail
    raise SMTPSenderRefused(code, resp, from_addr)
    smtplib.SMTPSenderRefused: (530, b'5.5.1 Authentication Required. Learn more at\n5.5.1 https://support.google.com/mail/answer/14257 d8sm9313296wiy.1 - gsmtp', '=?utf-8?q?Flasky_Ad
    min?= &#115;&#116;&#118;&#110;&#107;&#106;&#109;&#56;&#64;&#103;&#109;&#97;&#105;&#108;&#46;&#99;&#111;&#109;')

    <h2>Server configurations</h2>

    MAIL_SERVER = 'smtp.gmail.com'
    MAIL_PORT = 465
    MAIL_USE_TLS = False
    MAIL_USE_SSL = True
    MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
    MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
    FLASKY_MAIL_SUBJECT_PREFIX = '[Flasky]'
    FLASKY_MAIL_SENDER = 'Flasky Admin &#115;&#116;&#118;&#110;&#107;&#106;&#109;&#56;&#64;&#103;&#109;&#97;&#105;&#108;&#46;&#99;&#111;&#109;'
    FLASKY_ADMIN = os.environ.get('FLASKY_ADMIN')

  • #73 Miguel Grinberg said

    @st_kijo: Gmail recently started migrating towards a different type of authentication for mail apps, a lot of email clients are affected. You can make your account work by following the instructions on this stackoverflow questions: http://stackoverflow.com/questions/17227532/gmail-530-5-5-1-authentication-required-learn-more-at.

  • #74 st_kijo said

    @ Miguel: Thanks for the help, but i'd configured that setting even earlier on and still, i get the same error report.

  • #75 Miguel Grinberg said

    @st_kijo: If you've configured your gmail account, then the problem must be in how you are setting the configuration. I really can't tell you what's wrong from the stack trace. You may need to debug it to see what authentication values are getting inside Flask-Mail and smtplib.

Leave a Comment