Catch and display mail sending errors (#215)

This commit is contained in:
Tobias Kunze
2016-08-30 16:49:52 +02:00
committed by Raphael Michel
parent fe4946d591
commit 3c8f9f5a62
10 changed files with 75 additions and 34 deletions

View File

@@ -19,6 +19,10 @@ class TolerantDict(dict):
return key return key
class SendMailException(Exception):
pass
def mail(email: str, subject: str, template: str, def mail(email: str, subject: str, template: str,
context: Dict[str, Any]=None, event: Event=None, locale: str=None, context: Dict[str, Any]=None, event: Event=None, locale: str=None,
order: Order=None, headers: dict=None): order: Order=None, headers: dict=None):
@@ -46,8 +50,8 @@ def mail(email: str, subject: str, template: str,
:param locale: The locale to be used while evaluating the subject and the template :param locale: The locale to be used while evaluating the subject and the template
:raises Exception: on obvious, immediate failures. Not raising an exception does not necessarily mean that the :raises MailOrderException: on obvious, immediate failures. Not raising an exception does not necessarily mean
email has been sent, just that it has been queued by the email backend. that the email has been sent, just that it has been queued by the email backend.
""" """
with language(locale): with language(locale):
if isinstance(template, LazyI18nString): if isinstance(template, LazyI18nString):
@@ -94,7 +98,7 @@ def mail_send(to: str, subject: str, body: str, sender: str, event: int=None, he
backend.send_messages([email]) backend.send_messages([email])
except Exception: except Exception:
logger.exception('Error sending email') logger.exception('Error sending email')
raise raise SendMailException('Failed to send an email to {}.'.format(to))
if settings.HAS_CELERY and settings.EMAIL_BACKEND != 'django.core.mail.outbox': if settings.HAS_CELERY and settings.EMAIL_BACKEND != 'django.core.mail.outbox':

View File

@@ -14,7 +14,7 @@ from pretix.base.forms.auth import (
LoginForm, PasswordForgotForm, PasswordRecoverForm, RegistrationForm, LoginForm, PasswordForgotForm, PasswordRecoverForm, RegistrationForm,
) )
from pretix.base.models import User from pretix.base.models import User
from pretix.base.services.mail import mail from pretix.base.services.mail import SendMailException, mail
from pretix.helpers.urls import build_absolute_uri from pretix.helpers.urls import build_absolute_uri
@@ -103,15 +103,20 @@ class Forgot(TemplateView):
else: else:
rc.setex('pretix_pwreset_%s' % (user.id), 3600 * 24, '1') rc.setex('pretix_pwreset_%s' % (user.id), 3600 * 24, '1')
mail( try:
user.email, _('Password recovery'), 'pretixcontrol/email/forgot.txt', mail(
{ user.email, _('Password recovery'), 'pretixcontrol/email/forgot.txt',
'user': user, {
'url': (build_absolute_uri('control:auth.forgot.recover') 'user': user,
+ '?id=%d&token=%s' % (user.id, default_token_generator.make_token(user))) 'url': (build_absolute_uri('control:auth.forgot.recover')
}, + '?id=%d&token=%s' % (user.id, default_token_generator.make_token(user)))
None, locale=user.locale },
) None, locale=user.locale
)
except SendMailException:
messages.error(request, _('There was an error sending the mail. Please try again later.'))
return self.get(request, *args, **kwargs)
user.log_action('pretix.control.auth.user.forgot_password.mail_sent') user.log_action('pretix.control.auth.user.forgot_password.mail_sent')
messages.success(request, _('We sent you an e-mail containing further instructions.')) messages.success(request, _('We sent you an e-mail containing further instructions.'))
return redirect('control:auth.forgot') return redirect('control:auth.forgot')

View File

@@ -21,7 +21,7 @@ from pretix.base.services.invoices import (
generate_cancellation, generate_invoice, invoice_pdf, invoice_qualified, generate_cancellation, generate_invoice, invoice_pdf, invoice_qualified,
regenerate_invoice, regenerate_invoice,
) )
from pretix.base.services.mail import mail from pretix.base.services.mail import SendMailException, mail
from pretix.base.services.orders import cancel_order, mark_order_paid from pretix.base.services.orders import cancel_order, mark_order_paid
from pretix.base.services.stats import order_overview from pretix.base.services.stats import order_overview
from pretix.base.signals import ( from pretix.base.signals import (
@@ -201,6 +201,8 @@ class OrderTransition(OrderView):
mark_order_paid(self.order, manual=True, user=self.request.user) mark_order_paid(self.order, manual=True, user=self.request.user)
except Quota.QuotaExceededException as e: except Quota.QuotaExceededException as e:
messages.error(self.request, str(e)) messages.error(self.request, str(e))
except SendMailException:
messages.warning(self.request, _('The order has been marked as paid, but we were unable to send a confirmation mail.'))
else: else:
messages.success(self.request, _('The order has been marked as paid.')) messages.success(self.request, _('The order has been marked as paid.'))
elif self.order.status == Order.STATUS_PENDING and to == 'c': elif self.order.status == Order.STATUS_PENDING and to == 'c':
@@ -311,18 +313,23 @@ class OrderResendLink(OrderView):
def post(self, *args, **kwargs): def post(self, *args, **kwargs):
with language(self.order.locale): with language(self.order.locale):
mail( try:
self.order.email, _('Your order: %(code)s') % {'code': self.order.code}, mail(
self.order.event.settings.mail_text_resend_link, self.order.email, _('Your order: %(code)s') % {'code': self.order.code},
{ self.order.event.settings.mail_text_resend_link,
'event': self.order.event.name, {
'url': build_absolute_uri(self.order.event, 'presale:event.order', kwargs={ 'event': self.order.event.name,
'order': self.order.code, 'url': build_absolute_uri(self.order.event, 'presale:event.order', kwargs={
'secret': self.order.secret 'order': self.order.code,
}), 'secret': self.order.secret
}, }),
self.order.event, locale=self.order.locale },
) self.order.event, locale=self.order.locale
)
except SendMailException:
messages.error(self.request, _('There was an error sending the mail. Please try again later.'))
return redirect(self.get_order_url())
messages.success(self.request, _('The email has been queued to be sent.')) messages.success(self.request, _('The email has been queued to be sent.'))
self.order.log_action('pretix.event.order.resend', user=self.request.user) self.order.log_action('pretix.event.order.resend', user=self.request.user)
return redirect(self.get_order_url()) return redirect(self.get_order_url())

View File

@@ -4276,8 +4276,9 @@ msgid "Send"
msgstr "Senden" msgstr "Senden"
#: pretix/plugins/sendmail/views.py:43 #: pretix/plugins/sendmail/views.py:43
msgid "Your message will be sent to the selected users." msgid "Your message has been queued to be sent to the selected users."
msgstr "Die Nachricht wird an die ausgewählten Benutzer verschickt." msgstr ""
"Die Nachricht wurde zum Versenden an die ausgewählten Benutzer gespeichert."
#: pretix/plugins/statistics/__init__.py:10 #: pretix/plugins/statistics/__init__.py:10
#: pretix/plugins/statistics/__init__.py:14 #: pretix/plugins/statistics/__init__.py:14

View File

@@ -4265,8 +4265,9 @@ msgid "Send"
msgstr "Senden" msgstr "Senden"
#: pretix/plugins/sendmail/views.py:43 #: pretix/plugins/sendmail/views.py:43
msgid "Your message will be sent to the selected users." msgid "Your message has been queued to be sent to the selected users."
msgstr "Die Nachricht wird an die ausgewählten Benutzer verschickt." msgstr ""
"Die Nachricht wurde zum Versenden an die ausgewählten Benutzer gespeichert."
#: pretix/plugins/statistics/__init__.py:10 #: pretix/plugins/statistics/__init__.py:10
#: pretix/plugins/statistics/__init__.py:14 #: pretix/plugins/statistics/__init__.py:14

View File

@@ -15,6 +15,7 @@ from django.utils.translation import ugettext_lazy as _
from django.views.generic import TemplateView from django.views.generic import TemplateView
from pretix.base.models import Order, Quota from pretix.base.models import Order, Quota
from pretix.base.services.mail import SendMailException
from pretix.base.services.orders import mark_order_paid from pretix.base.services.orders import mark_order_paid
from pretix.base.settings import SettingsSandbox from pretix.base.settings import SettingsSandbox
from pretix.control.permissions import EventPermissionRequiredMixin from pretix.control.permissions import EventPermissionRequiredMixin
@@ -54,6 +55,7 @@ class ImportView(EventPermissionRequiredMixin, TemplateView):
orders = Order.objects.filter(event=self.request.event, orders = Order.objects.filter(event=self.request.event,
code__in=self.request.POST.getlist('mark_paid')) code__in=self.request.POST.getlist('mark_paid'))
some_failed = False some_failed = False
mail_failures = False
for order in orders: for order in orders:
try: try:
mark_order_paid(order, provider='banktransfer', info=json.dumps({ mark_order_paid(order, provider='banktransfer', info=json.dumps({
@@ -64,6 +66,8 @@ class ImportView(EventPermissionRequiredMixin, TemplateView):
})) }))
except Quota.QuotaExceededException: except Quota.QuotaExceededException:
some_failed = True some_failed = True
except SendMailException:
mail_failures = True
if some_failed: if some_failed:
messages.warning(self.request, _('Not all of the selected orders could be marked as ' messages.warning(self.request, _('Not all of the selected orders could be marked as '
@@ -72,6 +76,8 @@ class ImportView(EventPermissionRequiredMixin, TemplateView):
else: else:
messages.success(self.request, _('The selected orders have been marked as paid.')) messages.success(self.request, _('The selected orders have been marked as paid.'))
# TODO: Display a list of them! # TODO: Display a list of them!
if mail_failures:
messages.warning(self.request, _('Some confirmation mails could not be sent.'))
return self.redirect_back() return self.redirect_back()
messages.error(self.request, _('We were unable to detect the file type of this import. Please ' messages.error(self.request, _('We were unable to detect the file type of this import. Please '

View File

@@ -10,6 +10,7 @@ from django.utils.translation import ugettext as __, ugettext_lazy as _
from pretix.base.models import Quota from pretix.base.models import Quota
from pretix.base.payment import BasePaymentProvider from pretix.base.payment import BasePaymentProvider
from pretix.base.services.mail import SendMailException
from pretix.base.services.orders import mark_order_paid, mark_order_refunded from pretix.base.services.orders import mark_order_paid, mark_order_refunded
from pretix.multidomain.urlreverse import build_absolute_uri from pretix.multidomain.urlreverse import build_absolute_uri
@@ -182,6 +183,8 @@ class Paypal(BasePaymentProvider):
mark_order_paid(order, 'paypal', json.dumps(payment.to_dict())) mark_order_paid(order, 'paypal', json.dumps(payment.to_dict()))
except Quota.QuotaExceededException as e: except Quota.QuotaExceededException as e:
messages.error(request, str(e)) messages.error(request, str(e))
except SendMailException:
messages.warning(request, _('There was an error sending the confirmation mail.'))
return None return None
def order_pending_render(self, request, order) -> str: def order_pending_render(self, request, order) -> str:

View File

@@ -8,7 +8,7 @@ from django.utils.translation import ugettext_lazy as _
from django.views.generic import FormView from django.views.generic import FormView
from pretix.base.models import Order from pretix.base.models import Order
from pretix.base.services.mail import mail from pretix.base.services.mail import SendMailException, mail
from pretix.control.permissions import EventPermissionRequiredMixin from pretix.control.permissions import EventPermissionRequiredMixin
from . import forms from . import forms
@@ -36,11 +36,18 @@ class SenderView(EventPermissionRequiredMixin, FormView):
self.request.event.log_action('pretix.plugins.sendmail.sent', user=self.request.user, data=dict( self.request.event.log_action('pretix.plugins.sendmail.sent', user=self.request.user, data=dict(
form.cleaned_data)) form.cleaned_data))
failures = []
for o in orders: for o in orders:
mail(o.email, form.cleaned_data['subject'], form.cleaned_data['message'], try:
None, self.request.event, locale=o.locale, order=o) mail(o.email, form.cleaned_data['subject'], form.cleaned_data['message'],
None, self.request.event, locale=o.locale, order=o)
except SendMailException:
failures.append(o.email)
messages.success(self.request, _('Your message will be sent to the selected users.')) if failures:
messages.error(self.request, _('Failed to send mails to the following users: {}'.format(' '.join(failures))))
else:
messages.success(self.request, _('Your message has been queued to be sent to the selected users.'))
return redirect( return redirect(
'plugins:sendmail:send', 'plugins:sendmail:send',

View File

@@ -10,6 +10,7 @@ from django.utils.translation import ugettext_lazy as _
from pretix.base.models import Quota from pretix.base.models import Quota
from pretix.base.payment import BasePaymentProvider from pretix.base.payment import BasePaymentProvider
from pretix.base.services.mail import SendMailException
from pretix.base.services.orders import mark_order_paid, mark_order_refunded from pretix.base.services.orders import mark_order_paid, mark_order_refunded
from pretix.multidomain.urlreverse import build_absolute_uri from pretix.multidomain.urlreverse import build_absolute_uri
@@ -116,6 +117,9 @@ class Stripe(BasePaymentProvider):
mark_order_paid(order, 'stripe', str(charge)) mark_order_paid(order, 'stripe', str(charge))
except Quota.QuotaExceededException as e: except Quota.QuotaExceededException as e:
messages.error(request, str(e)) messages.error(request, str(e))
except SendMailException:
messages.warning(request, _('There was an error sending the confirmation mail.'))
else: else:
messages.warning(request, _('Stripe reported an error: %s' % charge.failure_message)) messages.warning(request, _('Stripe reported an error: %s' % charge.failure_message))
logger.info('Charge failed: %s' % str(charge)) logger.info('Charge failed: %s' % str(charge))

View File

@@ -12,6 +12,7 @@ from django.views.generic.base import TemplateResponseMixin
from pretix.base.models import CartPosition, Order from pretix.base.models import CartPosition, Order
from pretix.base.models.orders import InvoiceAddress from pretix.base.models.orders import InvoiceAddress
from pretix.base.services.mail import SendMailException
from pretix.base.services.orders import OrderError, perform_order from pretix.base.services.orders import OrderError, perform_order
from pretix.base.signals import register_payment_providers from pretix.base.signals import register_payment_providers
from pretix.multidomain.urlreverse import eventreverse from pretix.multidomain.urlreverse import eventreverse
@@ -351,6 +352,8 @@ class ConfirmStep(CartMixin, AsyncAction, TemplateFlowStep):
return exception['exc_message'] return exception['exc_message']
elif isinstance(exception, OrderError): elif isinstance(exception, OrderError):
return str(exception) return str(exception)
elif isinstance(exception, SendMailException):
return _('There was an error sending the confirmation mail. Please try again later.')
return super().get_error_message(exception) return super().get_error_message(exception)
def get_error_url(self): def get_error_url(self):