Flask-Mail Mail() instantiated twice - python

I am trying to understand this tutorial code.
from flask import Flask
from flask_mail import Mail, Message
app =Flask(__name__)
mail=Mail(app) # <-- This
app.config['MAIL_SERVER']='smtp.gmail.com'
app.config['MAIL_PORT'] = 465
app.config['MAIL_USERNAME'] = 'yourId#gmail.com'
app.config['MAIL_PASSWORD'] = '*****'
app.config['MAIL_USE_TLS'] = False
app.config['MAIL_USE_SSL'] = True
mail = Mail(app) # <-- This
#app.route("/")
def index():
msg = Message('Hello', sender = 'yourId#gmail.com', recipients = ['id1#gmail.com'])
msg.body = "Hello Flask message sent from Flask-Mail"
mail.send(msg)
return "Sent"
if __name__ == '__main__':
app.run(debug = True)
At the line 5 and 13, Mail object are instantiated and assigned to mail.
Even if I comment out the first instantiation in line 5, I can still send emails, so can I say it is just a typo, or it is necessary?

The first mail=Mail(app) is not needed. The primary fuctionality in the Mail() constructor is to read the app configuration. So since the appropriate app config variables are not set before line 5, the first Mail() object would likely not even work.

Related

Authentication error when I send email from Yandex using Flask

I want to send emails from Yandex using Flask but I get "authentication failed: This user does not have access rights to this service".
I did this:
from flask import Flask, render_template, redirect, request
from flask_mail import Mail, Message
app=Flask(__name__)
mail = Mail(app)
app.config['MAIL_SERVER'] = 'smtp.yandex.com'
app.config['MAIL_PORT'] = 465
app.config['MAIL_USE_TLS'] = False
app.config['MAIL_USE_SSL'] = True
app.config['MAIL_USERNAME'] = 'my-username#yandex.com'
app.config['MAIL_PASSWORD'] = 'my-password'
mail = Mail(app)
#app.route('/send-email')
def send_mail():
msg = Message('This email was sent by me from Flask', sender='my-username#yandex.com',recipients=['rec#gmail.com'])
msg.body = "This is the email body, I just wanted to test the flask email option, and see how doest it work."
mail.send(msg)
return 'Email sent!'
I was expecting the message to be sent, but I got the authentification error.
PS: My email and password are correct.

How to send mails with Flask-Mail in non-route scripts

I have a Flask server.
In route.py I have this:
from flask_mail import Mail
app = flask.Flask(__name__, static_folder='static')
app.config['MAIL_SERVER']='smtp.gmail.com'
app.config['MAIL_PORT'] = 465
app.config['MAIL_USERNAME'] = 'source#gmail.com'
app.config['MAIL_PASSWORD'] = 'password.'
app.config['MAIL_USE_TLS'] = False
app.config['MAIL_USE_SSL'] = True
mail= Mail(app)
I want to send mail in a function situated in another script using flask_mail.Message() and mail.send, how can I do it?
have a look at this example:
email.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)
source: https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-x-email-support
(Great tutorial on flask in general)

Flask-Mail [SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1123)

I'm developing a Flask application that implements a user registration system. The application uses Flask-Mail and itsdangerous to confirm a user's registration and reset their password via email. I configured Flask-Mail to use the recommended server settings provided by the email host that I'm using.
MAIL_PORT = 587
MAIL_USE_SSL = False
MAIL_USE_TLS = True
At first, things were working fine; I could submit emails without any issues. However, seemingly without changing any configuration settings, I now receive the following error when attempting to submit an email using Flask-Mail:
[SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1123)
I'm not sure where the problem is, and I am wondering if something has changed on the email provider's end? I have tried setting MAIL_PORT = 25 with MAIL_USE_SSL=False and MAIL_USE_TLS=False; and, MAIL_PORT = 465 with MAIL_USE_SSL=True and MAIL_USE_TLS=False as well. Using the former, I receive the same error as with port 587, but using the latter I'm receiving STARTTLS extension not supported by server.
I'm running the Flask app in development mode at localhost:5000. Here's some of my configuration settings and code:
config.py
SECRET_KEY = 'verysecret'
MAIL_SERVER = "smtp.mymailservice.com"
MAIL_PORT = 587
MAIL_USE_SSL = False
MAIL_USE_TLS = True
MAIL_USERNAME = "myemail#myhostname.com"
MAIL_PASSWORD = "mypassword"
MAIL_DEFAULT_SENDER = 'Brand <noreply#myhostname.com>'
app/mailing.py
from flask_mail import Message
from flask import current_app
from .extensions import mail
def send_email(to, subject, template):
msg = Message(
subject,
recipients=[to],
html=template,
sender=current_app.config["MAIL_DEFAULT_SENDER"]
)
mail.send(msg)
app/users/routes.py
(One of the routes where I receive the error)
from flask import (
render_template, session, request, redirect, url_for, g, jsonify, flash
)
import uuid
from passlib.hash import sha256_crypt
from app.mailing import send_email
from app.extensions import db
from app.users import bp
from app.users.forms import *
from app.users.models import *
from app.users.token import *
#bp.route('/register', methods=['POST', 'GET'])
def register():
# Initialize the Register Form
form = RegisterForm()
# If the submitted form is valid
if form.validate_on_submit():
# Check to see if a user already exists with this email address
user = User.query.filter_by(email=form.email.data).first()
# If there is not a user with this email address, create a new user
if not user:
new_user = User(public_id=str(uuid.uuid4()),
email=form.email.data,
password=sha256_crypt.encrypt(
(form.password.data)),
first_name=form.firstname.data,
last_name=form.lastname.data
)
db.session.add(new_user)
db.session.commit()
token = generate_confirmation_token(new_user.email)
confirm_url = url_for("users.confirm_email",
token=token, _external=True)
html = render_template('confirm_email.html',
confirm_url=confirm_url)
subject = "Please confirm your email"
try:
send_email(new_user.email, subject, html)
flash("A confirmation email has been sent to you. Please verify your email address to activate your account.", category="success")
except Exception as e:
flash(
"There was a problem sending the confirmation email. Please try again later.", category="danger")
print(e)
session["user_id"] = new_user.public_id
session["email"] = new_user.email
session["name"] = new_user.first_name
flash("Thanks for registering!", category="success")
return redirect(url_for('users.unconfirmed'))
else:
flash("There is already an account associated with this email address. Log in, or use a different email address.")
return render_template("register_user.html", form=form)
app/extensions.py
from flask_mail import Mail
from flask_bootstrap import Bootstrap
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
bootstrap = Bootstrap()
mail = Mail()
app/init.py
from flask import Flask
from config import Config, DevelopmentConfig
from .errors import (
page_not_found, forbidden, internal_server_error
)
from .extensions import (
db, mail, bootstrap
)
def create_app(config_class=DevelopmentConfig):
app = MyFlask(__name__)
# Set Configuration
app.config.from_object(config_class)
# Register extensions
# Initialize Boostrap-Flask
bootstrap.init_app(app)
# Initialize Flask-SQLAlchemy
db.init_app(app)
# Initialize Flask-Mail
mail.init_app(app)
# Register error views
app.register_error_handler(404, page_not_found)
app.register_error_handler(403, forbidden)
app.register_error_handler(500, internal_server_error)
with app.app_context():
# register blueprints
from app.main import bp as bp_main
app.register_blueprint(bp_main)
from app.users import bp as bp_users
app.register_blueprint(bp_users)
return app
This answer was almost there but not quite for me. Even more TL:DR.
Put this in your config.py file and forget about the rest...
class Config:
MAIL_USE_TLS = True
MAIL_USE_SSL = False
More details...
You probably have a config.py file that looks like this:
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY')
SQLALCHEMY_DATABASE_URI = 'sqlite:///site.db' # os.environ.get('DATABASE_URI')
MAIL_SERVER = os.environ.get('MAIL_SERVER')
MAIL_PORT = os.environ.get('MAIL_PORT')
MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS')
MAIL_USE_SSL = os.environ.get('MAIL_USE_SSL')
MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
MAIL_DEFAULT_SENDER = os.environ.get('MAIL_DEFAULT_SENDER')
And then you think you can have something like this in your VSCode debug config or the environment of your server:
"env": {
"MAIL_USE_SSL":"true",
"MAIL_USE_TLS":"true",
Well that doesn't work because of the answer from #serrobit because "true" in VSCode turns into a str instead of a Python True.
So back to the start, hard code TLS to True and SSL to False in the config.py file and go spend time on something useful.
I figured out what was going on. Evidently, you can pass in non-boolean types for MAIL_USE_TLS and MAIL_USE_SSL when initializing the Mail object from Flask-Mail. This becomes a problem when the Connection object calls configure_host() and conditionally checks if self.mail.use_ssl.
Thus, as long as self.mail.use_ssl is not None, the method will set host = smtplib.SMTP_SSL(self.mail.server, self.mail.port), which in my case, lead to [SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1123) because mail.port was set to 587.
tl;dr
Ensure the configuration variables for your Flask app are set to the appropriate type, especially if you are using environment variables, as those will always be of type str when accessing them via the os.environ dict.
flask_mail.py
class Connection(object):
"""Handles connection to host."""
def __init__(self, mail):
self.mail = mail
def __enter__(self):
if self.mail.suppress:
self.host = None
else:
self.host = self.configure_host()
self.num_emails = 0
return self
def __exit__(self, exc_type, exc_value, tb):
if self.host:
self.host.quit()
def configure_host(self):
## PROBLEM OCCURRED HERE BECAUSE type(self.mail.use_ssl) = <class 'str'> ##
if self.mail.use_ssl:
host = smtplib.SMTP_SSL(self.mail.server, self.mail.port)
else:
host = smtplib.SMTP(self.mail.server, self.mail.port)
host.set_debuglevel(int(self.mail.debug))
if self.mail.use_tls:
host.starttls()
if self.mail.username and self.mail.password:
host.login(self.mail.username, self.mail.password)
return host
Change this in your config.py:
class Config:
MAIL_USE_TLS = bool(strtobool(os.environ.get('MAIL_USE_TLS', 'False')))
MAIL_USE_SSL = bool(strtobool(os.environ.get('MAIL_USE_SSL', 'False')))

Email not sending on create_user

I am trying to use flask-security and have become helplessly stuck trying to send an email when create_user is called.
The user is being created. The roles are working, however, I can not find any documentation explaining how to send an email to the user when they sign up.
To keep things simple I am just using the code from the doc's to create a user before first request.
# Create a user to test with
#app.before_first_request
def create_user():
db.create_all()
# Create the Roles "admin" and "office_owner" -- unless they already exist
user_datastore.find_or_create_role(name='admin', description='Administrator')
user_datastore.find_or_create_role(name='office_owner', description='Office owner')
if not user_datastore.get_user('test#gmail.com'):
user_datastore.create_user(email='test#gmail.com', password=flask_security.utils.hash_password('password'))
# Commit any database changes; the User and Roles must exist before we can add a Role to the User
db.session.commit()
db.session.commit()
Here is my flask-mail settings
from flask import Flask
from flask_mail import Mail, Message
import os
mail_keys = {
'password_key': os.environ['EMAIL_PASSWORD'],
}
app =Flask(__name__)
mail=Mail(app)
app.config['MAIL_SERVER']='smtp.sendgrid.net'
app.config['MAIL_PORT'] = 465
app.config['MAIL_USERNAME'] = 'apikey'
app.config['MAIL_PASSWORD'] = mail_keys['password_key']
app.config['MAIL_USE_TLS'] = False
app.config['MAIL_USE_SSL'] = True
mail=Mail(app)
config.py is
import os
basedir = os.path.abspath(os.path.dirname(__file__))
class Config(object):
SQLALCHEMY_DATABASE_URI='postgresql://postgres///'
SQLALCHEMY_TRACK_MODIFICATIONS = False
SECURITY_PASSWORD_SALT = 'hjdsafjkhalkj'
SECURITY_PASSWORD_HASH='bcrypt'
SECURITY_CONFIRMABLE=True
SECURITY_REGISTERABLE=True
SECURITY_RECOVERABLE=True
SECURITY_CHANGEABLE=True
settings.py
import os
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from config import Config
from flask_mail import Mail, Message
from flask_migrate import Migrate
app=Flask(__name__)
app.config.from_object(Config)
mail=Mail(app)
db=SQLAlchemy(app)
migrate = Migrate(app, db)
The simplest answer is the create_user is an admin function. Flask-Security doesn't know anything about this since it isn't associated with a view.
Out of the box - as documented in the configuration.rst it can send mail for:
SEND_REGISTER_EMAIL, SEND_PASSWORD_CHANGE_EMAIL, SEND_PASSWORD_RESET_EMAIL, SEND_PASSWORD_RESET_NOTICE_EMAIL
that is all that is built in. Most sites would enable SECURITY_REGISTERABLE and allow users to sign up - at which point they will get an email.
It's been a while, but you can not send emails while TESTING = True in app.config :)
In your flask-mail settings file. Define a function for sending mail like this:
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)
Call this method where ever you want to trigger a mail action. For example:
#app.before_first_request
def create_user():
db.create_all() # Create the Roles "admin" and "office_owner" -- unless they already exist
user_datastore.find_or_create_role(name='admin', description='Administrator')
user_datastore.find_or_create_role(name='office_owner', description='Office owner')
if not user_datastore.get_user('test#gmail.com'):
user_datastore.create_user(email='test#gmail.com', password=flask_security.utils.hash_password('password')) # Commit any database changes; the User and Roles must exist before we can add a Role to the User
db.session.commit()
send_mail(subject="AccountCreation",Sender="somesender",recepient="somerecepient",text_body="congratulations account created ",text_html=None)
db.session.commit()

Celery Flask-Mail async fails. Sends without async. SMTPSenderRefused

I've just started working with Celery on my latest project with work; I'm having a bit of trouble with executing tasks asynchronously.
All the code is taken from Miguel Grinbergs 'Using Celery with Flask'
When sending the mail without executing a task, it sends perfectly fine. Though when I attempt to send the mail delayed, it fails out giving me an error as follows.
smtplib.SMTPSenderRefused: (530, b'5.5.1 Authentication Required. Learn more at\n5.5.1 https://support.google.com/mail/answer/14257 h19sm960819igq.6 - gsmtp', 'email-removed#gmail.com')
Here's the code I'm using, in my Flask app.
import os
import time
from flask import Flask, request, render_template, session, flash, redirect, url_for, jsonify
from flask.ext.mail import Mail, Message
from celery import Celery
app = Flask(__name__)
app.config['SECRET_KEY'] = 'super-duper-secret'
# Celery Configuration
app.config['CELERY_BROKER_URL'] = 'redis://localhost:6379/0'
app.config['CELERY_RESULT_BACKEND'] = 'redis://localhost:6379/0'
# Configuration for Flask-Mail
app.config['MAIL_SERVER'] = "smtp.gmail.com"
app.config['MAIL_PORT'] = 465
app.config['MAIL_USE_SSL'] = True
app.config['MAIL_USE_TLS'] = False
app.config['MAIL_USERNAME'] = 'email-removed#gmail.com'
app.config['MAIL_PASSWORD'] = 'password-removed'
app.config['MAIL_DEFAULT_SENDER'] = 'email-removed#gmail.com'
celery = Celery(app.name, broker=app.config['CELERY_BROKER_URL'])
celery.conf.update(app.config)
mail = Mail(app)
#app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'GET':
return render_template('index.html', email=session.get('email', ''))
email = request.form['email']
session['email'] = email
msg = Message("Hello from Flask", recipients=[email])
msg.body = "This is a test message from the flask application!"
if request.form['submit'] == "Send":
send_async_email(msg)
flash('Sending email to {0}'.format(email))
else:
send_async_email.apply_async(args=[msg], countdown=20)
flash('An email to {0} will be sent in a minute.'.format(email))
return redirect(url_for('index'))
#celery.task()
def send_async_email(msg):
with app.app_context():
mail.send(msg)
if __name__ == "__main__":
app.run(debug=True)
I'd appreciate some insight, and perhaps an explanation on how to make this work, and why it isn't working.
I've also looked upon other threads here, and turned on insecure-application access for my google accounts, along with nulling the captcha as suggested in the errors returned URL.

Categories