I am using the Flask-Mail library for my Flask application to send a default welcome email to the user when they sign up to be added to the newsletter. After debugging the library I found that it can only handle one connection at a time to send a message and will then automatically close the connection. If the backend sends an email to another user while a connection is still open then it throws this exception: raise SMTPServerDisconnected("Connection unexpectedly closed: " smtplib.SMTPServerDisconnected: Connection unexpectedly closed: [WinError 10054] An existing connection was forcibly closed by the remote host
. I want to be able to queue the mail Mail library to send a new message to another recipient after the connection has closed but currently it keeps throwing the error I mentioned above when I attempt to queue the function to send a message.
worker.py:
import os
import redis
from rq import Worker, Queue, Connection
listen = ['high', 'default', 'low']
redis_url = os.environ.get('REDISTOGO_URL')
conn = redis.from_url(redis_url)
if __name__ == '__main__':
with Connection(conn):
worker = Worker(map(Queue, listen))
worker.work()
user.routes.py
from flask import request, Blueprint, redirect, render_template
from flask_app import mail, db
from flask_app.users.forms import NewsLetterRegistrationForm
from flask_app.models import User
from flask_mail import Message
from rq import Queue
from worker import conn
import os, time
users = Blueprint("users", __name__)
queue = Queue(connection=conn)
@users.route("/newsletter-subscribe", methods=["GET", "POST"])
def newsletter_subscribe():
form = NewsLetterRegistrationForm()
if form.validate_on_submit():
user = User(name=form.name.data, email=form.email.data)
db.session.add(user)
db.session.commit()
queue.enqueue(send_welcome_email(user))
return "Success"
return "Failure"
def send_welcome_email(user):
with mail.connect() as con:
html = render_template("welcome-email.html", name=user.name)
subject = "Welcome!"
msg = Message(
subject=subject,
recipients=[user.email],
html=html
)
con.send(msg)
main.routes.py
from flask import render_template, session, request, current_app, Blueprint, redirect, url_for, json, make_response
from flask_app.users.forms import NewsLetterRegistrationForm
import os
main = Blueprint("main", __name__)
@main.route("/", methods=["GET"])
def index():
return render_template("index.html", title="Home")
@main.route("/example", methods=["GET"])
def example():
return render_template("example.html", title="example")
@main.context_processor
def inject_template_scope():
injections = dict()
form = NewsLetterRegistrationForm()
injections.update(form=form)
return injections
_init_.py
from logging.config import dictConfig
from flask import Flask, url_for, current_app
from flask_bcrypt import Bcrypt
from flask_sqlalchemy import SQLAlchemy
from flask_talisman import Talisman
from flask_compress import Compress
from flask_mail import Mail
import os
config = {
"SECRET_KEY": os.environ.get("SECRET_KEY"),
"DEBUG": True,
"SQLALCHEMY_DATABASE_URI": os.environ.get("DATABASE_URL"),
"SQLALCHEMY_TRACK_MODIFICATIONS": False,
"SQLALCHEMY_ECHO": False,
"MAIL_SERVER": "mail.privateemail.com",
"MAIL_PORT": 587,
"MAIL_USE_SSL": False,
"MAIL_USE_TLS": True,
"MAIL_USERNAME": "[email protected]",
"MAIL_PASSWORD": os.environ.get("NEWS_MAIL_PASSWORD"),
"MAIL_DEFAULT_SENDER": "[email protected]"
}
talisman = Talisman()
db = SQLAlchemy()
bcrypt = Bcrypt()
compress = Compress()
mail = Mail()
app = Flask(__name__)
def create_app():
app.config.from_mapping(config)
talisman.init_app(app)
db.init_app(app)
bcrypt.init_app(app)
compress.init_app(app)
mail.init_app(app)
from flask_app.users.routes import users
app.register_blueprint(users)
with app.app_context():
db.create_all()
return app
run.py
from flask_app import create_app
Error Log:
Traceback (most recent call last):
File "C:UsersuserAppDataLocalProgramsPythonPython38-32Libsmtplib.py", line 391, in getreply
line = self.file.readline(_MAXLINE + 1)
File "C:UsersuserAppDataLocalProgramsPythonPython38-32Libsocket.py", line 669, in readinto
return self._sock.recv_into(b)
ConnectionResetError: [WinError 10054] An existing connection was forcibly closed by the remote host
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "C:UsersuserAppDataLocalProgramsPythonPython38-32Libsite-packagesflaskapp.py", line 2464, in __call__
return self.wsgi_app(environ, start_response)
File "C:UsersuserAppDataLocalProgramsPythonPython38-32Libsite-packagesflaskapp.py", line 2450, in wsgi_app
response = self.handle_exception(e)
File "C:UsersuserAppDataLocalProgramsPythonPython38-32Libsite-packagesflaskapp.py", line 1867, in handle_exception
reraise(exc_type, exc_value, tb)
File "C:UsersuserAppDataLocalProgramsPythonPython38-32Libsite-packagesflask_compat.py", line 39, in reraise
raise value
File "C:UsersuserAppDataLocalProgramsPythonPython38-32Libsite-packagesflaskapp.py", line 2447, in wsgi_app
response = self.full_dispatch_request()
File "C:UsersuserAppDataLocalProgramsPythonPython38-32Libsite-packagesflaskapp.py", line 1952, in full_dispatch_request
rv = self.handle_user_exception(e)
File "C:UsersuserAppDataLocalProgramsPythonPython38-32Libsite-packagesflaskapp.py", line 1821, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "C:UsersuserAppDataLocalProgramsPythonPython38-32Libsite-packagesflask_compat.py", line 39, in reraise
raise value
File "C:UsersuserAppDataLocalProgramsPythonPython38-32Libsite-packagesflaskapp.py", line 1950, in full_dispatch_request
rv = self.dispatch_request()
File "C:UsersuserAppDataLocalProgramsPythonPython38-32Libsite-packagesflaskapp.py", line 1936, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "C:UserWork Stuffexample.comflask_appusersroutes.py", line 18, in newsletter_subscribe
send_welcome_email(user, request.host_url)
File "C:UserWork Stuffexample.comflask_appusersroutes.py", line 42, in send_welcome_email
with mail.connect() as con:
File "C:UsersuserAppDataLocalProgramsPythonPython38-32Libsite-packagesflask_mail.py",
line 144, in __enter__
self.host = self.configure_host()
File "C:UsersuserAppDataLocalProgramsPythonPython38-32Libsite-packagesflask_mail.py",
line 158, in configure_host
host = smtplib.SMTP(self.mail.server, self.mail.port)
File "C:UsersuserAppDataLocalProgramsPythonPython38-32Libsmtplib.py", line 253, in __init__
(code, msg) = self.connect(host, port)
File "C:UsersuserAppDataLocalProgramsPythonPython38-32Libsmtplib.py", line 341, in connect
(code, msg) = self.getreply()
File "C:UsersuserAppDataLocalProgramsPythonPython38-32Libsmtplib.py", line 394, in getreply
raise SMTPServerDisconnected("Connection unexpectedly closed: "
smtplib.SMTPServerDisconnected: Connection unexpectedly closed: [WinError 10054] An existing connection was forcibly closed by the remote host
2
Answers
It seems to me that your mail server is closing the connection because you are making more requests than its configuration allows. If you are using a third party mail provider you might want to check if the service you use offers any way to send bulk emails, e.g. through an API or file upload. Or if they have a way to change that configuration for you.
If that is not possible:
One solution would be to make a blocking call (
time.sleep()
) in order for you to lower the frequency at which you send your mails:Another solution would be to wrap your code in a try catch block and on exceptions wait for a while before trying to send the email again.
I’ve run into a similar issue with my provider (privateemail), one way I got it to work is blocking
time.sleep()
this however will cause requests to hand while you are sending your e-mails which is rather bad, a but if you don’t mind it you can make something like thisAnother better solution is to make an email process with celery so your code doesn’t become blocking