diff --git a/ihatemoney/utils.py b/ihatemoney/utils.py index d8ee112b..f6fab922 100644 --- a/ihatemoney/utils.py +++ b/ihatemoney/utils.py @@ -4,11 +4,14 @@ import re import os import ast import operator +import smtplib +import socket from io import BytesIO, StringIO + import jinja2 from json import dumps, JSONEncoder -from flask import redirect, current_app, render_template +from flask import flash, redirect, current_app, render_template from flask_babel import get_locale from babel import Locale from werkzeug.routing import HTTPException, RoutingException @@ -33,6 +36,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 45da8726..53dfc9aa 100644 --- a/ihatemoney/web.py +++ b/ihatemoney/web.py @@ -27,7 +27,7 @@ from ihatemoney.forms import ( AdminAuthenticationForm, AuthenticationForm, EditProjectForm, InviteForm, MemberForm, PasswordReminder, ResetPasswordForm, ProjectForm, get_billform_for ) -from ihatemoney.utils import Redirect303, list_of_dicts2json, list_of_dicts2csv, LoginThrottler, render_localized_template +from ihatemoney.utils import Redirect303, list_of_dicts2json, list_of_dicts2csv, LoginThrottler, render_localized_template, send_email main = Blueprint("main", __name__) @@ -247,10 +247,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)) @@ -267,16 +274,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) @@ -408,9 +420,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)