diff --git a/ihatemoney/utils.py b/ihatemoney/utils.py index 175b7621..d206593b 100644 --- a/ihatemoney/utils.py +++ b/ihatemoney/utils.py @@ -7,10 +7,12 @@ from json import JSONEncoder, dumps import operator import os import re +import smtplib +import socket from babel import Locale from babel.numbers import get_currency_name, get_currency_symbol -from flask import current_app, redirect, render_template +from flask import current_app, flash, redirect, render_template from flask_babel import get_locale, lazy_gettext as _ import jinja2 from werkzeug.routing import HTTPException, RoutingException @@ -28,6 +30,28 @@ def slugify(value): return re.sub(r"[-\s]+", "-", value) +def send_email(mail_message, flash_success="", flash_error=""): + """Send an email using Flask-Mail, returning False if there was an error. + + Optionally display a "flash alert" message to the user. The flash + message is different depending on whether we could successfully send + the email or not. + """ + # Since Python 3.4, SMTPException and socket.error are actually + # identical, but this was not the case before. Also, it is more clear + # to check for both. + try: + current_app.mail.send(mail_message) + except (smtplib.SMTPException, socket.error): + if flash_error: + flash(flash_error, category="danger") + return False + # Email was sent successfully + if flash_success: + flash(flash_success, category="success") + return True + + class Redirect303(HTTPException, RoutingException): """Raise if the map requests a redirect. This is for example the case if diff --git a/ihatemoney/web.py b/ihatemoney/web.py index ae124ac5..328ca095 100644 --- a/ihatemoney/web.py +++ b/ihatemoney/web.py @@ -12,7 +12,6 @@ from datetime import datetime from functools import wraps import json import os -from smtplib import SMTPRecipientsRefused from dateutil.parser import parse from dateutil.relativedelta import relativedelta @@ -60,6 +59,7 @@ from ihatemoney.utils import ( list_of_dicts2json, render_localized_template, same_bill, + send_email, ) main = Blueprint("main", __name__) @@ -308,11 +308,17 @@ def create_project(): msg = Message( message_title, body=message_body, recipients=[project.contact_email] ) - try: - current_app.mail.send(msg) - except SMTPRecipientsRefused: - flash(_("Error while sending reminder email"), category="danger") - + success = send_email(msg, _("A reminder email has just been sent to you")) + if not success: + # Display the error as a simple "info" alert, because it's + # not critical and doesn't prevent using the project. + flash( + _( + "We tried to send you an reminder email, but there was an error. " + "You can still use the project normally." + ), + category="info", + ) # redirect the user to the next step (invite) flash(_("The project identifier is %(project)s", project=project.id)) return redirect(url_for(".list_bills", project_id=project.id)) @@ -328,16 +334,21 @@ def remind_password(): # get the project project = Project.query.get(form.id.data) # send a link to reset the password - current_app.mail.send( - Message( - "password recovery", - body=render_localized_template( - "password_reminder", project=project - ), - recipients=[project.contact_email], - ) + remind_message = Message( + "password recovery", + body=render_localized_template("password_reminder", project=project), + recipients=[project.contact_email], ) - return redirect(url_for(".password_reminder_sent")) + success = send_email( + remind_message, + _("A password reminder email has just been sent to you"), + _( + "Sorry, there was an error while sending you an email with password reset instructions. " + "Please check the email configuration of the server or contact the administrator." + ), + ) + if success: + return redirect(url_for(".password_reminder_sent")) return render_template("password_reminder.html", form=form) @@ -585,9 +596,16 @@ def invite(): body=message_body, recipients=[email.strip() for email in form.emails.data.split(",")], ) - current_app.mail.send(msg) - flash(_("Your invitations have been sent")) - return redirect(url_for(".list_bills")) + success = send_email( + msg, + _("Your invitations have been sent"), + _( + "Sorry, there was an error while trying to send the invitation emails. " + "Please check the email configuration of the server or contact the administrator." + ), + ) + if success: + return redirect(url_for(".list_bills")) return render_template("send_invites.html", form=form)