diff --git a/src/pretix/base/email.py b/src/pretix/base/email.py
index feeded31f..5e2fa5c5e 100644
--- a/src/pretix/base/email.py
+++ b/src/pretix/base/email.py
@@ -520,20 +520,20 @@ def base_placeholders(sender, **kwargs):
lambda event: (event if not event.has_subevents or not event.subevents.exists() else event.subevents.first()).get_date_from_display()
),
SimpleFunctionalMailTextPlaceholder(
- 'url_remove', ['waiting_list_entry', 'event'],
- lambda waiting_list_entry, event: build_absolute_uri(
+ 'url_remove', ['waiting_list_voucher', 'event'],
+ lambda waiting_list_voucher, event: build_absolute_uri(
event, 'presale:event.waitinglist.remove'
- ) + '?voucher=' + waiting_list_entry.voucher.code,
+ ) + '?voucher=' + waiting_list_voucher.code,
lambda event: build_absolute_uri(
event,
'presale:event.waitinglist.remove',
) + '?voucher=68CYU2H6ZTP3WLK5',
),
SimpleFunctionalMailTextPlaceholder(
- 'url', ['waiting_list_entry', 'event'],
- lambda waiting_list_entry, event: build_absolute_uri(
+ 'url', ['waiting_list_voucher', 'event'],
+ lambda waiting_list_voucher, event: build_absolute_uri(
event, 'presale:event.redeem'
- ) + '?voucher=' + waiting_list_entry.voucher.code,
+ ) + '?voucher=' + waiting_list_voucher.code,
lambda event: build_absolute_uri(
event,
'presale:event.redeem',
diff --git a/src/pretix/base/models/waitinglist.py b/src/pretix/base/models/waitinglist.py
index 5e9002e29..98571d39b 100644
--- a/src/pretix/base/models/waitinglist.py
+++ b/src/pretix/base/models/waitinglist.py
@@ -20,6 +20,7 @@
# .
#
from datetime import timedelta
+from typing import Any, Dict, Union
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.db import models, transaction
@@ -27,14 +28,16 @@ from django.db.models import F, Q, Sum
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from django_scopes import ScopedManager
+from i18nfield.strings import LazyI18nString
from phonenumber_field.modelfields import PhoneNumberField
from pretix.base.email import get_email_context
from pretix.base.i18n import language
-from pretix.base.models import Voucher
-from pretix.base.services.mail import mail
+from pretix.base.models import User, Voucher
+from pretix.base.services.mail import SendMailException, mail, render_mail
from pretix.base.settings import PERSON_NAME_SCHEMES
+from ...helpers.format import format_map
from .base import LoggedModel
from .event import Event, SubEvent
from .items import Item, ItemVariation
@@ -213,15 +216,74 @@ class WaitingListEntry(LoggedModel):
self.voucher = v
self.save()
+ self.send_mail(
+ self.event.settings.mail_subject_waiting_list,
+ self.event.settings.mail_text_waiting_list,
+ get_email_context(
+ event=self.event,
+ waiting_list_entry=self,
+ waiting_list_voucher=v,
+ event_or_subevent=self.subevent or self.event,
+ ),
+ user=user,
+ auth=auth,
+ )
+
+ def send_mail(self, subject: Union[str, LazyI18nString], template: Union[str, LazyI18nString],
+ context: Dict[str, Any]=None, log_entry_type: str='pretix.waitinglist.email.sent',
+ user: User=None, headers: dict=None, sender: str=None, auth=None, auto_email=True,
+ attach_other_files: list=None, attach_cached_files: list=None):
+ """
+ Sends an email to the entry's contact address.
+
+ * Call ``pretix.base.services.mail.mail`` with useful values for the ``event``, ``locale``, and ``recipient``
+ parameters.
+
+ * Create a ``LogEntry`` with the email contents.
+
+ :param subject: Subject of the email
+ :param template: LazyI18nString or template filename, see ``pretix.base.services.mail.mail`` for more details
+ :param context: Dictionary to use for rendering the template
+ :param log_entry_type: Key to be used for the log entry
+ :param user: Administrative user who triggered this mail to be sent
+ :param headers: Dictionary with additional mail headers
+ :param sender: Custom email sender.
+ """
+ if not self.email:
+ return
+
+ for k, v in self.event.meta_data.items():
+ context['meta_' + k] = v
+
with language(self.locale, self.event.settings.region):
- mail(
- self.email,
- self.event.settings.mail_subject_waiting_list,
- self.event.settings.mail_text_waiting_list,
- get_email_context(event=self.event, waiting_list_entry=self),
- self.event,
- locale=self.locale
- )
+ recipient = self.email
+
+ try:
+ email_content = render_mail(template, context)
+ subject = format_map(subject, context)
+ mail(
+ recipient, subject, template, context,
+ self.event,
+ self.locale,
+ headers=headers,
+ sender=sender,
+ auto_email=auto_email,
+ attach_other_files=attach_other_files,
+ attach_cached_files=attach_cached_files,
+ )
+ except SendMailException:
+ raise
+ else:
+ self.log_action(
+ log_entry_type,
+ user=user,
+ auth=auth,
+ data={
+ 'subject': subject,
+ 'message': email_content,
+ 'recipient': recipient,
+ }
+ )
@staticmethod
def clean_itemvar(event, item, variation):
diff --git a/src/pretix/control/forms/event.py b/src/pretix/control/forms/event.py
index 0885e03d6..98843b979 100644
--- a/src/pretix/control/forms/event.py
+++ b/src/pretix/control/forms/event.py
@@ -1209,8 +1209,8 @@ class MailSettingsForm(SettingsForm):
'mail_text_resend_link': ['event', 'order'],
'mail_subject_resend_link': ['event', 'order'],
'mail_subject_resend_link_attendee': ['event', 'order'],
- 'mail_text_waiting_list': ['event', 'waiting_list_entry'],
- 'mail_subject_waiting_list': ['event', 'waiting_list_entry'],
+ 'mail_text_waiting_list': ['event', 'waiting_list_entry', 'waiting_list_voucher'],
+ 'mail_subject_waiting_list': ['event', 'waiting_list_entry', 'waiting_list_voucher'],
'mail_text_resend_all_links': ['event', 'orders'],
'mail_subject_resend_all_links': ['event', 'orders'],
'mail_attach_ical_description': ['event', 'event_or_subevent'],
diff --git a/src/pretix/plugins/sendmail/forms.py b/src/pretix/plugins/sendmail/forms.py
index 67109e77d..ca23bed4b 100644
--- a/src/pretix/plugins/sendmail/forms.py
+++ b/src/pretix/plugins/sendmail/forms.py
@@ -70,14 +70,7 @@ class FormPlaceholderMixin:
)
-class MailForm(FormPlaceholderMixin, forms.Form):
- recipients = forms.ChoiceField(
- label=_('Send email to'),
- widget=forms.RadioSelect,
- initial='orders',
- choices=[]
- )
- sendto = forms.MultipleChoiceField() # overridden later
+class BaseMailForm(FormPlaceholderMixin, forms.Form):
subject = forms.CharField(label=_("Subject"))
message = forms.CharField(label=_("Message"))
attachment = CachedFileField(
@@ -91,12 +84,100 @@ class MailForm(FormPlaceholderMixin, forms.Form):
help_text=_('Sending an attachment increases the chance of your email not arriving or being sorted into spam folders. We recommend only using PDFs '
'of no more than 2 MB in size.'),
max_size=settings.FILE_UPLOAD_MAX_SIZE_EMAIL_ATTACHMENT
- ) # TODO i18n
+ )
+
+ def __init__(self, *args, **kwargs):
+ event = self.event = kwargs.pop('event')
+ context_parameters = kwargs.pop('context_parameters')
+ super().__init__(*args, **kwargs)
+ self.fields['subject'] = I18nFormField(
+ label=_('Subject'),
+ widget=I18nTextInput, required=True,
+ locales=event.settings.get('locales'),
+ )
+ self.fields['message'] = I18nFormField(
+ label=_('Message'),
+ widget=I18nTextarea, required=True,
+ locales=event.settings.get('locales'),
+ )
+ self._set_field_placeholders('subject', context_parameters)
+ self._set_field_placeholders('message', context_parameters)
+
+
+class WaitinglistMailForm(BaseMailForm):
items = forms.ModelMultipleChoiceField(
widget=forms.CheckboxSelectMultiple(
attrs={'class': 'scrolling-multiple-choice'}
),
- label=_('Only send to people who bought'),
+ label=pgettext_lazy('sendmail_form', 'Waiting for'),
+ required=True,
+ queryset=Item.objects.none()
+ )
+ subevent = forms.ModelChoiceField(
+ SubEvent.objects.none(),
+ label=pgettext_lazy('sendmail_form', 'Restrict to a specific event date'),
+ required=False,
+ empty_label=pgettext_lazy('subevent', 'All dates')
+ )
+ subevents_from = forms.SplitDateTimeField(
+ widget=SplitDateTimePickerWidget(),
+ label=pgettext_lazy('sendmail_form', 'Restrict to event dates starting at or after'),
+ required=False,
+ )
+ subevents_to = forms.SplitDateTimeField(
+ widget=SplitDateTimePickerWidget(),
+ label=pgettext_lazy('sendmail_form', 'Restrict to event dates starting before'),
+ required=False,
+ )
+
+ def clean(self):
+ d = super().clean()
+ if d.get('subevent') and (d.get('subevents_from') or d.get('subevents_to')):
+ raise ValidationError(pgettext_lazy('subevent', 'Please either select a specific date or a date range, not both.'))
+ if bool(d.get('subevents_from')) != bool(d.get('subevents_to')):
+ raise ValidationError(pgettext_lazy('subevent', 'If you set a date range, please set both a start and an end.'))
+ return d
+
+ def __init__(self, *args, **kwargs):
+ event = self.event = kwargs['event']
+ super().__init__(*args, **kwargs)
+
+ self.fields['items'].queryset = event.items.all()
+ if not self.initial.get('items'):
+ self.initial['items'] = event.items.all()
+
+ if event.has_subevents:
+ self.fields['subevent'].queryset = event.subevents.all()
+ self.fields['subevent'].widget = Select2(
+ attrs={
+ 'data-model-select2': 'event',
+ 'data-select2-url': reverse('control:event.subevents.select2', kwargs={
+ 'event': event.slug,
+ 'organizer': event.organizer.slug,
+ }),
+ 'data-placeholder': pgettext_lazy('subevent', 'Date')
+ }
+ )
+ self.fields['subevent'].widget.choices = self.fields['subevent'].choices
+ else:
+ del self.fields['subevent']
+ del self.fields['subevents_from']
+ del self.fields['subevents_to']
+
+
+class OrderMailForm(BaseMailForm):
+ recipients = forms.ChoiceField(
+ label=pgettext_lazy('sendmail_from', 'Send to'),
+ widget=forms.RadioSelect,
+ initial='orders',
+ choices=[]
+ )
+ sendto = forms.MultipleChoiceField() # overridden later
+ items = forms.ModelMultipleChoiceField(
+ widget=forms.CheckboxSelectMultiple(
+ attrs={'class': 'scrolling-multiple-choice'}
+ ),
+ label=pgettext_lazy('sendmail_form', 'Restrict to products'),
required=True,
queryset=Item.objects.none()
)
@@ -105,31 +186,31 @@ class MailForm(FormPlaceholderMixin, forms.Form):
required=False
)
checkin_lists = SafeModelMultipleChoiceField(queryset=CheckinList.objects.none(), required=False) # overridden later
- not_checked_in = forms.BooleanField(label=_("Send to customers not checked in"), required=False)
+ not_checked_in = forms.BooleanField(label=pgettext_lazy('sendmail_from', 'Restrict to recipients without check-in'), required=False)
subevent = forms.ModelChoiceField(
SubEvent.objects.none(),
- label=_('Only send to customers of'),
+ label=pgettext_lazy('sendmail_form', 'Restrict to a specific event date'),
required=False,
empty_label=pgettext_lazy('subevent', 'All dates')
)
subevents_from = forms.SplitDateTimeField(
widget=SplitDateTimePickerWidget(),
- label=pgettext_lazy('subevent', 'Only send to customers of dates starting at or after'),
+ label=pgettext_lazy('sendmail_form', 'Restrict to event dates starting at or after'),
required=False,
)
subevents_to = forms.SplitDateTimeField(
widget=SplitDateTimePickerWidget(),
- label=pgettext_lazy('subevent', 'Only send to customers of dates starting before'),
+ label=pgettext_lazy('sendmail_form', 'Restrict to event dates starting before'),
required=False,
)
created_from = forms.SplitDateTimeField(
widget=SplitDateTimePickerWidget(),
- label=pgettext_lazy('subevent', 'Only send to customers with orders created after'),
+ label=pgettext_lazy('sendmail_form', 'Restrict to orders created at or after'),
required=False,
)
created_to = forms.SplitDateTimeField(
widget=SplitDateTimePickerWidget(),
- label=pgettext_lazy('subevent', 'Only send to customers with orders created before'),
+ label=pgettext_lazy('sendmail_form', 'Restrict to orders created before'),
required=False,
)
attach_tickets = forms.BooleanField(
@@ -147,7 +228,7 @@ class MailForm(FormPlaceholderMixin, forms.Form):
return d
def __init__(self, *args, **kwargs):
- event = self.event = kwargs.pop('event')
+ event = self.event = kwargs['event']
super().__init__(*args, **kwargs)
recp_choices = [
@@ -161,18 +242,6 @@ class MailForm(FormPlaceholderMixin, forms.Form):
]
self.fields['recipients'].choices = recp_choices
- self.fields['subject'] = I18nFormField(
- label=_('Subject'),
- widget=I18nTextInput, required=True,
- locales=event.settings.get('locales'),
- )
- self.fields['message'] = I18nFormField(
- label=_('Message'),
- widget=I18nTextarea, required=True,
- locales=event.settings.get('locales'),
- )
- self._set_field_placeholders('subject', ['event', 'order', 'position_or_address'])
- self._set_field_placeholders('message', ['event', 'order', 'position_or_address'])
choices = [(e, l) for e, l in Order.STATUS_CHOICE if e != 'n']
choices.insert(0, ('na', _('payment pending (except unapproved)')))
choices.insert(0, ('pa', _('approval pending')))
@@ -181,7 +250,7 @@ class MailForm(FormPlaceholderMixin, forms.Form):
('overdue', _('pending with payment overdue'))
)
self.fields['sendto'] = forms.MultipleChoiceField(
- label=_("Send to customers with order status"),
+ label=pgettext_lazy('sendmail_from', 'Restrict to orders with status'),
widget=forms.CheckboxSelectMultiple(
attrs={'class': 'scrolling-multiple-choice no-search'}
),
@@ -205,11 +274,11 @@ class MailForm(FormPlaceholderMixin, forms.Form):
'event': event.slug,
'organizer': event.organizer.slug,
}),
- 'data-placeholder': _('Send to customers checked in on list'),
+ 'data-placeholder': pgettext_lazy('sendmail_from', 'Restrict to recipients with check-in on list')
}
)
self.fields['checkin_lists'].widget.choices = self.fields['checkin_lists'].choices
- self.fields['checkin_lists'].label = _('Send to customers checked in on list')
+ self.fields['checkin_lists'].label = pgettext_lazy('sendmail_from', 'Restrict to recipients with check-in on list')
if event.has_subevents:
self.fields['subevent'].queryset = event.subevents.all()
diff --git a/src/pretix/plugins/sendmail/signals.py b/src/pretix/plugins/sendmail/signals.py
index 4a5cd674e..6e3c7e2cf 100644
--- a/src/pretix/plugins/sendmail/signals.py
+++ b/src/pretix/plugins/sendmail/signals.py
@@ -48,10 +48,11 @@ from django_scopes import scope, scopes_disabled
from pretix.base.models import SubEvent
from pretix.base.signals import (
- event_copy_data, logentry_display, periodic_task,
+ EventPluginSignal, event_copy_data, logentry_display, periodic_task,
)
from pretix.control.signals import nav_event
from pretix.plugins.sendmail.models import ScheduledMail
+from pretix.plugins.sendmail.views import OrderSendView, WaitinglistSendView
logger = logging.getLogger(__name__)
@@ -91,7 +92,7 @@ def control_nav_import(sender, request=None, **kwargs):
'event': request.event.slug,
'organizer': request.event.organizer.slug,
}),
- 'active': (url.namespace == 'plugins:sendmail' and url.url_name == 'send'),
+ 'active': (url.namespace == 'plugins:sendmail' and url.url_name.startswith('send')),
},
{
'label': _('Automated emails'),
@@ -117,7 +118,8 @@ def control_nav_import(sender, request=None, **kwargs):
@receiver(signal=logentry_display)
def pretixcontrol_logentry_display(sender, logentry, **kwargs):
plains = {
- 'pretix.plugins.sendmail.sent': _('Email was sent'),
+ 'pretix.plugins.sendmail.sent': _('Mass email was sent to customers or attendees.'),
+ 'pretix.plugins.sendmail.sent.waitinglist': _('Mass email was sent to waiting list entries.'),
'pretix.plugins.sendmail.order.email.sent': _('The order received a mass email.'),
'pretix.plugins.sendmail.order.email.sent.attendee': _('A ticket holder of this order received a mass email.'),
'pretix.plugins.sendmail.rule.added': _('An email rule was created'),
@@ -219,3 +221,17 @@ def sendmail_copy_data_receiver(sender, other, item_map, **kwargs):
r.save()
if limit_products:
r.limit_products.add(*[item_map[p.id] for p in limit_products if p.id in item_map])
+
+
+sendmail_view_classes = EventPluginSignal()
+"""
+This signal allows you to register subclasses of ``pretix.plugins.sendmail.views.BaseSenderView`` that should be
+discovered by this plugin.
+
+As with all plugin signals, the ``sender`` keyword will contain the event.
+"""
+
+
+@receiver(signal=sendmail_view_classes, dispatch_uid="sendmail_register_sendmail_view_classes")
+def register_view_classes(sender, **kwargs):
+ return [OrderSendView, WaitinglistSendView]
diff --git a/src/pretix/plugins/sendmail/tasks.py b/src/pretix/plugins/sendmail/tasks.py
index 0a4cdd453..af04f369d 100644
--- a/src/pretix/plugins/sendmail/tasks.py
+++ b/src/pretix/plugins/sendmail/tasks.py
@@ -44,12 +44,12 @@ from pretix.helpers.format import format_map
@app.task(base=ProfiledEventTask, acks_late=True)
-def send_mails(event: Event, user: int, subject: dict, message: dict, orders: list, items: list,
- recipients: str, filter_checkins: bool, not_checked_in: bool, checkin_lists: list,
- attachments: list = None, attach_tickets: bool = False) -> None:
+def send_mails_to_orders(event: Event, user: int, subject: dict, message: dict, objects: list, items: list,
+ recipients: str, filter_checkins: bool, not_checked_in: bool, checkin_lists: list,
+ attachments: list = None, attach_tickets: bool = False) -> None:
failures = []
user = User.objects.get(pk=user) if user else None
- orders = Order.objects.filter(pk__in=orders, event=event)
+ orders = Order.objects.filter(pk__in=objects, event=event)
subject = LazyI18nString(subject)
message = LazyI18nString(message)
@@ -138,7 +138,7 @@ def send_mails(event: Event, user: int, subject: dict, message: dict, orders: li
locale=o.locale,
order=o,
attach_tickets=attach_tickets,
- attach_cached_files=attachments
+ attach_cached_files=attachments,
)
o.log_action(
'pretix.plugins.sendmail.order.email.sent',
@@ -151,3 +151,27 @@ def send_mails(event: Event, user: int, subject: dict, message: dict, orders: li
)
except SendMailException:
failures.append(o.email)
+
+
+@app.task(base=ProfiledEventTask, acks_late=True)
+def send_mails_to_waitinglist(event: Event, user: int, subject: dict, message: dict, objects: list,
+ attachments: list = None) -> None:
+ user = User.objects.get(pk=user) if user else None
+ entries = event.waitinglistentries.filter(pk__in=objects).select_related(
+ 'subevent'
+ )
+ subject = LazyI18nString(subject)
+ message = LazyI18nString(message)
+
+ for e in entries:
+ e.send_mail(
+ subject,
+ message,
+ get_email_context(
+ event=e.event,
+ waiting_list_entry=e,
+ event_or_subevent=e.subevent or e.event,
+ ),
+ user=user,
+ attach_cached_files=attachments,
+ )
diff --git a/src/pretix/plugins/sendmail/templates/pretixplugins/sendmail/history.html b/src/pretix/plugins/sendmail/templates/pretixplugins/sendmail/history.html
index 57480a9f2..970ba287e 100644
--- a/src/pretix/plugins/sendmail/templates/pretixplugins/sendmail/history.html
+++ b/src/pretix/plugins/sendmail/templates/pretixplugins/sendmail/history.html
@@ -22,33 +22,7 @@
{% if log.display %}
{{ log.display }}
{% endif %}
-
{% trans "Sent to orders:" %}
- {% for status in log.parsed_data.sendto %}
- {{ status }}{% if forloop.revcounter > 1 %},{% endif %}
- {% endfor %}
- {% if log.pdata.items %}
-
{{ log.pdata.items|join:", " }}
- {% endif %}
- {% if log.pdata.filter_checkins %}
- {% if log.pdata.not_checked_in %}
-
{% trans "All customers not checked in" %}
- {% endif %}
- {% if log.pdata.checkin_lists %}
-
{{ log.pdata.checkin_lists|join:", " }}
- {% endif %}
- {% endif %}
- {% if log.pdata.subevent_obj %}
-
{{ log.pdata.subevent_obj }}
- {% elif log.pdata.subevents_from %}
-
{{ log.pdata.subevents_from }} – {{ log.pdata.subevents_to }}
- {% endif %}
- {% if log.pdata.recipients == "attendees" %}
-
{% trans "Attendee contact addresses" %}
- {% elif log.pdata.recipients == "both" %}
-
{% trans "All contact addresses" %}
- {% else%}
-
{% trans "Order contact addresses" %}
- {% endif %}
+ {{ log.view.rendered_data }}
{% for locale, value in log.pdata.locales.items %}
@@ -58,7 +32,7 @@
{{ value.message|linebreaksbr }}
{% endfor %}
- {% trans "Send a new email based on this" %}
+ {% trans "Send a new email based on this" %}
{% endfor %}
diff --git a/src/pretix/plugins/sendmail/templates/pretixplugins/sendmail/history_fragment_orders.html b/src/pretix/plugins/sendmail/templates/pretixplugins/sendmail/history_fragment_orders.html
new file mode 100644
index 000000000..ebfb6ca27
--- /dev/null
+++ b/src/pretix/plugins/sendmail/templates/pretixplugins/sendmail/history_fragment_orders.html
@@ -0,0 +1,28 @@
+{% load i18n %}
+
{% trans "Sent to orders:" %}
+{% for status in log.parsed_data.sendto %}
+ {{ status }}{% if forloop.revcounter > 1 %},{% endif %}
+{% endfor %}
+{% if log.pdata.items %}
+
{{ log.pdata.items|join:", " }}
+{% endif %}
+{% if log.pdata.filter_checkins %}
+ {% if log.pdata.not_checked_in %}
+
{% trans "All customers not checked in" %}
+ {% endif %}
+ {% if log.pdata.checkin_lists %}
+
{{ log.pdata.checkin_lists|join:", " }}
+ {% endif %}
+{% endif %}
+{% if log.pdata.subevent_obj %}
+
{{ log.pdata.subevent_obj }}
+{% elif log.pdata.subevents_from %}
+
{{ log.pdata.subevents_from }} – {{ log.pdata.subevents_to }}
+{% endif %}
+{% if log.pdata.recipients == "attendees" %}
+
{% trans "Attendee contact addresses" %}
+{% elif log.pdata.recipients == "both" %}
+
{% trans "All contact addresses" %}
+{% else%}
+
{% trans "Order contact addresses" %}
+{% endif %}
diff --git a/src/pretix/plugins/sendmail/templates/pretixplugins/sendmail/history_fragment_waitinglist.html b/src/pretix/plugins/sendmail/templates/pretixplugins/sendmail/history_fragment_waitinglist.html
new file mode 100644
index 000000000..8bea24ac7
--- /dev/null
+++ b/src/pretix/plugins/sendmail/templates/pretixplugins/sendmail/history_fragment_waitinglist.html
@@ -0,0 +1,9 @@
+{% load i18n %}
+{% if log.pdata.items %}
+
{{ log.pdata.items|join:", " }}
+{% endif %}
+{% if log.pdata.subevent_obj %}
+
{{ log.pdata.subevent_obj }}
+{% elif log.pdata.subevents_from %}
+
{{ log.pdata.subevents_from }} – {{ log.pdata.subevents_to }}
+{% endif %}
diff --git a/src/pretix/plugins/sendmail/templates/pretixplugins/sendmail/index.html b/src/pretix/plugins/sendmail/templates/pretixplugins/sendmail/index.html
new file mode 100644
index 000000000..e491905a9
--- /dev/null
+++ b/src/pretix/plugins/sendmail/templates/pretixplugins/sendmail/index.html
@@ -0,0 +1,16 @@
+{% extends "pretixcontrol/event/base.html" %}
+{% load i18n %}
+{% block title %}{% trans "Send out emails" %}{% endblock %}
+{% block content %}
+ {% trans "Send out emails" %}
+
+{% endblock %}
diff --git a/src/pretix/plugins/sendmail/templates/pretixplugins/sendmail/send_form.html b/src/pretix/plugins/sendmail/templates/pretixplugins/sendmail/send_form.html
index 4250aa6c7..c562fb959 100644
--- a/src/pretix/plugins/sendmail/templates/pretixplugins/sendmail/send_form.html
+++ b/src/pretix/plugins/sendmail/templates/pretixplugins/sendmail/send_form.html
@@ -4,7 +4,10 @@
{% load humanize %}
{% block title %}{% trans "Send out emails" %}{% endblock %}
{% block content %}
- {% trans "Send out emails" %}
+
+ {% trans "Send out emails" %}
+ {{ view_title }}
+
{% block inner %}