I am using flask mail to send messages from app. I am doing something like this:
app = Flask(__name__)
app.config.update(
MAIL_SERVER='smtp.gmail.com',
MAIL_PORT=465,
MAIL_USE_SSL=True,
MAIL_USERNAME = 'your#gmail.com',
MAIL_PASSWORD = 'yourpassword'
)
mail = Mail(app)
But I don't want my password be visible in my code. I want to encrypt it, for example. I can store in database but I still doesn't get how to keep it encrypted because I need to decrypt it to set MAIL_PASSWORD property.
Your app must be able to access your cleartext password, so encrypting it is useless in the scenario where the server gets compromised.
Encryption may be useful (but I don't suggest it) if you want to save it on a public place (e.g. code on github) and then you give the key only to the application (for example with a non tracked file or in a environment variable).
Since you must give your application some secret, I suggest you to simply give the email password as a secret.
For example using a file called local_settings.py you can do this:
# local_settings.py
MAIL_PASSWORD = 'mypassword'
# app.py
...
import local_settings
app = Flask(__name__)
app.config.update(
MAIL_SERVER='smtp.gmail.com',
MAIL_PORT=465,
MAIL_USE_SSL=True,
MAIL_USERNAME = 'your#gmail.com',
MAIL_PASSWORD = local_settings.MAIL_PASSWORD
)
mail = Mail(app)
Using environment variable is a good option (for mail password, database password, ...). Example:
import os
app = Flask(__name__)
app.config.update(
MAIL_SERVER='smtp.gmail.com',
MAIL_PORT=465,
MAIL_USE_SSL=True,
MAIL_USERNAME = 'your#gmail.com',
MAIL_PASSWORD = os.environ['MAIL_PASSWORD']
)
mail = Mail(app)
You can set value for MAIL_PASSWORD environment variable every time run script, using
In Linux: export MAIL_PASSWORD="{your_mail_password}"
In Windows: set MAIL_PASSWORD="{your_mail_password}"
Shouldn't use plain text in file to store password
Related
When I try to send an email using Flask-Mail to Gmail's SMTP server using the settings below, I get [Errno -2] Name or service not known. How do I fix my configuration to send email with Gmail?
from flask import Flask, render_template, redirect, url_for
from flask_mail import Mail, Message
app = Flask(__name__)
app.config.update(
MAIL_SERVER='smtp#gmail.com',
MAIL_PORT=587,
MAIL_USE_SSL=True,
MAIL_USERNAME = 'ri******a#gmail.com',
MAIL_PASSWORD = 'Ma*****fe'
)
mail = Mail(app)
#app.route('/send-mail/')
def send_mail():
msg = mail.send_message(
'Send Mail tutorial!',
sender='ri******a#gmail.com',
recipients=['ri*********07#msn.com'],
body="Congratulations you've succeeded!"
)
return 'Mail sent'
The server is "smtp.gmail.com".
The port must match the type of security used.
If using STARTTLS with MAIL_USE_TLS = True, then use MAIL_PORT = 587.
If using SSL/TLS directly with MAIL_USE_SSL = True, then use MAIL_PORT = 465.
Enable either STARTTLS or SSL/TLS, not both.
Depending on your Google account's security settings, you may need to generate and use an app password rather than the account password. This may also require enabling 2-step verification. You should probably set this up anyway.
MAIL_SERVER = 'smtp.gmail.com'
MAIL_PORT = 465
MAIL_USE_SSL = True
MAIL_USERNAME = 'username#gmail.com'
MAIL_PASSWORD = 'app password generated in step 3'
A small but important addition to davidism's answer:
You must have '2-step verification' enabled on your Google account before you're able to set up app-specific passwords.
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')))
I have tried what I can think of but can't get the email to be sent from my application using flask-mail with Zoho mail.
I've tried setting up an app password and I have tried the following examples of configuration using some of the information from their site:
https://www.zoho.com/mail/help/pop-access.html
app.config['MAIL_SERVER'] = 'smtp.zoho.com'
app.config['MAIL_PORT'] = 465
app.config['MAIL_USE_SSL'] = True
app.config['MAIL_USERNAME'] = '_#whatever.com'
app.config['MAIL_PASSWORD'] = 'XXXXXXXXXXXX'
app.config['MAIL_SERVER'] = 'smtp.zoho.com'
app.config['MAIL_PORT'] = 587
app.config['MAIL_USE_TLS'] = True
app.config['MAIL_USERNAME'] = '__#whatever.com'
app.config['MAIL_PASSWORD'] = 'XXXXXXXXXXX'
I would expect to be able to send an email using flask-mail with my custom domain which is setup with zoho.
Check which host of Zoho you are registered with, if you are based in Europe you will have to edit the MAIL_SEVER parameter to:
app.config['MAIL_SERVER'] = 'smtp.zoho.eu'
This should solve the SMTPAuthenticationError.
Similar question link
For secure access head on to general setting on Zoho mail(https://mail.zoho.com/zm/#settings/general)
My account>Security Questions
Application-Specific Passwords > Generate new password
name your app. It'll give you a password
export MAIL_PASSWORD=<generated-password>
then in config.py
import os
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
And when you use TLS Zoho on time of writing this uses the port 587\
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()
When I try to send an email using Flask-Mail to Gmail's SMTP server using the settings below, I get [Errno -2] Name or service not known. How do I fix my configuration to send email with Gmail?
from flask import Flask, render_template, redirect, url_for
from flask_mail import Mail, Message
app = Flask(__name__)
app.config.update(
MAIL_SERVER='smtp#gmail.com',
MAIL_PORT=587,
MAIL_USE_SSL=True,
MAIL_USERNAME = 'ri******a#gmail.com',
MAIL_PASSWORD = 'Ma*****fe'
)
mail = Mail(app)
#app.route('/send-mail/')
def send_mail():
msg = mail.send_message(
'Send Mail tutorial!',
sender='ri******a#gmail.com',
recipients=['ri*********07#msn.com'],
body="Congratulations you've succeeded!"
)
return 'Mail sent'
The server is "smtp.gmail.com".
The port must match the type of security used.
If using STARTTLS with MAIL_USE_TLS = True, then use MAIL_PORT = 587.
If using SSL/TLS directly with MAIL_USE_SSL = True, then use MAIL_PORT = 465.
Enable either STARTTLS or SSL/TLS, not both.
Depending on your Google account's security settings, you may need to generate and use an app password rather than the account password. This may also require enabling 2-step verification. You should probably set this up anyway.
MAIL_SERVER = 'smtp.gmail.com'
MAIL_PORT = 465
MAIL_USE_SSL = True
MAIL_USERNAME = 'username#gmail.com'
MAIL_PASSWORD = 'app password generated in step 3'
A small but important addition to davidism's answer:
You must have '2-step verification' enabled on your Google account before you're able to set up app-specific passwords.